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

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

@ -209,6 +209,11 @@ public interface JavaLangAccess {
*/
void addReadsAllUnnamed(Module m);
/**
* Updates module m1 to export a package unconditionally.
*/
void addExports(Module m1, String pkg);
/**
* Updates module m1 to export a package to module m2. The export does
* not result in a strong reference to m2 (m2 can be GC'ed).

@ -105,6 +105,13 @@ public class Modules {
JLA.addExports(m1, pn, m2);
}
/**
* Updates module m to export a package unconditionally.
*/
public static void addExports(Module m, String pn) {
JLA.addExports(m, pn);
}
/**
* Updates module m to export a package to all unnamed modules.
*/

@ -263,8 +263,7 @@ public final class ReflectUtil {
if (!Proxy.isProxyClass(cls)) {
return false;
}
String pkg = cls.getPackageName();
return pkg == null || !pkg.startsWith(PROXY_PACKAGE);
return !Modifier.isPublic(cls.getModifiers());
}
/**

@ -0,0 +1,386 @@
/*
* Copyright (c) 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
/**
* @test
* @bug 8159746
* @run testng DefaultMethods
* @summary Basic tests for Proxy::invokeSuper default method
*/
public class DefaultMethods {
public interface I1 {
default int m() {
return 10;
}
}
public interface I2 {
default int m() {
return 20;
}
private void privateMethod() {
throw new Error("should not reach here");
}
}
// I3::m inherits from I2:m
public interface I3 extends I2 {
default int m3(String... s) {
return Arrays.stream(s).mapToInt(String::length).sum();
}
}
public interface I4 extends I1, I2 {
default int m() {
return 40;
}
default int mix(int a, String b) {
return 0;
}
}
public interface I12 extends I1, I2 {
@Override
int m();
default int sum(int a, int b) {
return a + b;
}
default Object[] concat(Object first, Object... rest) {
Object[] result = new Object[1 + rest.length];
result[0] = first;
System.arraycopy(rest, 0, result, 1, rest.length);
return result;
}
}
public interface IX {
default void doThrow(Throwable exception) throws Throwable {
throw exception;
}
}
private static Method findDefaultMethod(Class<?> refc, Method m) {
try {
assertTrue(refc.isInterface());
Method method = refc.getMethod(m.getName(), m.getParameterTypes());
assertTrue(method.isDefault());
return method;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@Test
public void test() {
ClassLoader loader = DefaultMethods.class.getClassLoader();
Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { I1.class, I2.class},
(o, method, params) -> {
return InvocationHandler.invokeDefault(o, findDefaultMethod(I2.class, method), params);
});
I1 i1 = (I1) proxy;
assertEquals(i1.m(), 20);
}
// a default method is declared in one of the proxy interfaces
@DataProvider(name = "defaultMethods")
private Object[][] defaultMethods() {
return new Object[][]{
new Object[]{new Class<?>[]{I1.class, I2.class}, true, 10},
new Object[]{new Class<?>[]{I1.class, I3.class}, true, 10},
new Object[]{new Class<?>[]{I1.class, I12.class}, true, 10},
new Object[]{new Class<?>[]{I2.class, I12.class}, true, 20},
new Object[]{new Class<?>[]{I4.class}, true, 40},
new Object[]{new Class<?>[]{I4.class, I3.class}, true, 40},
new Object[]{new Class<?>[]{I12.class}, false, -1},
new Object[]{new Class<?>[]{I12.class, I1.class, I2.class}, false, -1}
};
}
@Test(dataProvider = "defaultMethods")
public void testDefaultMethod(Class<?>[] intfs, boolean isDefault, int expected) throws Throwable {
InvocationHandler ih = (proxy, method, params) -> {
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
switch (method.getName()) {
case "m":
assertTrue(method.isDefault() == isDefault);
assertTrue(Arrays.stream(proxy.getClass().getInterfaces())
.anyMatch(intf -> method.getDeclaringClass() == intf),
Arrays.toString(proxy.getClass().getInterfaces()));
if (method.isDefault()) {
return InvocationHandler.invokeDefault(proxy, method, params);
} else {
return -1;
}
default:
throw new UnsupportedOperationException(method.toString());
}
};
Object proxy = Proxy.newProxyInstance(DefaultMethods.class.getClassLoader(), intfs, ih);
Method m = proxy.getClass().getMethod("m");
int result = (int)m.invoke(proxy);
assertEquals(result, expected);
}
// a default method may be declared in a proxy interface or
// inherited from a superinterface of a proxy interface
@DataProvider(name = "supers")
private Object[][] supers() {
return new Object[][]{
// invoke "m" implemented in the first proxy interface
// same as the method passed to InvocationHandler::invoke
new Object[]{new Class<?>[]{I1.class}, I1.class, 10},
new Object[]{new Class<?>[]{I2.class}, I2.class, 20},
new Object[]{new Class<?>[]{I1.class, I2.class}, I1.class, 10},
// "m" is implemented in I2, an indirect superinterface of I3
new Object[]{new Class<?>[]{I3.class}, I3.class, 20},
// "m" is implemented in I1, I2 and overridden in I4
new Object[]{new Class<?>[]{I4.class}, I4.class, 40},
// invoke "m" implemented in the second proxy interface
// different from the method passed to InvocationHandler::invoke
new Object[]{new Class<?>[]{I1.class, I2.class}, I2.class, 20},
new Object[]{new Class<?>[]{I1.class, I3.class}, I3.class, 20},
// I2::m is implemented in more than one proxy interface directly or indirectly
// I3::m resolves to I2::m (indirect superinterface)
// I2 is the superinterface of I4 and I4 overrides m
// the proxy class can invoke I4::m and I2::m
new Object[]{new Class<?>[]{I3.class, I4.class}, I3.class, 20},
new Object[]{new Class<?>[]{I3.class, I4.class}, I4.class, 40},
new Object[]{new Class<?>[]{I4.class, I3.class}, I3.class, 20},
new Object[]{new Class<?>[]{I4.class, I3.class}, I4.class, 40}
};
}
@Test(dataProvider = "supers")
public void testSuper(Class<?>[] intfs, Class<?> proxyInterface, int expected) throws Throwable {
final InvocationHandler ih = (proxy, method, params) -> {
switch (method.getName()) {
case "m":
assertTrue(method.isDefault());
return InvocationHandler.invokeDefault(proxy, findDefaultMethod(proxyInterface, method), params);
default:
throw new UnsupportedOperationException(method.toString());
}
};
ClassLoader loader = proxyInterface.getClassLoader();
Object proxy = Proxy.newProxyInstance(loader, intfs, ih);
if (proxyInterface == I1.class) {
I1 i1 = (I1) proxy;
assertEquals(i1.m(), expected);
} else if (proxyInterface == I2.class) {
I2 i2 = (I2) proxy;
assertEquals(i2.m(), expected);
} else if (proxyInterface == I3.class) {
I3 i3 = (I3) proxy;
assertEquals(i3.m(), expected);
} else if (proxyInterface == I4.class) {
I4 i4 = (I4) proxy;
assertEquals(i4.m(), expected);
} else {
throw new UnsupportedOperationException(proxyInterface.toString());
}
// invoke via InvocationHandler.invokeDefaultMethod directly
assertEquals(InvocationHandler.invokeDefault(proxy, proxyInterface.getMethod("m")), expected);
}
// invoke I12 default methods with parameters and var args
@Test
public void testI12() throws Throwable {
final InvocationHandler ih = (proxy, method, params) -> {
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
switch (method.getName()) {
case "sum":
case "concat":
assertTrue(method.isDefault());
return InvocationHandler.invokeDefault(proxy, method, params);
default:
throw new UnsupportedOperationException(method.toString());
}
};
ClassLoader loader = DefaultMethods.class.getClassLoader();
I12 i12 = (I12) Proxy.newProxyInstance(loader, new Class<?>[] { I12.class }, ih);
assertEquals(i12.sum(1, 2), 3);
assertEquals(i12.concat(1, 2, 3, 4), new Object[]{1, 2, 3, 4});
Method m = I12.class.getMethod("concat", Object.class, Object[].class);
assertTrue(m.isDefault());
assertEquals(InvocationHandler.invokeDefault(i12, m, 100, new Object[] {"foo", true, "bar"}),
new Object[] {100, "foo", true, "bar"});
}
// test a no-arg default method with and without arguments passed in the invocation
@Test
public void testEmptyArgument() throws Throwable {
ClassLoader loader = DefaultMethods.class.getClassLoader();
Object proxy = Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
Method m1 = I4.class.getMethod("m");
assertTrue(m1.getDeclaringClass() == I4.class);
assertTrue(m1.isDefault());
InvocationHandler.invokeDefault(proxy, m1);
InvocationHandler.invokeDefault(proxy, m1, new Object[0]);
Method m2 = I4.class.getMethod("mix", int.class, String.class);
assertTrue(m1.getDeclaringClass() == I4.class);
assertTrue(m1.isDefault());
InvocationHandler.invokeDefault(proxy, m2, Integer.valueOf(100), "foo");
}
@Test
public void testVarArgs() throws Throwable {
ClassLoader loader = DefaultMethods.class.getClassLoader();
I3 proxy = (I3)Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER);
Method m = I3.class.getMethod("m3", String[].class);
assertTrue(m.isVarArgs() && m.isDefault());
assertEquals(proxy.m3("a", "b", "cde"), 5);
assertEquals(InvocationHandler.invokeDefault(proxy, m, (Object)new String[] { "a", "bc" }), 3);
}
/*
* Invoke I12::m which is an abstract method
*/
@Test(expectedExceptions = {IllegalArgumentException.class})
public void invokeAbstractMethod() throws Exception {
ClassLoader loader = DefaultMethods.class.getClassLoader();
I12 proxy = (I12) Proxy.newProxyInstance(loader, new Class<?>[]{I12.class}, HANDLER);
Method method = I12.class.getMethod("m");
assertTrue(method.getDeclaringClass() == I12.class);
assertFalse(method.isDefault());
proxy.m();
}
/*
* Invoke a non proxy (default) method with parameters
*/
@Test(expectedExceptions = {IllegalArgumentException.class})
public void invokeNonProxyMethod() throws Throwable {
ClassLoader loader = DefaultMethods.class.getClassLoader();
I3 proxy = (I3) Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER);
Method m = I4.class.getMethod("mix", int.class, String.class);
assertTrue(m.isDefault());
InvocationHandler.invokeDefault(proxy, m);
}
// negative cases
@DataProvider(name = "negativeCases")
private Object[][] negativeCases() {
return new Object[][]{
// I4::m overrides I1::m and I2::m
new Object[] { new Class<?>[]{I4.class}, I1.class, "m" },
new Object[] { new Class<?>[]{I4.class}, I2.class, "m" },
// I12::m is not a default method
new Object[] { new Class<?>[]{I12.class}, I12.class, "m" },
// non-proxy default method
new Object[] { new Class<?>[]{I3.class}, I1.class, "m" },
// not a default method and not a proxy interface
new Object[] { new Class<?>[]{I12.class}, DefaultMethods.class, "test" },
new Object[] { new Class<?>[]{I12.class}, Runnable.class, "run" },
// I2::privateMethod is a private method
new Object[] { new Class<?>[]{I3.class}, I2.class, "privateMethod" }
};
}
@Test(dataProvider = "negativeCases", expectedExceptions = {IllegalArgumentException.class})
public void testNegativeCase(Class<?>[] interfaces, Class<?> defc, String name)
throws Throwable {
ClassLoader loader = DefaultMethods.class.getClassLoader();
Object proxy = Proxy.newProxyInstance(loader, interfaces, HANDLER);
try {
Method method = defc.getDeclaredMethod(name);
InvocationHandler.invokeDefault(proxy, method);
} catch (Throwable e) {
System.out.format("%s method %s::%s exception thrown: %s%n",
Arrays.toString(interfaces), defc.getName(), name, e.getMessage());
throw e;
}
}
@DataProvider(name = "illegalArguments")
private Object[][] illegalArguments() {
return new Object[][] {
new Object[] {},
new Object[] { 100 },
new Object[] { 100, "foo", 100 },
new Object[] { 100L, "foo" },
new Object[] { "foo", 100},
new Object[] { null, "foo" }
};
}
@Test(dataProvider = "illegalArguments", expectedExceptions = {IllegalArgumentException.class})
public void testIllegalArgument(Object... args) throws Throwable {
ClassLoader loader = DefaultMethods.class.getClassLoader();
I4 proxy = (I4)Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
Method m = I4.class.getMethod("mix", int.class, String.class);
assertTrue(m.isDefault());
if (args.length == 0) {
// substitute empty args with null since @DataProvider doesn't allow null array
args = null;
}
InvocationHandler.invokeDefault(proxy, m, args);
}
@DataProvider(name = "throwables")
private Object[][] throwables() {
return new Object[][] {
new Object[] { new IOException() },
new Object[] { new IllegalArgumentException() },
new Object[] { new ClassCastException() },
new Object[] { new NullPointerException() },
new Object[] { new AssertionError() },
new Object[] { new Throwable() }
};
}
@Test(dataProvider = "throwables")
public void testInvocationException(Throwable exception) throws Throwable {
ClassLoader loader = DefaultMethods.class.getClassLoader();
IX proxy = (IX)Proxy.newProxyInstance(loader, new Class<?>[]{IX.class}, HANDLER);
Method m = IX.class.getMethod("doThrow", Throwable.class);
try {
InvocationHandler.invokeDefault(proxy, m, exception);
} catch (Throwable e) {
assertEquals(e, exception);
}
}
private static final InvocationHandler HANDLER = (proxy, method, params) -> {
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
return InvocationHandler.invokeDefault(proxy, method, params);
};
}

