Skip to content

Commit 144516a

Browse files
committed
[GR-10194] Support for java.lang.reflect.Proxy.
PullRequest: graal/1616
2 parents ad6abc4 + 7a28101 commit 144516a

File tree

20 files changed

+853
-136
lines changed

20 files changed

+853
-136
lines changed

substratevm/DYNAMIC_PROXY.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Dynamic proxies on Substrate VM
2+
3+
Java dynamic proxies, implemented by `java.lang.reflect.Proxy`, provide a mechanism which enables object level access control by routing all method invocations through a `java.lang.reflect.InvocationHandler`. Dynamic proxy classes are generated from a list of interfaces.
4+
5+
Substrate VM doesn't provide machinery for generating and interpreting bytecodes at run time. Therefore all dynamic proxy classes need to be generated at native image build time
6+
7+
8+
# Automatic detection
9+
Substrate VM employs a simple static analysis that detects calls to `java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)` and `java.lang.reflect.Proxy.getProxyClass(ClassLoader, Class<?>[])` and tries to determine the list of interfaces that define dynamic proxies automatically. Given the list of interfaces then Substrate VM generates the proxy classes at image build time and adds them to the native image heap. In addition to generating the dynamic proxy class the constructor of the generated class that takes a `java.lang.reflect.InvocationHandler` argument, i.e., the one reflectively invoked by `java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)`, is registered for reflection so that dynamic proxy instances can be allocated at run time.
10+
11+
The analysis is limited to situations where the list of interfaces comes from a constant array or an array that is allocated in the same method. For example in the code snippets bellow the dynamic proxy interfaces can be determined automatically.
12+
13+
### Static final array:
14+
15+
```
16+
class ProxyFactory {
17+
18+
private static final Class<?>[] interfaces = new Class<?>[]{java.util.Comparator.class};
19+
20+
static Comparator createProxyInstanceFromConstantArray() {
21+
ClassLoader classLoader = ProxyFactory.class.getClassLoader();
22+
InvocationHandler handler = new ProxyInvocationHandler();
23+
return (Comparator) Proxy.newProxyInstance(classLoader, interfaces, handler);
24+
}
25+
}
26+
```
27+
Note: The analysis operates on Graal graphs and not source code. Therefore the following ways to declare and populate an array are equivalent from the point of view of the analysis:
28+
29+
```
30+
private static final Class<?>[] interfacesArrayPreInitialized = new Class<?>[]{java.util.Comparator.class};
31+
```
32+
33+
```
34+
private static final Class<?>[] interfacesArrayLiteral = {java.util.Comparator.class};
35+
```
36+
37+
```
38+
private static final Class<?>[] interfacesArrayPostInitialized = new Class<?>[1];
39+
static {
40+
interfacesArrayPostInitialized[0] = java.util.Comparator.class;
41+
}
42+
```
43+
However, in Java there are no immutable arrays. Even if the array is declared as `static final` its contents can change later on. The simple analysis that we employ here doesn't track further changes to the array.
44+
45+
### New array:
46+
47+
```
48+
class ProxyFactory {
49+
50+
static Comparator createProxyInstanceFromNewArray() {
51+
ClassLoader classLoader = ProxyFactory.class.getClassLoader();
52+
InvocationHandler handler = new ProxyInvocationHandler();
53+
Class<?>[] interfaces = new Class<?>[]{java.util.Comparator.class};
54+
return (Comparator) Proxy.newProxyInstance(classLoader, interfaces, handler);
55+
}
56+
}
57+
```
58+
59+
Note: Just like with constant arrays, the following ways to declare and populate an array are equivalent from the point of view of the analysis:
60+
```
61+
Class<?>[] interfaces = new Class<?>[]{java.util.Comparator.class};
62+
```
63+
64+
```
65+
Class<?>[] interfaces = new Class<?>[1];
66+
interfaces[0] = Question.class;
67+
```
68+
69+
```
70+
Class<?>[] interfaces = {java.util.Comparator.class};
71+
```
72+
73+
The static analysis covers code patterns most frequently used to define dynamic proxy classes. For the exceptional cases where the analysis cannot discover the interface array there is also a manual dynamic proxy configuration mechanism.
74+
75+
# Manual configuration
76+
Dynamic proxy classes can be generated at native image build time by specifying the list of interfaces that they implement. Substrate VM provides two options for this purpose: `-H:DynamicProxyConfigurationFiles=<comma-separated-config-files>` and `-H:DynamicProxyConfigurationResources=<comma-separated-config-resources>`.
77+
78+
These options accept JSON files whose structure is an array of arrays of fully qualified interface names.
79+
80+
Example:
81+
82+
```
83+
[
84+
["java.lang.AutoCloseable", "java.util.Comparator"],
85+
["java.util.Comparator"],
86+
["java.util.List"]
87+
]
88+
```
89+
90+
The `java.lang.reflect.Proxy` API also allows creation of a dynamic proxy that doesn't implement any user provided interfaces. Therefore the following is a valid configuration:
91+
92+
```
93+
[
94+
[]
95+
]
96+
```
97+
98+
In this case the generated dynamic proxy class only implements `java.lang.reflect.Proxy`.
99+
100+
# Dynamic proxy classes in static initializers
101+
102+
Dynamic proxy classes and instances of dynamic proxy classes that are defined in static initializers can be accessed at runtime without any special handling. This is possible since the static initializers are executed at native image build time. For example this will work:
103+
104+
```
105+
private final static Comparator proxyInstance;
106+
private final static Class<?> proxyClass;
107+
static {
108+
ClassLoader classLoader = ProxyFactory.class.getClassLoader();
109+
InvocationHandler handler = new ProxyInvocationHandler();
110+
Class<?>[] interfaces = {java.util.Comparator.class};
111+
proxyInstance = (Comparator) Proxy.newProxyInstance(classLoader, interfaces, handler);
112+
proxyClass = Proxy.getProxyClass(classLoader, interfaces);
113+
}
114+
```

