8159746: (proxy) Support for default methods

Co-authored-by: Peter Levart <plevart@openjdk.org>
Reviewed-by: darcy, alanb, plevart
This commit is contained in:
Mandy Chung 2020-12-01 17:23:07 +00:00
parent 1433bafb33
commit 56b15fbbcc
29 changed files with 1380 additions and 148 deletions

View file

@ -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);
}

View file

@ -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();
}
}
}

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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