@ -87,7 +87,7 @@ public class ProxyClassAccessTest {
* Test unnamed module has no access to other proxy interface
*/
@Test
public void testNoReadAccess() throws Exception {
public void testNoReadAccess() throws Throwable {
ModuleFinder finder = ModuleFinder.of(MODS_DIR);
ModuleLayer bootLayer = ModuleLayer.boot();
Configuration cf = bootLayer
@ -104,7 +104,7 @@ public class ProxyClassAccessTest {
checkIAE(loader, interfaces);
}
private void checkIAE(ClassLoader loader, Class<?>[] interfaces) {
private void checkIAE(ClassLoader loader, Class<?>[] interfaces) throws Throwable {
try {
Proxy.getProxyClass(loader, interfaces);
throw new RuntimeException("Expected IllegalArgumentException thrown");

@ -99,8 +99,9 @@ public class ProxyLayerTest {
Class<?> proxyClass = o.getClass();
Package pkg = proxyClass.getPackage();
assertFalse(proxyClass.getModule().isNamed());
assertFalse(pkg.isSealed());
assertTrue(proxyClass.getModule().isNamed());
assertTrue(pkg.isSealed());
assertTrue(proxyClass.getModule().isExported(pkg.getName()));
assertEquals(proxyClass.getModule().getLayer(), null);
}
@ -132,6 +133,7 @@ public class ProxyLayerTest {
Package pkg = proxyClass.getPackage();
assertTrue(proxyClass.getModule().isNamed());
assertTrue(pkg.isSealed());
assertFalse(proxyClass.getModule().isExported(pkg.getName()));
assertEquals(proxyClass.getModule().getLayer(), null);
}

@ -35,7 +35,7 @@ public class ProxyModuleMapping {
public static void main(String... args) throws Exception {
ClassLoader ld = ProxyModuleMapping.class.getClassLoader();
Module unnamed = ld.getUnnamedModule();
new ProxyModuleMapping(unnamed, Runnable.class).test();
new ProxyModuleMapping(Runnable.class).test();
// unnamed module gets access to sun.invoke package (e.g. via --add-exports)
new ProxyModuleMapping(sun.invoke.WrapperInstance.class).test();
@ -79,11 +79,13 @@ public class ProxyModuleMapping {
try {
Constructor<?> cons = c.getConstructor(InvocationHandler.class);
cons.newInstance(ih);
if (module.isNamed()) {
// the exported package name is same as the module name
if (!c.getPackageName().equals(module.getName())) {
throw new RuntimeException("expected IAE not thrown");
}
} catch (IllegalAccessException e) {
if (!module.isNamed()) {
// non-exported package from the dynamic module
if (c.getPackageName().equals(module.getName())) {
throw e;
}
}

@ -81,4 +81,20 @@ public class ProxyTest {
assertTrue(exitValue == 0);
}
/**
* Tests invocation of default methods in exported and non-exported types
* in a named module
*/
@Test
public void runDefaultMethodsTest() throws Exception {
int exitValue = executeTestJava("-cp", CPATH_DIR.toString(),
"--module-path", MODS_DIR.toString(),
"-m", "test/jdk.test.DefaultMethods")
.outputTo(System.out)
.errorTo(System.out)
.getExitValue();
assertTrue(exitValue == 0);
}
}

@ -0,0 +1,129 @@
/*
* Copyright (c) 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
/*
* @test
* @bug 8159746
* @summary Test invoking a default method in a non-public proxy interface
* @build p.Foo p.Bar p.ProxyMaker
* @run testng DefaultMethodProxy
*/
public class DefaultMethodProxy {
public interface I {
default String m() { return "I"; }
}
@Test
public static void publicInterface() throws ReflectiveOperationException {
// create a proxy instance of a public proxy interface should succeed
Proxy proxy = (Proxy)Proxy.newProxyInstance(DefaultMethodProxy.class.getClassLoader(),
new Class<?>[] { I.class }, IH);
testDefaultMethod(proxy, "I");
// can get the invocation handler
assertTrue(Proxy.getInvocationHandler(proxy) == IH);
}
@DataProvider(name = "nonPublicIntfs")
private static Object[][] nonPublicIntfs() throws ClassNotFoundException {
Class<?> fooClass = Class.forName("p.Foo");
Class<?> barClass = Class.forName("p.Bar");
return new Object[][]{
new Object[]{new Class<?>[]{ fooClass }, "foo"},
new Object[]{new Class<?>[]{ barClass, fooClass }, "bar"},
new Object[]{new Class<?>[]{ barClass }, "bar"},
};
}
@Test(dataProvider = "nonPublicIntfs")
public static void hasPackageAccess(Class<?>[] intfs, String expected) throws ReflectiveOperationException {
Proxy proxy = (Proxy)Proxy.newProxyInstance(DefaultMethodProxy.class.getClassLoader(), intfs, IH);
testDefaultMethod(proxy, expected);
// proxy instance is created successfully even invocation handler has no access
Proxy.newProxyInstance(DefaultMethodProxy.class.getClassLoader(), intfs, IH_NO_ACCESS);
}
// IAE thrown at invocation time
@Test(dataProvider = "nonPublicIntfs", expectedExceptions = {IllegalAccessException.class})
public static void noPackageAccess(Class<?>[] intfs, String ignored) throws Throwable {
Proxy proxy = (Proxy)Proxy.newProxyInstance(DefaultMethodProxy.class.getClassLoader(), intfs, IH_NO_ACCESS);
try {
testDefaultMethod(proxy, "dummy");
} catch (InvocationTargetException e) {
// unwrap the exception
if (e.getCause() instanceof UndeclaredThrowableException) {
Throwable cause = e.getCause();
throw cause.getCause();
}
throw e;
}
}
/*
* Verify if a default method "m" can be invoked successfully
*/
static void testDefaultMethod(Proxy proxy, String expected) throws ReflectiveOperationException {
Method m = proxy.getClass().getDeclaredMethod("m");
m.setAccessible(true);
String name = (String) m.invoke(proxy);
if (!expected.equals(name)) {
throw new RuntimeException("return value: " + name + " expected: " + expected);
}
}
// invocation handler with access to the non-public interface in package p
private static final InvocationHandler IH = (proxy, method, params) -> {
System.out.format("Proxy for %s: invoking %s%n",
Arrays.stream(proxy.getClass().getInterfaces())
.map(Class::getName)
.collect(Collectors.joining(", ")), method.getName());
if (method.isDefault()) {
return p.ProxyMaker.invoke(proxy, method, params);
}
throw new UnsupportedOperationException(method.toString());
};
// invocation handler with no access to the non-public interface in package p
// expect IllegalAccessException thrown
private static final InvocationHandler IH_NO_ACCESS = (proxy, method, params) -> {
System.out.format("Proxy for %s: invoking %s%n",
Arrays.stream(proxy.getClass().getInterfaces())
.map(Class::getName)
.collect(Collectors.joining(", ")), method.getName());
if (method.isDefault()) {
InvocationHandler.invokeDefault(proxy, method, params);
}
throw new UnsupportedOperationException(method.toString());
};
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 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

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 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
@ -23,4 +23,5 @@
package p;
interface Bar {
default String m() { return "bar"; }
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 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
@ -23,4 +23,5 @@
package p;
interface Foo {
default String m() { return "foo"; }
}

@ -0,0 +1,37 @@
/*
* Copyright (c) 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package p;
import java.lang.reflect.*;
public class ProxyMaker {
public static Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
return InvocationHandler.invokeDefault(proxy, method, args);
}
// get the invocation handler associated with the proxy
public static InvocationHandler getInvocationHandler(Object proxy) {
return Proxy.getInvocationHandler(proxy);
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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,4 +25,6 @@ package p.one;
public interface I {
void run();
default int m() { return 1; }
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -24,4 +24,5 @@
package p.two.internal;
public interface C {
default int m() { return 2; }
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -24,4 +24,7 @@
package p.three.internal;
public interface Q {
default int m() {
throw new UnsupportedOperationException("Q::m is in a non-exported package");
}
}

@ -0,0 +1,97 @@
/*
* Copyright (c) 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/**
* Tests invocation of default methods in exported types and inaccessible types
* in a named module
*/
public class DefaultMethods {
private final static Module TEST_MODULE = DefaultMethods.class.getModule();
private final static InvocationHandler IH = (proxy, method, params) -> {
return InvocationHandler.invokeDefault(proxy, method, params);
};
public static void main(String... args) throws Throwable {
// exported types from m1
testDefaultMethod(new Class<?>[] { p.one.I.class, p.two.A.class}, 1);
// qualified-exported type from m2
testDefaultMethod(new Class<?>[] { p.two.internal.C.class, p.two.A.class }, 2);
// module-private type from test module
testDefaultMethod(new Class<?>[] { jdk.test.internal.R.class }, 10);
// non-public interface in the same runtime package
testDefaultMethod(new Class<?>[] { Class.forName("jdk.test.NP") }, 100);
// inaccessible type - not exported to test module
Class<?> qType = Class.forName("p.three.internal.Q");
inaccessibleDefaultMethod(qType);
// non-public interface in the same runtime package
Class<?> nonPublicType = Class.forName("jdk.test.internal.NP");
inaccessibleDefaultMethod(nonPublicType);
}
static void testDefaultMethod(Class<?>[] intfs, int expected) throws Exception {
Object proxy = Proxy.newProxyInstance(TEST_MODULE.getClassLoader(), intfs, IH);
if (!proxy.getClass().getModule().isNamed()) {
throw new RuntimeException(proxy.getClass() + " expected to be in a named module");
}
Method m = intfs[0].getMethod("m");
int result = (int)m.invoke(proxy);
if (result != expected) {
throw new RuntimeException("return value: " + result + " expected: " + expected);
}
}
static void inaccessibleDefaultMethod(Class<?> intf) throws Throwable {
Object proxy = Proxy.newProxyInstance(TEST_MODULE.getClassLoader(), new Class<?>[] { intf }, IH);
if (!proxy.getClass().getModule().isNamed()) {
throw new RuntimeException(proxy.getClass() + " expected to be in a named module");
}
Method m = intf.getMethod("m");
try {
InvocationHandler.invokeDefault(proxy, m, null);
throw new RuntimeException("IAE not thrown invoking: " + m);
} catch (IllegalAccessException e) {}
if (m.trySetAccessible()) {
try {
m.invoke(proxy);
throw new RuntimeException("IAE not thrown invoking: " + m);
} catch (InvocationTargetException e) {
// IAE wrapped by InvocationHandler::invoke with UndeclaredThrowableException
// then wrapped by Method::invoke with InvocationTargetException
assert e.getCause() instanceof UndeclaredThrowableException;
Throwable cause = e.getCause().getCause();
if (!(cause instanceof IllegalAccessException))
throw e;
}
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -66,22 +66,20 @@ public class Main {
*/
static Data[] proxiesForExportedTypes() {
ClassLoader ld = Main.class.getClassLoader();
Module unnamed = ld.getUnnamedModule();
ClassLoader ld2 = new URLClassLoader(new URL[0], ld);
Module unnamed2 = ld2.getUnnamedModule();
return new Data[] {
new Data(unnamed, ld, Runnable.class),
new Data(unnamed, ld, p.one.I.class),
new Data(unnamed, ld, p.one.I.class, p.two.A.class),
new Data(unnamed, ld, p.one.I.class, unnamedModuleClass),
new Data(unnamed2, ld2, Runnable.class),
new Data(unnamed2, ld2, p.one.I.class),
new Data(unnamed2, ld2, p.one.I.class, p.two.A.class),
new Data(unnamed2, ld2, p.one.I.class, unnamedModuleClass),
new Data(unnamed, m1.getClassLoader(), p.one.I.class),
new Data(unnamed, m2.getClassLoader(), p.two.A.class),
new Data(unnamed, m3.getClassLoader(), p.three.P.class),
new Data(ld, Runnable.class),
new Data(ld, p.one.I.class),
new Data(ld, p.one.I.class, p.two.A.class),
new Data(ld, p.one.I.class, unnamedModuleClass),
new Data(ld2, Runnable.class),
new Data(ld2, p.one.I.class),
new Data(ld2, p.one.I.class, p.two.A.class),
new Data(ld2, p.one.I.class, unnamedModuleClass),
new Data(m1.getClassLoader(), p.one.I.class),
new Data(m2.getClassLoader(), p.two.A.class),
new Data(m3.getClassLoader(), p.three.P.class),
};
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -28,4 +28,8 @@ package jdk.test;
*/
interface NP {
void test();
default int m() {
return 100;
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -96,7 +96,10 @@ public class ProxyTest {
try {
Constructor<?> cons = proxyClass.getConstructor(InvocationHandler.class);
cons.newInstance(handler);
throw new RuntimeException("Expected IllegalAccessException: " + proxyClass);
// the exported package has the same name as the dynamic module
if (!proxyClass.getPackageName().equals(m.getName())) {
throw new RuntimeException("Expected IllegalAccessException: " + proxyClass);
}
} catch (IllegalAccessException e) {
// expected
} catch (NoSuchMethodException|InstantiationException|InvocationTargetException e) {

@ -0,0 +1,33 @@
/*
* Copyright (c) 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.internal;
/*
* Non-public interface
*/
interface NP {
default int m() {
throw new UnsupportedOperationException("non-public interface: " + NP.class.getName());
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -31,4 +31,6 @@ public interface R {
public void throwException() throws FooException;
public void setBarArray(Bar[][] array);
default int m() { return 10; }
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
@ -227,7 +227,7 @@ public class ExceptionsTest extends KullaTesting {
assertExceptionMatch(se,
new ExceptionInfo(IllegalStateException.class, message,
newStackTraceElement("", "lambda$do_it$$0", se.snippet(), 1),
new StackTraceElement("com.sun.proxy.$Proxy0", "hashCode", null, -1),
new StackTraceElement("jdk.proxy1.$Proxy0", "hashCode", null, -1),
newStackTraceElement("", "", se.snippet(), 1)));
}

@ -24,73 +24,106 @@ package org.openjdk.bench.java.lang.reflect.proxy;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Warmup(iterations = 5)
@Measurement(iterations = 10)
@Fork(value = 1)
@BenchmarkMode(Mode.AverageTime)
@Fork(1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 2)
@State(Scope.Benchmark)
public class ProxyBench {
Interfaze implClass, implProxy;
PpInterfaze ppImplClass, ppImplProxy;
int a, b, c;
/**
* On Dell T7610:
*
* Benchmark w/ the old ProxyGenerator
* Benchmark Mode Cnt Score Error Units
* ProxyBench.getProxyClass1i avgt 10 20.472 +/- 0.209 ns/op
* ProxyBench.getProxyClass4i avgt 10 57.353 +/- 0.461 ns/op
* ProxyBench.newProxyInstance1i avgt 10 31.459 +/- 0.516 ns/op
* ProxyBench.newProxyInstance4i avgt 10 66.580 +/- 0.983 ns/op
*
* Benchmark w/ the new ProxyGenerator using ASM
* Benchmark Mode Cnt Score Error Units
* ProxyBench.getProxyClass1i avgt 10 21.291 +/- 0.475 ns/op
* ProxyBench.getProxyClass4i avgt 10 61.481 +/- 4.709 ns/op
* ProxyBench.newProxyInstance1i avgt 10 30.177 +/- 0.761 ns/op
* ProxyBench.newProxyInstance4i avgt 10 68.302 +/- 1.344 ns/op
*/
@Setup(Level.Trial)
public void setup() {
implClass = new Clazz();
implProxy = (Interfaze) Proxy.newProxyInstance(
Interfaze.class.getClassLoader(),
new Class<?>[]{Interfaze.class},
new IHandler()
);
ppImplClass = new PpClazz();
ppImplProxy = (PpInterfaze) Proxy.newProxyInstance(
PpInterfaze.class.getClassLoader(),
new Class<?>[]{PpInterfaze.class},
new IHandler()
);
interface PkgPrivate1 {
void m1();
}
interface PkgPrivate2 {
void m2();
}
static final InvocationHandler handler = (proxy, method, args) -> null;
static final ClassLoader loader1 = null;
static final Class<?>[] interfaces1 = {Runnable.class};
static final ClassLoader loader4 = PkgPrivate1.class.getClassLoader();
static final Class<?>[] interfaces4 = {Runnable.class, Callable.class,
PkgPrivate1.class, PkgPrivate2.class};
@Benchmark
@SuppressWarnings("deprecation")
public Class<?> getProxyClass1i() {
return Proxy.getProxyClass(loader1, interfaces1);
ThreadLocalRandom tlr = ThreadLocalRandom.current();
a = tlr.nextInt();
b = tlr.nextInt();
c = tlr.nextInt();
}
@Benchmark
@SuppressWarnings("deprecation")
public Class<?> getProxyClass4i() {
return Proxy.getProxyClass(loader4, interfaces4);
public int implClass() {
return implClass.sum(a, b, c);
}
@Benchmark
public Object newProxyInstance1i() {
return Proxy.newProxyInstance(loader1, interfaces1, handler);
public int implProxy() {
return implProxy.sum(a, b, c);
}
@Benchmark
public Object newProxyInstance4i() {
return Proxy.newProxyInstance(loader4, interfaces4, handler);
public int ppImplClass() {
return ppImplClass.sum(a, b, c);
}
}
@Benchmark
public int ppImplProxy() {
return ppImplProxy.sum(a, b, c);
}
public interface Interfaze {
default int sum(int a, int b, int c) {
return a + b + c;
}
}
static class Clazz implements Interfaze {
@Override
public int sum(int a, int b, int c) {
return Interfaze.super.sum(a, b, c);
}
}
interface PpInterfaze {
default int sum(int a, int b, int c) {
return a + b + c;
}
}
static class PpClazz implements PpInterfaze {
@Override
public int sum(int a, int b, int c) {
return PpInterfaze.super.sum(a, b, c);
}
}
static class IHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return InvocationHandler.invokeDefault(proxy, method, args);
}
}
}