substratevm/LIMITATIONS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Substrate VM does not support all features of Java to keep the implementation sm
77
| ---------- | ----------|
88
| [Dynamic Class Loading / Unloading](#dynamic-class-loading--unloading) | Not supported|
99
| [Reflection](#reflection) | Mostly supported|
10+
| [Dynamic Proxy](#dynamic-proxy) | Mostly supported|
1011
| [Java Native Interface (JNI)](#java-native-interface--jni) | Mostly supported|
1112
| [Unsafe Memory Access](#unsafe-memory-access) | Mostly supported |
1213
| [Static Initializers](#static-initializers) | Partially supported|
@@ -42,6 +43,14 @@ Individual classes, methods, and fields that should be accessible via reflection
4243

4344
During native image generation, reflection can be used without restrictions during native image generation, for example in static initializers.
4445

46+
Dynamic Proxy
47+
----------
48+
49+
**Support Status: Mostly supported**
50+
51+
What: Generating dynamic proxy classes and allocating instances of dynamic proxy classes using the `java.lang.reflect.Proxy` API.
52+
53+
Dynamic class proxies are supported as long as the bytecodes are generated ahead-of-time. This means that the list of interfaces that define dynamic proxies needs to be known at image build time. SubstrateVM employs a simple static analysis that intercepts calls to `java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)` and `java.lang.reflect.Proxy.getProxyClass(ClassLoader, Class<?>[])` and tries to determine the list of interfaces automatically. Where the analysis fails the lists of interfaces can be specified via configuration files. For more details, read our [documentation on dynamic proxies](DYNAMIC_PROXY.md).
4554

4655
Java Native Interface (JNI)
4756
---------------------------

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangReflectSubstitutions.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,10 @@
2727
// Checkstyle: allow reflection
2828

2929
import java.lang.reflect.Array;
30-
import java.lang.reflect.InvocationHandler;
3130

3231
import org.graalvm.compiler.word.BarrieredAccess;
3332
import org.graalvm.word.UnsignedWord;
3433

35-
import com.oracle.svm.core.annotate.Delete;
3634
import com.oracle.svm.core.annotate.Substitute;
3735
import com.oracle.svm.core.annotate.TargetClass;
3836
import com.oracle.svm.core.config.ConfigurationValues;
@@ -41,24 +39,6 @@
4139
import com.oracle.svm.core.snippets.KnownIntrinsics;
4240
import com.oracle.svm.core.util.VMError;
4341

44-
@TargetClass(java.lang.reflect.Proxy.class)
45-
final class Target_java_lang_reflect_Proxy {
46-
47-
/*
48-
* We cannot mark the whole Proxy class as @Delete, since the JDK implements annotation
49-
* interfaces via proxies. SVM uses its own mechanism for annotations, without using the
50-
* InvocationHandler field "h". All other usages of Proxy must be disallowed, so we mark the
51-
* field "h" as deleted.
52-
*/
53-
@Delete private InvocationHandler h;
54-
55-
@Delete private static Target_java_lang_reflect_WeakCache proxyClassCache;
56-
}
57-
58-
@TargetClass(className = "java.lang.reflect.WeakCache")
59-
final class Target_java_lang_reflect_WeakCache {
60-
}
61-
6242
@TargetClass(java.lang.reflect.Array.class)
6343
final class Target_java_lang_reflect_Array {
6444

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2018, 2018, 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.core.jdk.proxy;
26+
27+
// Checkstyle: allow reflection
28+
29+
import org.graalvm.nativeimage.Platform;
30+
import org.graalvm.nativeimage.Platforms;
31+
32+
public interface DynamicProxyRegistry {
33+
34+
@Platforms(Platform.HOSTED_ONLY.class)
35+
void addProxyClass(Class<?>... interfaces);
36+
37+
Class<?> getProxyClass(Class<?>... interfaces);
38+
39+
boolean isProxyClass(Class<?> clazz);
40+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2018, 2018, 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.core.jdk.proxy;
26+
27+
// Checkstyle: allow reflection
28+
29+
import java.lang.reflect.Proxy;
30+
31+
import org.graalvm.nativeimage.ImageSingletons;
32+
33+
import com.oracle.svm.core.annotate.Delete;
34+
import com.oracle.svm.core.annotate.Substitute;
35+
import com.oracle.svm.core.annotate.TargetClass;
36+
37+
@TargetClass(java.lang.reflect.Proxy.class)
38+
final class Target_java_lang_reflect_Proxy {
39+
40+
/** We have our own proxy cache so mark the original one as deleted. */
41+
@Delete private static Target_java_lang_reflect_WeakCache proxyClassCache;
42+
43+
@Substitute
44+
private static Class<?> getProxyClass0(@SuppressWarnings("unused") ClassLoader loader, Class<?>... interfaces) {
45+
return ImageSingletons.lookup(DynamicProxyRegistry.class).getProxyClass(interfaces);
46+
}
47+
48+
@Substitute
49+
public static boolean isProxyClass(Class<?> cl) {
50+
return Proxy.class.isAssignableFrom(cl) && ImageSingletons.lookup(DynamicProxyRegistry.class).isProxyClass(cl);
51+
}
52+
}
53+
54+
@TargetClass(className = "java.lang.reflect.WeakCache")
55+
final class Target_java_lang_reflect_WeakCache {
56+
}
57+
58+
public class ProxySubstitutions {
59+
}

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import com.oracle.svm.hosted.substitute.DeclarativeSubstitutionProcessor;
8282
import com.oracle.svm.jni.hosted.JNIFeature;
8383
import com.oracle.svm.reflect.hosted.ReflectionFeature;
84+
import com.oracle.svm.reflect.proxy.hosted.DynamicProxyFeature;
8485

8586
public class NativeImage {
8687

@@ -143,6 +144,8 @@ private static <T> String oH(OptionKey<T> option) {
143144
static final String oHIncludeResourceBundles = oH(LocalizationSupport.Options.IncludeResourceBundles);
144145
static final String oHReflectionConfigurationFiles = oH(ReflectionFeature.Options.ReflectionConfigurationFiles);
145146
static final String oHReflectionConfigurationResources = oH(ReflectionFeature.Options.ReflectionConfigurationResources);
147+
static final String oHDynamicProxyConfigurationFiles = oH(DynamicProxyFeature.Options.DynamicProxyConfigurationFiles);
148+
static final String oHDynamicProxyConfigurationResources = oH(DynamicProxyFeature.Options.DynamicProxyConfigurationResources);
146149
static final String oHJNIConfigurationFiles = oH(JNIFeature.Options.JNIConfigurationFiles);
147150
static final String oHJNIConfigurationResources = oH(JNIFeature.Options.JNIConfigurationResources);
148151
static final String oHInterfacesForJNR = oH + "InterfacesForJNR=";
@@ -477,6 +480,8 @@ private void completeImageBuildArgs(String[] args) {
477480
consolidateListArgs(imageBuilderArgs, oHInterfacesForJNR, ",", Function.identity());
478481
consolidateListArgs(imageBuilderArgs, oHReflectionConfigurationFiles, ",", canonicalizedPathStr);
479482
consolidateListArgs(imageBuilderArgs, oHReflectionConfigurationResources, ",", Function.identity());
483+
consolidateListArgs(imageBuilderArgs, oHDynamicProxyConfigurationFiles, ",", canonicalizedPathStr);
484+
consolidateListArgs(imageBuilderArgs, oHDynamicProxyConfigurationResources, ",", Function.identity());
480485
consolidateListArgs(imageBuilderArgs, oHJNIConfigurationFiles, ",", canonicalizedPathStr);
481486
consolidateListArgs(imageBuilderArgs, oHJNIConfigurationResources, ",", Function.identity());
482487
consolidateListArgs(imageBuilderArgs, oHFeatures, ",", Function.identity());

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,9 +1254,9 @@ private void checkName(String name, AnalysisMethod method) {
12541254
*/
12551255
String lname = name.toLowerCase();
12561256
if (lname.contains("hosted")) {
1257-
bigbang.getUnsupportedFeatures().addMessage(name, method, "Hosted element used at run time");
1257+
bigbang.getUnsupportedFeatures().addMessage(name, method, "Hosted element used at run time: " + name);
12581258
} else if (lname.contains("hotspot")) {
1259-
bigbang.getUnsupportedFeatures().addMessage(name, method, "HotSpot element used at run time");
1259+
bigbang.getUnsupportedFeatures().addMessage(name, method, "HotSpot element used at run time: " + name);
12601260
}
12611261
}
12621262

0 commit comments

Comments
 (0)