aboutsummaryrefslogtreecommitdiff
path: root/guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java')
-rw-r--r--guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java118
1 files changed, 118 insertions, 0 deletions
diff --git a/guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java b/guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java
new file mode 100644
index 000000000..bf10f5f75
--- /dev/null
+++ b/guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.common.collect;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.lang.reflect.Modifier.PRIVATE;
+import static java.lang.reflect.Modifier.PROTECTED;
+import static java.lang.reflect.Modifier.PUBLIC;
+import static java.util.Arrays.asList;
+
+import com.google.common.base.Optional;
+import com.google.common.reflect.ClassPath;
+import com.google.common.reflect.ClassPath.ClassInfo;
+import com.google.common.reflect.TypeToken;
+import java.lang.reflect.Method;
+import junit.framework.TestCase;
+
+/**
+ * Tests that all package-private {@code writeReplace} methods are overridden in any existing
+ * subclasses. Without such overrides, optimizers might put a {@code writeReplace}-containing class
+ * and its subclass in different packages, causing the serialization system to fail to invoke {@code
+ * writeReplace} when serializing an instance of the subclass. For an example of this problem, see
+ * b/310253115.
+ */
+public class WriteReplaceOverridesTest extends TestCase {
+ private static final ImmutableSet<String> GUAVA_PACKAGES =
+ FluentIterable.of(
+ "base",
+ "cache",
+ "collect",
+ "escape",
+ "eventbus",
+ "graph",
+ "hash",
+ "html",
+ "io",
+ "math",
+ "net",
+ "primitives",
+ "reflect",
+ "util.concurrent",
+ "xml")
+ .transform("com.google.common."::concat)
+ .toSet();
+
+ public void testClassesHaveOverrides() throws Exception {
+ for (ClassInfo info : ClassPath.from(getClass().getClassLoader()).getAllClasses()) {
+ if (!GUAVA_PACKAGES.contains(info.getPackageName())) {
+ continue;
+ }
+ if (info.getName().endsWith("GwtSerializationDependencies")) {
+ continue; // These classes exist only for the GWT compiler, not to be used.
+ }
+ if (
+ /*
+ * At least one of the classes nested inside TypeResolverTest triggers a bug under older JDKs:
+ * https://bugs.openjdk.org/browse/JDK-8215328 -> https://bugs.openjdk.org/browse/JDK-8215470
+ * https://github.com/google/guava/blob/4f12c5891a7adedbaa1d99fc9f77d8cc4e9da206/guava-tests/test/com/google/common/reflect/TypeResolverTest.java#L201
+ */
+ info.getName().contains("TypeResolverTest")
+ /*
+ * And at least one of the classes inside TypeTokenTest ends up with a null value in
+ * TypeMappingIntrospector.mappings. That happens only under older JDKs, too, so it may
+ * well be a JDK bug.
+ */
+ || info.getName().contains("TypeTokenTest")
+ /*
+ * Luckily, we don't care about analyzing tests at all. We'd skip them all if we could do so
+ * trivially, but it's enough to skip these ones.
+ */
+ ) {
+ continue;
+ }
+ Class<?> clazz = info.load();
+ try {
+ Method unused = clazz.getDeclaredMethod("writeReplace");
+ continue; // It overrides writeReplace, so it's safe.
+ } catch (NoSuchMethodException e) {
+ // This is a class whose supertypes we want to examine. We'll do that below.
+ }
+ Optional<Class<?>> supersWithPackagePrivateWriteReplace =
+ FluentIterable.from(TypeToken.of(clazz).getTypes())
+ .transform(TypeToken::getRawType)
+ .transformAndConcat(c -> asList(c.getDeclaredMethods()))
+ .firstMatch(
+ m ->
+ m.getName().equals("writeReplace")
+ && m.getParameterTypes().length == 0
+ // Only package-private methods are a problem.
+ && (m.getModifiers() & (PUBLIC | PROTECTED | PRIVATE)) == 0)
+ .transform(Method::getDeclaringClass);
+ if (!supersWithPackagePrivateWriteReplace.isPresent()) {
+ continue;
+ }
+ assertWithMessage(
+ "To help optimizers, any class that inherits a package-private writeReplace() method"
+ + " should override that method.\n"
+ + "(An override that delegates to the supermethod is fine.)\n"
+ + "%s has no such override despite inheriting writeReplace() from %s",
+ clazz.getName(), supersWithPackagePrivateWriteReplace.get().getName())
+ .fail();
+ }
+ }
+}