1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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();
}
}
}
|