Skip to content

Commit 9e8701b

Browse files
committed
Implement stable name for Proxy type
1 parent 450475d commit 9e8701b

File tree

5 files changed

+248
-1
lines changed

5 files changed

+248
-1
lines changed

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This changelog summarizes major changes to GraalVM Native Image.
77
* (GR-49996) Ensure explicitly set image name (e.g., via `-o imagename`) is not accidentally overwritten by `-jar jarfile` option.
88
* (GR-48683) Together with Red Hat, we added partial support for the JFR event `OldObjectSample`.
99
* (GR-47109) Together with Red Hat, we added support for JFR event throttling and the event `ObjectAllocationSample`.
10+
* (GR-52030) Add a stable name for `Proxy` types in Native Image. The name `$Proxy[id]` is replaced by `$Proxy.s[hashCode]` where `hashCode` is computed using the names of the `Proxy` interfaces, the name of the class loader and the name of the module.
1011

1112
## GraalVM for JDK 22 (Internal Version 24.0.0)
1213
* (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics.

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@
253253
import com.oracle.svm.hosted.phases.SubstrateClassInitializationPlugin;
254254
import com.oracle.svm.hosted.phases.VerifyDeoptLIRFrameStatesPhase;
255255
import com.oracle.svm.hosted.phases.VerifyNoGuardsPhase;
256+
import com.oracle.svm.hosted.reflect.proxy.ProxyRenamingSubstitutionProcessor;
256257
import com.oracle.svm.hosted.snippets.SubstrateGraphBuilderPlugins;
257258
import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor;
258259
import com.oracle.svm.hosted.substitute.DeletedFieldsPlugin;
@@ -1056,7 +1057,8 @@ public static SubstitutionProcessor createAnalysisSubstitutionProcessor(
10561057
List<SubstitutionProcessor> additionalSubstitutionProcessors) {
10571058
List<SubstitutionProcessor> allProcessors = new ArrayList<>();
10581059
SubstitutionProcessor cFunctionSubstitutions = new CFunctionSubstitutionProcessor();
1059-
allProcessors.addAll(Arrays.asList(annotationSubstitutions, cFunctionSubstitutions, cEnumProcessor));
1060+
SubstitutionProcessor proxySubstitutionProcessor = new ProxyRenamingSubstitutionProcessor();
1061+
allProcessors.addAll(Arrays.asList(annotationSubstitutions, cFunctionSubstitutions, cEnumProcessor, proxySubstitutionProcessor));
10601062
allProcessors.addAll(additionalSubstitutionProcessors);
10611063
return SubstitutionProcessor.chainUpInOrder(allProcessors.toArray(new SubstitutionProcessor[0]));
10621064
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.reflect.proxy;
26+
27+
import java.lang.reflect.Proxy;
28+
import java.util.Arrays;
29+
import java.util.HashSet;
30+
import java.util.Set;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
import java.util.concurrent.ConcurrentMap;
33+
34+
import org.graalvm.nativeimage.hosted.Feature;
35+
36+
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
37+
import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor;
38+
39+
import jdk.vm.ci.meta.ResolvedJavaType;
40+
41+
/**
42+
* A substitution processor that renames Proxy classes using a stable name. The name is created
43+
* using the name of the interfaces composing the Proxy type combined with the name of the class
44+
* loader of the type.
45+
* <p>
46+
* Needs to be created before
47+
* {@link org.graalvm.nativeimage.hosted.Feature#duringSetup(Feature.DuringSetupAccess)} as Proxy
48+
* types can be created in the serialization feature.
49+
*/
50+
public class ProxyRenamingSubstitutionProcessor extends SubstitutionProcessor {
51+
public static final String DYNAMIC_MODULE_REGEX = "jdk[.]proxy[0-9]+";
52+
public static final String NULL_CLASS_LOADER_NAME = "native-image-null-class-loader";
53+
public static final String NULL_MODULE_NAME = "native-image-null-module";
54+
public static final String UNNAMED_MODULE_NAME = "native-image-unnamed";
55+
private static final String STABLE_NAME_TEMPLATE = "/$Proxy.s";
56+
57+
private final ConcurrentMap<ResolvedJavaType, ProxySubstitutionType> typeSubstitutions = new ConcurrentHashMap<>();
58+
private final Set<String> uniqueTypeNames = new HashSet<>();
59+
60+
public static boolean isProxyType(ResolvedJavaType type) {
61+
Class<?> clazz = OriginalClassProvider.getJavaClass(type);
62+
return Proxy.isProxyClass(clazz);
63+
}
64+
65+
/**
66+
* The code creating the name of dynamic modules can be found in
67+
* Proxy$ProxyBuilder.getDynamicModule
68+
*/
69+
private static boolean isModuleDynamic(Module module) {
70+
return module != null && module.getName() != null && module.getName().matches(DYNAMIC_MODULE_REGEX);
71+
}
72+
73+
@Override
74+
public ResolvedJavaType lookup(ResolvedJavaType type) {
75+
if (!shouldReplace(type)) {
76+
return type;
77+
}
78+
return getSubstitution(type);
79+
}
80+
81+
private static boolean shouldReplace(ResolvedJavaType type) {
82+
return !(type instanceof ProxySubstitutionType) && isProxyType(type);
83+
}
84+
85+
private ProxySubstitutionType getSubstitution(ResolvedJavaType original) {
86+
Class<?> clazz = OriginalClassProvider.getJavaClass(original);
87+
return typeSubstitutions.computeIfAbsent(original, key -> new ProxySubstitutionType(key, getUniqueProxyName(clazz)));
88+
}
89+
90+
public String getUniqueProxyName(Class<?> clazz) {
91+
StringBuilder sb = new StringBuilder();
92+
93+
Class<?>[] interfaces = clazz.getInterfaces();
94+
Arrays.stream(interfaces).forEach(i -> sb.append(i.getName()));
95+
96+
/*
97+
* Two proxy classes with the same interfaces and two different class loaders with the same
98+
* name or without a name but from the same class will produce the same stable name.
99+
*
100+
* The module of proxy classes without a package private interface contains a unique id that
101+
* depends on the class loader. This id is assigned to the class loaders based on the order
102+
* in which they are used to create a proxy class. In some rare cases, this order can be
103+
* different in two build processes, meaning the module will contain a different id and the
104+
* name will not be stable.
105+
*/
106+
ClassLoader classLoader = clazz.getClassLoader();
107+
String classLoaderName = classLoader == null ? NULL_CLASS_LOADER_NAME : classLoader.getName();
108+
sb.append(classLoaderName != null ? classLoaderName : classLoader.getClass().getName());
109+
110+
Module module = clazz.getModule();
111+
if (!isModuleDynamic(module)) {
112+
String moduleName = module == null ? NULL_MODULE_NAME : module.getName();
113+
sb.append(moduleName != null ? moduleName : UNNAMED_MODULE_NAME);
114+
}
115+
116+
return findUniqueName(clazz, sb.toString().hashCode());
117+
}
118+
119+
private String findUniqueName(Class<?> clazz, int hashCode) {
120+
CharSequence baseName = "L" + clazz.getPackageName().replace('.', '/') + STABLE_NAME_TEMPLATE + Integer.toHexString(hashCode);
121+
String name = baseName + ";";
122+
synchronized (uniqueTypeNames) {
123+
int suffix = 1;
124+
while (uniqueTypeNames.contains(name)) {
125+
name = baseName + "_" + suffix + ";";
126+
suffix++;
127+
}
128+
uniqueTypeNames.add(name);
129+
return name;
130+
}
131+
}
132+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.reflect.proxy;
26+
27+
import com.oracle.svm.hosted.annotation.CustomSubstitutionType;
28+
29+
import jdk.vm.ci.meta.ResolvedJavaType;
30+
31+
public class ProxySubstitutionType extends CustomSubstitutionType {
32+
private final String stableName;
33+
34+
ProxySubstitutionType(ResolvedJavaType original, String stableName) {
35+
super(original);
36+
this.stableName = stableName;
37+
}
38+
39+
@Override
40+
public String getName() {
41+
return stableName;
42+
}
43+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.reflect.proxy;
26+
27+
import java.util.HashSet;
28+
import java.util.List;
29+
import java.util.Set;
30+
31+
import com.oracle.graal.pointsto.meta.AnalysisType;
32+
import com.oracle.svm.core.SubstrateUtil;
33+
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
34+
import com.oracle.svm.core.feature.InternalFeature;
35+
import com.oracle.svm.hosted.FeatureImpl.AfterAnalysisAccessImpl;
36+
37+
/**
38+
* @see ProxyRenamingSubstitutionProcessor
39+
*/
40+
@AutomaticallyRegisteredFeature
41+
final class StableProxyNameFeature implements InternalFeature {
42+
@Override
43+
public void afterAnalysis(AfterAnalysisAccess access) {
44+
assert checkProxyNames(((AfterAnalysisAccessImpl) access).getUniverse().getTypes());
45+
}
46+
47+
private static boolean checkProxyNames(List<AnalysisType> types) {
48+
if (!SubstrateUtil.assertionsEnabled()) {
49+
throw new AssertionError("Expensive check: should only run with assertions enabled.");
50+
}
51+
/* There should be no random proxy type names visible to the analysis. */
52+
if (types.stream().anyMatch(type -> ProxyRenamingSubstitutionProcessor.isProxyType(type) && type.getWrapped().getClass() != ProxySubstitutionType.class)) {
53+
throw new AssertionError("All proxies should be substituted.");
54+
}
55+
56+
/* Proxy names should be unique. */
57+
Set<String> proxyNames = new HashSet<>();
58+
types.stream()
59+
.filter(ProxyRenamingSubstitutionProcessor::isProxyType)
60+
.map(AnalysisType::getName)
61+
.forEach(name -> {
62+
if (proxyNames.contains(name)) {
63+
throw new AssertionError("Duplicate proxy name: " + name);
64+
}
65+
proxyNames.add(name);
66+
});
67+
return true;
68+
}
69+
}

0 commit comments

Comments
 (0)