mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8159746: (proxy) Support for default methods
Co-authored-by: Peter Levart <plevart@openjdk.org> Reviewed-by: darcy, alanb, plevart
This commit is contained in:
parent
1433bafb33
commit
56b15fbbcc
29 changed files with 1380 additions and 148 deletions
|
@ -2221,6 +2221,9 @@ public final class System {
|
|||
public void addReadsAllUnnamed(Module m) {
|
||||
m.implAddReadsAllUnnamed();
|
||||
}
|
||||
public void addExports(Module m, String pn) {
|
||||
m.implAddExports(pn);
|
||||
}
|
||||
public void addExports(Module m, String pn, Module other) {
|
||||
m.implAddExports(pn, other);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -25,6 +25,12 @@
|
|||
|
||||
package java.lang.reflect;
|
||||
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@code InvocationHandler} is the interface implemented by
|
||||
* the <i>invocation handler</i> of a proxy instance.
|
||||
|
@ -92,4 +98,197 @@ public interface InvocationHandler {
|
|||
*/
|
||||
public Object invoke(Object proxy, Method method, Object[] args)
|
||||
throws Throwable;
|
||||
|
||||
/**
|
||||
* Invokes the specified default method on the given {@code proxy} instance with
|
||||
* the given parameters. The given {@code method} must be a default method
|
||||
* declared in a proxy interface of the {@code proxy}'s class or inherited
|
||||
* from its superinterface directly or indirectly.
|
||||
* <p>
|
||||
* Invoking this method behaves as if {@code invokespecial} instruction executed
|
||||
* from the proxy class, targeting the default method in a proxy interface.
|
||||
* This is equivalent to the invocation:
|
||||
* {@code X.super.m(A* a)} where {@code X} is a proxy interface and the call to
|
||||
* {@code X.super::m(A*)} is resolved to the given {@code method}.
|
||||
* <p>
|
||||
* Examples: interface {@code A} and {@code B} both declare a default
|
||||
* implementation of method {@code m}. Interface {@code C} extends {@code A}
|
||||
* and inherits the default method {@code m} from its superinterface {@code A}.
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* interface A {
|
||||
* default T m(A a) { return t1; }
|
||||
* }
|
||||
* interface B {
|
||||
* default T m(A a) { return t2; }
|
||||
* }
|
||||
* interface C extends A {}
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* The following creates a proxy instance that implements {@code A}
|
||||
* and invokes the default method {@code A::m}.
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { A.class },
|
||||
* (o, m, params) -> {
|
||||
* if (m.isDefault()) {
|
||||
* // if it's a default method, invoke it
|
||||
* return InvocationHandler.invokeDefault(o, m, params);
|
||||
* }
|
||||
* });
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* If a proxy instance implements both {@code A} and {@code B}, both
|
||||
* of which provides the default implementation of method {@code m},
|
||||
* the invocation handler can dispatch the method invocation to
|
||||
* {@code A::m} or {@code B::m} via the {@code invokeDefault} method.
|
||||
* For example, the following code delegates the method invocation
|
||||
* to {@code B::m}.
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { A.class, B.class },
|
||||
* (o, m, params) -> {
|
||||
* if (m.getName().equals("m")) {
|
||||
* // invoke B::m instead of A::m
|
||||
* Method bMethod = B.class.getMethod(m.getName(), m.getParameterTypes());
|
||||
* return InvocationHandler.invokeDefault(o, bMethod, params);
|
||||
* }
|
||||
* });
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* If a proxy instance implements {@code C} that inherits the default
|
||||
* method {@code m} from its superinterface {@code A}, then
|
||||
* the interface method invocation on {@code "m"} is dispatched to
|
||||
* the invocation handler's {@link #invoke(Object, Method, Object[]) invoke}
|
||||
* method with the {@code Method} object argument representing the
|
||||
* default method {@code A::m}.
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { C.class },
|
||||
* (o, m, params) -> {
|
||||
* if (m.isDefault()) {
|
||||
* // behaves as if calling C.super.m(params)
|
||||
* return InvocationHandler.invokeDefault(o, m, params);
|
||||
* }
|
||||
* });
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* The invocation of method {@code "m"} on this {@code proxy} will behave
|
||||
* as if {@code C.super::m} is called and that is resolved to invoking
|
||||
* {@code A::m}.
|
||||
* <p>
|
||||
* Adding a default method, or changing a method from abstract to default
|
||||
* may cause an exception if an existing code attempts to call {@code invokeDefault}
|
||||
* to invoke a default method.
|
||||
*
|
||||
* For example, if {@code C} is modified to implement a default method
|
||||
* {@code m}:
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* interface C extends A {
|
||||
* default T m(A a) { return t3; }
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* The code above that creates proxy instance {@code proxy} with
|
||||
* the modified {@code C} will run with no exception and it will result in
|
||||
* calling {@code C::m} instead of {@code A::m}.
|
||||
* <p>
|
||||
* The following is another example that creates a proxy instance of {@code C}
|
||||
* and the invocation handler calls the {@code invokeDefault} method
|
||||
* to invoke {@code A::m}:
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* C c = (C) Proxy.newProxyInstance(loader, new Class<?>[] { C.class },
|
||||
* (o, m, params) -> {
|
||||
* if (m.getName().equals("m")) {
|
||||
* // IllegalArgumentException thrown as {@code A::m} is not a method
|
||||
* // inherited from its proxy interface C
|
||||
* Method aMethod = A.class.getMethod(m.getName(), m.getParameterTypes());
|
||||
* return InvocationHandler.invokeDefault(o, aMethod params);
|
||||
* }
|
||||
* });
|
||||
* c.m(...);
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* The above code runs successfully with the old version of {@code C} and
|
||||
* {@code A::m} is invoked. When running with the new version of {@code C},
|
||||
* the above code will fail with {@code IllegalArgumentException} because
|
||||
* {@code C} overrides the implementation of the same method and
|
||||
* {@code A::m} is not accessible by a proxy instance.
|
||||
*
|
||||
* @apiNote
|
||||
* The {@code proxy} parameter is of type {@code Object} rather than {@code Proxy}
|
||||
* to make it easy for {@link InvocationHandler#invoke(Object, Method, Object[])
|
||||
* InvocationHandler::invoke} implementation to call directly without the need
|
||||
* of casting.
|
||||
*
|
||||
* @param proxy the {@code Proxy} instance on which the default method to be invoked
|
||||
* @param method the {@code Method} instance corresponding to a default method
|
||||
* declared in a proxy interface of the proxy class or inherited
|
||||
* from its superinterface directly or indirectly
|
||||
* @param args the parameters used for the method invocation; can be {@code null}
|
||||
* if the number of formal parameters required by the method is zero.
|
||||
* @return the value returned from the method invocation
|
||||
*
|
||||
* @throws IllegalArgumentException if any of the following conditions is {@code true}:
|
||||
* <ul>
|
||||
* <li>{@code proxy} is not {@linkplain Proxy#isProxyClass(Class)
|
||||
* a proxy instance}; or</li>
|
||||
* <li>the given {@code method} is not a default method declared
|
||||
* in a proxy interface of the proxy class and not inherited from
|
||||
* any of its superinterfaces; or</li>
|
||||
* <li>the given {@code method} is overridden directly or indirectly by
|
||||
* the proxy interfaces and the method reference to the named
|
||||
* method never resolves to the given {@code method}; or</li>
|
||||
* <li>the length of the given {@code args} array does not match the
|
||||
* number of parameters of the method to be invoked; or</li>
|
||||
* <li>any of the {@code args} elements fails the unboxing
|
||||
* conversion if the corresponding method parameter type is
|
||||
* a primitive type; or if, after possible unboxing, any of the
|
||||
* {@code args} elements cannot be assigned to the corresponding
|
||||
* method parameter type.</li>
|
||||
* </ul>
|
||||
* @throws IllegalAccessException if the declaring class of the specified
|
||||
* default method is inaccessible to the caller class
|
||||
* @throws NullPointerException if {@code proxy} or {@code method} is {@code null}
|
||||
* @throws Throwable anything thrown by the default method
|
||||
|
||||
* @since 16
|
||||
* @jvms 5.4.3. Method Resolution
|
||||
*/
|
||||
@CallerSensitive
|
||||
public static Object invokeDefault(Object proxy, Method method, Object... args)
|
||||
throws Throwable {
|
||||
Objects.requireNonNull(proxy);
|
||||
Objects.requireNonNull(method);
|
||||
|
||||
// verify that the object is actually a proxy instance
|
||||
if (!Proxy.isProxyClass(proxy.getClass())) {
|
||||
throw new IllegalArgumentException("'proxy' is not a proxy instance");
|
||||
}
|
||||
if (!method.isDefault()) {
|
||||
throw new IllegalArgumentException("\"" + method + "\" is not a default method");
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Proxy> proxyClass = (Class<? extends Proxy>)proxy.getClass();
|
||||
|
||||
Class<?> intf = method.getDeclaringClass();
|
||||
// access check on the default method
|
||||
method.checkAccess(Reflection.getCallerClass(), intf, proxyClass, method.getModifiers());
|
||||
|
||||
MethodHandle mh = Proxy.defaultMethodHandle(proxyClass, method);
|
||||
// invoke the super method
|
||||
try {
|
||||
// the args array can be null if the number of formal parameters required by
|
||||
// the method is zero (consistent with Method::invoke)
|
||||
Object[] params = args != null ? args : Proxy.EMPTY_ARGS;
|
||||
return mh.invokeExact(proxy, params);
|
||||
} catch (ClassCastException | NullPointerException e) {
|
||||
throw new IllegalArgumentException(e.getMessage(), e);
|
||||
} catch (Proxy.InvocationException e) {
|
||||
// unwrap and throw the exception thrown by the default method
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import jdk.internal.reflect.MethodAccessor;
|
|||
import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.reflect.annotation.ExceptionProxy;
|
||||
import sun.reflect.annotation.TypeNotPresentExceptionProxy;
|
||||
import sun.reflect.generics.repository.MethodRepository;
|
||||
|
@ -66,6 +67,7 @@ import java.util.StringJoiner;
|
|||
* @since 1.1
|
||||
*/
|
||||
public final class Method extends Executable {
|
||||
@Stable
|
||||
private Class<?> clazz;
|
||||
private int slot;
|
||||
// This is guaranteed to be interned by the VM in the 1.4
|
||||
|
@ -74,6 +76,7 @@ public final class Method extends Executable {
|
|||
private Class<?> returnType;
|
||||
private Class<?>[] parameterTypes;
|
||||
private Class<?>[] exceptionTypes;
|
||||
@Stable
|
||||
private int modifiers;
|
||||
// Generics and annotations support
|
||||
private transient String signature;
|
||||
|
|
|
@ -25,11 +25,17 @@
|
|||
|
||||
package java.lang.reflect;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.WrongMethodTypeException;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
|
@ -37,25 +43,26 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.loader.BootLoader;
|
||||
import jdk.internal.module.Modules;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.loader.ClassLoaderValue;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.reflect.misc.ReflectUtil;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
import sun.security.util.SecurityConstants;
|
||||
|
||||
import static java.lang.invoke.MethodType.methodType;
|
||||
import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* {@code Proxy} provides static methods for creating objects that act like instances
|
||||
|
@ -144,6 +151,12 @@ import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
|
|||
* InvocationHandler#invoke invoke} method as described in the
|
||||
* documentation for that method.
|
||||
*
|
||||
* <li>A proxy interface may define a default method or inherit
|
||||
* a default method from its superinterface directly or indirectly.
|
||||
* An invocation handler can invoke a default method of a proxy interface
|
||||
* by calling {@link InvocationHandler#invokeDefault(Object, Method, Object...)
|
||||
* InvocationHandler::invokeDefault}.
|
||||
*
|
||||
* <li>An invocation of the {@code hashCode},
|
||||
* {@code equals}, or {@code toString} methods declared in
|
||||
* {@code java.lang.Object} on a proxy instance will be encoded and
|
||||
|
@ -172,9 +185,8 @@ import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
|
|||
* packages:
|
||||
* <ol type="a">
|
||||
* <li>if all the proxy interfaces are <em>public</em>, then the proxy class is
|
||||
* <em>public</em> in a package exported by the
|
||||
* {@linkplain ClassLoader#getUnnamedModule() unnamed module} of the specified
|
||||
* loader. The name of the package is unspecified.</li>
|
||||
* <em>public</em> in an unconditionally exported but non-open package.
|
||||
* The name of the package and the module are unspecified.</li>
|
||||
*
|
||||
* <li>if at least one of all the proxy interfaces is <em>non-public</em>, then
|
||||
* the proxy class is <em>non-public</em> in the package and module of the
|
||||
|
@ -483,6 +495,7 @@ public class Proxy implements java.io.Serializable {
|
|||
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
|
||||
String proxyPkg = null; // package to define proxy class in
|
||||
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
|
||||
boolean nonExported = false;
|
||||
|
||||
/*
|
||||
* Record the package of a non-public proxy interface so that the
|
||||
|
@ -500,13 +513,20 @@ public class Proxy implements java.io.Serializable {
|
|||
throw new IllegalArgumentException(
|
||||
"non-public interfaces from different packages");
|
||||
}
|
||||
} else {
|
||||
if (!intf.getModule().isExported(intf.getPackageName())) {
|
||||
// module-private types
|
||||
nonExported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (proxyPkg == null) {
|
||||
// all proxy interfaces are public
|
||||
proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
|
||||
: PROXY_PACKAGE_PREFIX;
|
||||
// all proxy interfaces are public and exported
|
||||
if (!m.isNamed())
|
||||
throw new InternalError("ununamed module: " + m);
|
||||
proxyPkg = nonExported ? PROXY_PACKAGE_PREFIX + "." + m.getName()
|
||||
: m.getName();
|
||||
} else if (proxyPkg.isEmpty() && m.isNamed()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unnamed package cannot be added to " + m);
|
||||
|
@ -644,6 +664,8 @@ public class Proxy implements java.io.Serializable {
|
|||
*/
|
||||
Constructor<?> build() {
|
||||
Class<?> proxyClass = defineProxyClass(module, interfaces);
|
||||
assert !module.isNamed() || module.isOpen(proxyClass.getPackageName(), Proxy.class.getModule());
|
||||
|
||||
final Constructor<?> cons;
|
||||
try {
|
||||
cons = proxyClass.getConstructor(constructorParams);
|
||||
|
@ -740,96 +762,89 @@ public class Proxy implements java.io.Serializable {
|
|||
/**
|
||||
* Returns the module that the generated proxy class belongs to.
|
||||
*
|
||||
* If all proxy interfaces are public and in exported packages,
|
||||
* then the proxy class is in unnamed module.
|
||||
*
|
||||
* If any of proxy interface is package-private, then the proxy class
|
||||
* is in the same module of the package-private interface.
|
||||
*
|
||||
* If all proxy interfaces are public and in exported packages,
|
||||
* then the proxy class is in a dynamic module in an unconditionally
|
||||
* exported package.
|
||||
*
|
||||
* If all proxy interfaces are public and at least one in a non-exported
|
||||
* package, then the proxy class is in a dynamic module in a
|
||||
* non-exported package. Reads edge and qualified exports are added
|
||||
* for dynamic module to access.
|
||||
* non-exported package.
|
||||
*
|
||||
* The package of proxy class is open to java.base for deep reflective access.
|
||||
*
|
||||
* Reads edge and qualified exports are added for dynamic module to access.
|
||||
*/
|
||||
private static Module mapToModule(ClassLoader loader,
|
||||
List<Class<?>> interfaces,
|
||||
Set<Class<?>> refTypes) {
|
||||
Map<Class<?>, Module> modulePrivateTypes = new HashMap<>();
|
||||
Map<Class<?>, Module> packagePrivateTypes = new HashMap<>();
|
||||
for (Class<?> intf : interfaces) {
|
||||
Module m = intf.getModule();
|
||||
if (Modifier.isPublic(intf.getModifiers())) {
|
||||
// module-private types
|
||||
if (!m.isExported(intf.getPackageName())) {
|
||||
modulePrivateTypes.put(intf, m);
|
||||
}
|
||||
} else {
|
||||
if (!Modifier.isPublic(intf.getModifiers())) {
|
||||
packagePrivateTypes.put(intf, m);
|
||||
}
|
||||
}
|
||||
|
||||
// all proxy interfaces are public and exported, the proxy class
|
||||
// is in unnamed module. Such proxy class is accessible to
|
||||
// any unnamed module and named module that can read unnamed module
|
||||
if (packagePrivateTypes.isEmpty() && modulePrivateTypes.isEmpty()) {
|
||||
return loader != null ? loader.getUnnamedModule()
|
||||
: BootLoader.getUnnamedModule();
|
||||
}
|
||||
|
||||
if (packagePrivateTypes.size() > 0) {
|
||||
// all package-private types must be in the same runtime package
|
||||
// i.e. same package name and same module (named or unnamed)
|
||||
//
|
||||
// Configuration will fail if M1 and in M2 defined by the same loader
|
||||
// and both have the same package p (so no need to check class loader)
|
||||
if (packagePrivateTypes.size() > 1 &&
|
||||
(packagePrivateTypes.keySet().stream() // more than one package
|
||||
.map(Class::getPackageName).distinct().count() > 1 ||
|
||||
packagePrivateTypes.values().stream() // or more than one module
|
||||
.distinct().count() > 1)) {
|
||||
throw new IllegalArgumentException(
|
||||
"non-public interfaces from different packages");
|
||||
}
|
||||
|
||||
// all package-private types are in the same module (named or unnamed)
|
||||
Module target = null;
|
||||
for (Module m : packagePrivateTypes.values()) {
|
||||
Module targetModule = null;
|
||||
String targetPackageName = null;
|
||||
for (Map.Entry<Class<?>, Module> e : packagePrivateTypes.entrySet()) {
|
||||
Class<?> intf = e.getKey();
|
||||
Module m = e.getValue();
|
||||
if ((targetModule != null && targetModule != m) ||
|
||||
(targetPackageName != null && targetPackageName != intf.getPackageName())) {
|
||||
throw new IllegalArgumentException(
|
||||
"cannot have non-public interfaces in different packages");
|
||||
}
|
||||
if (getLoader(m) != loader) {
|
||||
// the specified loader is not the same class loader
|
||||
// of the non-public interface
|
||||
throw new IllegalArgumentException(
|
||||
"non-public interface is not defined by the given loader");
|
||||
}
|
||||
target = m;
|
||||
|
||||
targetModule = m;
|
||||
targetPackageName = e.getKey().getPackageName();
|
||||
}
|
||||
|
||||
// validate if the target module can access all other interfaces
|
||||
for (Class<?> intf : interfaces) {
|
||||
Module m = intf.getModule();
|
||||
if (m == target) continue;
|
||||
if (m == targetModule) continue;
|
||||
|
||||
if (!target.canRead(m) || !m.isExported(intf.getPackageName(), target)) {
|
||||
throw new IllegalArgumentException(target + " can't access " + intf.getName());
|
||||
if (!targetModule.canRead(m) || !m.isExported(intf.getPackageName(), targetModule)) {
|
||||
throw new IllegalArgumentException(targetModule + " can't access " + intf.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// opens the package of the non-public proxy class for java.base to access
|
||||
if (targetModule.isNamed()) {
|
||||
Modules.addOpens(targetModule, targetPackageName, Proxy.class.getModule());
|
||||
}
|
||||
// return the module of the package-private interface
|
||||
return target;
|
||||
return targetModule;
|
||||
}
|
||||
|
||||
// All proxy interfaces are public and at least one in a non-exported
|
||||
// package. So maps to a dynamic proxy module and add reads edge
|
||||
// and qualified exports, if necessary
|
||||
Module target = getDynamicModule(loader);
|
||||
// All proxy interfaces are public. So maps to a dynamic proxy module
|
||||
// and add reads edge and qualified exports, if necessary
|
||||
Module targetModule = getDynamicModule(loader);
|
||||
|
||||
// set up proxy class access to proxy interfaces and types
|
||||
// referenced in the method signature
|
||||
Set<Class<?>> types = new HashSet<>(interfaces);
|
||||
types.addAll(refTypes);
|
||||
for (Class<?> c : types) {
|
||||
ensureAccess(target, c);
|
||||
ensureAccess(targetModule, c);
|
||||
}
|
||||
return target;
|
||||
return targetModule;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -875,8 +890,9 @@ public class Proxy implements java.io.Serializable {
|
|||
private static final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
/*
|
||||
* Define a dynamic module for the generated proxy classes in
|
||||
* a non-exported package named com.sun.proxy.$MODULE.
|
||||
* Define a dynamic module with a packge named $MODULE which
|
||||
* is unconditionally exported and another package named
|
||||
* com.sun.proxy.$MODULE which is encapsulated.
|
||||
*
|
||||
* Each class loader will have one dynamic module.
|
||||
*/
|
||||
|
@ -886,13 +902,16 @@ public class Proxy implements java.io.Serializable {
|
|||
String mn = "jdk.proxy" + counter.incrementAndGet();
|
||||
String pn = PROXY_PACKAGE_PREFIX + "." + mn;
|
||||
ModuleDescriptor descriptor =
|
||||
ModuleDescriptor.newModule(mn, Set.of(SYNTHETIC))
|
||||
.packages(Set.of(pn))
|
||||
.build();
|
||||
ModuleDescriptor.newModule(mn, Set.of(SYNTHETIC))
|
||||
.packages(Set.of(pn, mn))
|
||||
.exports(mn)
|
||||
.build();
|
||||
Module m = Modules.defineModule(ld, descriptor, null);
|
||||
Modules.addReads(m, Proxy.class.getModule());
|
||||
// java.base to create proxy instance
|
||||
Modules.addExports(m, pn, Object.class.getModule());
|
||||
Modules.addExports(m, mn);
|
||||
// java.base to create proxy instance and access its Lookup instance
|
||||
Modules.addOpens(m, pn, Proxy.class.getModule());
|
||||
Modules.addOpens(m, mn, Proxy.class.getModule());
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
@ -1120,4 +1139,205 @@ public class Proxy implements java.io.Serializable {
|
|||
}
|
||||
|
||||
private static final String PROXY_PACKAGE_PREFIX = ReflectUtil.PROXY_PACKAGE;
|
||||
|
||||
/**
|
||||
* A cache of Method -> MethodHandle for default methods.
|
||||
*/
|
||||
private static final ClassValue<ConcurrentHashMap<Method, MethodHandle>>
|
||||
DEFAULT_METHODS_MAP = new ClassValue<>() {
|
||||
@Override
|
||||
protected ConcurrentHashMap<Method, MethodHandle> computeValue(Class<?> type) {
|
||||
return new ConcurrentHashMap<>(4);
|
||||
}
|
||||
};
|
||||
|
||||
private static ConcurrentHashMap<Method, MethodHandle> defaultMethodMap(Class<?> proxyClass) {
|
||||
assert isProxyClass(proxyClass);
|
||||
return DEFAULT_METHODS_MAP.get(proxyClass);
|
||||
}
|
||||
|
||||
static final Object[] EMPTY_ARGS = new Object[0];
|
||||
|
||||
static MethodHandle defaultMethodHandle(Class<? extends Proxy> proxyClass, Method method) {
|
||||
// lookup the cached method handle
|
||||
ConcurrentHashMap<Method, MethodHandle> methods = defaultMethodMap(proxyClass);
|
||||
MethodHandle superMH = methods.get(method);
|
||||
if (superMH == null) {
|
||||
MethodType type = methodType(method.getReturnType(), method.getParameterTypes());
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
Class<?> proxyInterface = findProxyInterfaceOrElseThrow(proxyClass, method);
|
||||
MethodHandle dmh;
|
||||
try {
|
||||
dmh = proxyClassLookup(lookup, proxyClass)
|
||||
.findSpecial(proxyInterface, method.getName(), type, proxyClass)
|
||||
.withVarargs(false);
|
||||
} catch (IllegalAccessException | NoSuchMethodException e) {
|
||||
// should not reach here
|
||||
throw new InternalError(e);
|
||||
}
|
||||
// this check can be turned into assertion as it is guaranteed to succeed by the virtue of
|
||||
// looking up a default (instance) method declared or inherited by proxyInterface
|
||||
// while proxyClass implements (is a subtype of) proxyInterface ...
|
||||
assert ((BooleanSupplier) () -> {
|
||||
try {
|
||||
// make sure that the method type matches
|
||||
dmh.asType(type.insertParameterTypes(0, proxyClass));
|
||||
return true;
|
||||
} catch (WrongMethodTypeException e) {
|
||||
return false;
|
||||
}
|
||||
}).getAsBoolean() : "Wrong method type";
|
||||
// change return type to Object
|
||||
MethodHandle mh = dmh.asType(dmh.type().changeReturnType(Object.class));
|
||||
// wrap any exception thrown with InvocationTargetException
|
||||
mh = MethodHandles.catchException(mh, Throwable.class, InvocationException.wrapMH());
|
||||
// spread array of arguments among parameters (skipping 1st parameter - target)
|
||||
mh = mh.asSpreader(1, Object[].class, type.parameterCount());
|
||||
// change target type to Object
|
||||
mh = mh.asType(MethodType.methodType(Object.class, Object.class, Object[].class));
|
||||
|
||||
// push MH into cache
|
||||
MethodHandle cached = methods.putIfAbsent(method, mh);
|
||||
if (cached != null) {
|
||||
superMH = cached;
|
||||
} else {
|
||||
superMH = mh;
|
||||
}
|
||||
}
|
||||
return superMH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first proxy interface that declares the given method
|
||||
* directly or indirectly.
|
||||
*
|
||||
* @throws IllegalArgumentException if not found
|
||||
*/
|
||||
private static Class<?> findProxyInterfaceOrElseThrow(Class<?> proxyClass, Method method) {
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
if (!declaringClass.isInterface()) {
|
||||
throw new IllegalArgumentException("\"" + method +
|
||||
"\" is not a method declared in the proxy class");
|
||||
}
|
||||
|
||||
List<Class<?>> proxyInterfaces = Arrays.asList(proxyClass.getInterfaces());
|
||||
// the method's declaring class is a proxy interface
|
||||
if (proxyInterfaces.contains(declaringClass))
|
||||
return declaringClass;
|
||||
|
||||
// find the first proxy interface that inherits the default method
|
||||
// i.e. the declaring class of the default method is a superinterface
|
||||
// of the proxy interface
|
||||
Deque<Class<?>> deque = new ArrayDeque<>();
|
||||
Set<Class<?>> visited = new HashSet<>();
|
||||
boolean indirectMethodRef = false;
|
||||
for (Class<?> proxyIntf : proxyInterfaces) {
|
||||
assert proxyIntf != declaringClass;
|
||||
visited.add(proxyIntf);
|
||||
deque.add(proxyIntf);
|
||||
|
||||
// for each proxy interface, traverse its subinterfaces with
|
||||
// breadth-first search to find a subinterface declaring the
|
||||
// default method
|
||||
Class<?> c;
|
||||
while ((c = deque.poll()) != null) {
|
||||
if (c == declaringClass) {
|
||||
try {
|
||||
// check if this method is the resolved method if referenced from
|
||||
// this proxy interface (i.e. this method is not implemented
|
||||
// by any other superinterface)
|
||||
Method m = proxyIntf.getMethod(method.getName(), method.getParameterTypes());
|
||||
if (m.getDeclaringClass() == declaringClass) {
|
||||
return proxyIntf;
|
||||
}
|
||||
indirectMethodRef = true;
|
||||
} catch (NoSuchMethodException e) {}
|
||||
|
||||
// skip traversing its superinterfaces
|
||||
// another proxy interface may extend it and so
|
||||
// the method's declaring class is left unvisited.
|
||||
continue;
|
||||
}
|
||||
// visit all superinteraces of one proxy interface to find if
|
||||
// this proxy interface inherits the method directly or indirectly
|
||||
visited.add(c);
|
||||
for (Class<?> superIntf : c.getInterfaces()) {
|
||||
if (!visited.contains(superIntf) && !deque.contains(superIntf)) {
|
||||
if (superIntf == declaringClass) {
|
||||
// fast-path as the matching subinterface is found
|
||||
deque.addFirst(superIntf);
|
||||
} else {
|
||||
deque.add(superIntf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("\"" + method + (indirectMethodRef
|
||||
? "\" is overridden directly or indirectly by the proxy interfaces"
|
||||
: "\" is not a method declared in the proxy class"));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method invokes the proxy's proxyClassLookup method to get a
|
||||
* Lookup on the proxy class.
|
||||
*
|
||||
* @return a lookup for proxy class of this proxy instance
|
||||
*/
|
||||
private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup caller, Class<?> proxyClass) {
|
||||
return AccessController.doPrivileged(new PrivilegedAction<>() {
|
||||
@Override
|
||||
public MethodHandles.Lookup run() {
|
||||
try {
|
||||
Method m = proxyClass.getDeclaredMethod("proxyClassLookup", MethodHandles.Lookup.class);
|
||||
m.setAccessible(true);
|
||||
return (MethodHandles.Lookup) m.invoke(null, caller);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal exception type to wrap the exception thrown by the default method
|
||||
* so that it can distinguish CCE and NPE thrown due to the arguments
|
||||
* incompatible with the method signature.
|
||||
*/
|
||||
static class InvocationException extends ReflectiveOperationException {
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
InvocationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps given cause with InvocationException and throws it.
|
||||
*/
|
||||
static Object wrap(Throwable cause) throws InvocationException {
|
||||
throw new InvocationException(cause);
|
||||
}
|
||||
|
||||
@Stable
|
||||
static MethodHandle wrapMethodHandle;
|
||||
|
||||
static MethodHandle wrapMH() {
|
||||
MethodHandle mh = wrapMethodHandle;
|
||||
if (mh == null) {
|
||||
try {
|
||||
wrapMethodHandle = mh = MethodHandles.lookup().findStatic(
|
||||
InvocationException.class,
|
||||
"wrap",
|
||||
MethodType.methodType(Object.class, Throwable.class)
|
||||
);
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
return mh;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import jdk.internal.org.objectweb.asm.ClassWriter;
|
|||
import jdk.internal.org.objectweb.asm.Label;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||
import jdk.internal.org.objectweb.asm.Type;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -59,9 +60,13 @@ final class ProxyGenerator extends ClassWriter {
|
|||
private static final String JL_OBJECT = "java/lang/Object";
|
||||
private static final String JL_THROWABLE = "java/lang/Throwable";
|
||||
private static final String JL_CLASS_NOT_FOUND_EX = "java/lang/ClassNotFoundException";
|
||||
private static final String JL_ILLEGAL_ACCESS_EX = "java/lang/IllegalAccessException";
|
||||
|
||||
private static final String JL_NO_CLASS_DEF_FOUND_ERROR = "java/lang/NoClassDefFoundError";
|
||||
private static final String JL_NO_SUCH_METHOD_EX = "java/lang/NoSuchMethodException";
|
||||
private static final String JL_NO_SUCH_METHOD_ERROR = "java/lang/NoSuchMethodError";
|
||||
private static final String JLI_LOOKUP = "java/lang/invoke/MethodHandles$Lookup";
|
||||
private static final String JLI_METHODHANDLES = "java/lang/invoke/MethodHandles";
|
||||
|
||||
private static final String JLR_INVOCATION_HANDLER = "java/lang/reflect/InvocationHandler";
|
||||
private static final String JLR_PROXY = "java/lang/reflect/Proxy";
|
||||
|
@ -75,6 +80,7 @@ final class ProxyGenerator extends ClassWriter {
|
|||
|
||||
private static final String NAME_CTOR = "<init>";
|
||||
private static final String NAME_CLINIT = "<clinit>";
|
||||
private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup";
|
||||
|
||||
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
|
||||
|
||||
|
@ -484,7 +490,7 @@ final class ProxyGenerator extends ClassWriter {
|
|||
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
|
||||
for (ProxyMethod pm : sigmethods) {
|
||||
// add static field for the Method object
|
||||
visitField(Modifier.PRIVATE | Modifier.STATIC, pm.methodFieldName,
|
||||
visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, pm.methodFieldName,
|
||||
LJLR_METHOD, null, null);
|
||||
|
||||
// Generate code for proxy method
|
||||
|
@ -493,7 +499,7 @@ final class ProxyGenerator extends ClassWriter {
|
|||
}
|
||||
|
||||
generateStaticInitializer();
|
||||
|
||||
generateLookupAccessor();
|
||||
return toByteArray();
|
||||
}
|
||||
|
||||
|
@ -625,6 +631,46 @@ final class ProxyGenerator extends ClassWriter {
|
|||
mv.visitEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the static lookup accessor method that returns the Lookup
|
||||
* on this proxy class if the caller's lookup class is java.lang.reflect.Proxy;
|
||||
* otherwise, IllegalAccessException is thrown
|
||||
*/
|
||||
private void generateLookupAccessor() {
|
||||
MethodVisitor mv = visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_LOOKUP_ACCESSOR,
|
||||
"(Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;", null,
|
||||
new String[] { JL_ILLEGAL_ACCESS_EX });
|
||||
mv.visitCode();
|
||||
Label L_illegalAccess = new Label();
|
||||
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "lookupClass",
|
||||
"()Ljava/lang/Class;", false);
|
||||
mv.visitLdcInsn(Type.getType(Proxy.class));
|
||||
mv.visitJumpInsn(IF_ACMPNE, L_illegalAccess);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "hasFullPrivilegeAccess",
|
||||
"()Z", false);
|
||||
mv.visitJumpInsn(IFEQ, L_illegalAccess);
|
||||
mv.visitMethodInsn(INVOKESTATIC, JLI_METHODHANDLES, "lookup",
|
||||
"()Ljava/lang/invoke/MethodHandles$Lookup;", false);
|
||||
mv.visitInsn(ARETURN);
|
||||
|
||||
mv.visitLabel(L_illegalAccess);
|
||||
mv.visitTypeInsn(Opcodes.NEW, JL_ILLEGAL_ACCESS_EX);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "toString",
|
||||
"()Ljava/lang/String;", false);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, JL_ILLEGAL_ACCESS_EX,
|
||||
"<init>", "(Ljava/lang/String;)V", false);
|
||||
mv.visitInsn(ATHROW);
|
||||
|
||||
// Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored
|
||||
mv.visitMaxs(-1, -1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* A ProxyMethod object represents a proxy method in the proxy class
|
||||
* being generated: a method whose implementation will encode and
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue