From 25fdc6bafad29e4b1c7b0f4b340641a4d7f8c476 Mon Sep 17 00:00:00 2001 From: chibash Date: Sat, 15 Apr 2017 23:57:23 +0900 Subject: adds javassist.util.proxy.ProxyFactory#onlyPublicMethods. If true, Javassist uses java.lang.invoke.MethodHandles.Lookup to load a class. --- javassist.jar | Bin 724588 -> 724114 bytes .../javassist/util/proxy/DefineClassHelper.java | 39 ++++++++---- src/main/javassist/util/proxy/FactoryHelper.java | 7 ++- src/main/javassist/util/proxy/ProxyFactory.java | 25 +++++++- src/test/test/javassist/proxy/ProxySimpleTest.java | 69 ++++++++++++++++++++- 5 files changed, 125 insertions(+), 15 deletions(-) diff --git a/javassist.jar b/javassist.jar index 068a36b7..8d9c1982 100644 Binary files a/javassist.jar and b/javassist.jar differ diff --git a/src/main/javassist/util/proxy/DefineClassHelper.java b/src/main/javassist/util/proxy/DefineClassHelper.java index c5749925..97c83800 100644 --- a/src/main/javassist/util/proxy/DefineClassHelper.java +++ b/src/main/javassist/util/proxy/DefineClassHelper.java @@ -68,6 +68,15 @@ public class DefineClassHelper { /** * Loads a class file by a given class loader. * + *

This first tries to use {@code sun.misc.Unsafe} to load a class. + * Then it tries to use a {@code protected} method in {@code java.lang.ClassLoader} + * via {@code PrivilegedAction}. Since the latter approach is not available + * any longer by default in Java 9 or later, the JVM argument + * {@code --add-opens java.base/java.lang=ALL-UNNAMED} must be given to the JVM. + * If this JVM argument cannot be given, {@link #toPublicClass(String,byte[])} + * should be used instead. + *

+ * * @param domain if it is null, a default domain is used. * @since 3.22 */ @@ -75,9 +84,7 @@ public class DefineClassHelper { ProtectionDomain domain, byte[] bcode) throws CannotCompileException { - if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_9) - return toClass2(className, loader, domain, bcode); - else { + if (ClassFile.MAJOR_VERSION >= ClassFile.JAVA_9) if (sunMiscUnsafe != null) try { return sunMiscUnsafe.defineClass(className, bcode, 0, bcode.length, @@ -85,14 +92,24 @@ public class DefineClassHelper { } catch (Throwable t2) {} - try { - Lookup lookup = MethodHandles.lookup(); - lookup = lookup.dropLookupMode(java.lang.invoke.MethodHandles.Lookup.PRIVATE); - return lookup.defineClass(bcode); - } - catch (Throwable t) { - throw new CannotCompileException(t); - } + return toClass2(className, loader, domain, bcode); + } + + /** + * Loads a class file by {@code java.lang.invoke.MethodHandles.Lookup}. + * + * @since 3.22 + */ + static Class toPublicClass(String className, byte[] bcode) + throws CannotCompileException + { + try { + Lookup lookup = MethodHandles.lookup(); + lookup = lookup.dropLookupMode(java.lang.invoke.MethodHandles.Lookup.PRIVATE); + return lookup.defineClass(bcode); + } + catch (Throwable t) { + throw new CannotCompileException(t); } } diff --git a/src/main/javassist/util/proxy/FactoryHelper.java b/src/main/javassist/util/proxy/FactoryHelper.java index 39114657..34af79d3 100644 --- a/src/main/javassist/util/proxy/FactoryHelper.java +++ b/src/main/javassist/util/proxy/FactoryHelper.java @@ -104,7 +104,7 @@ public class FactoryHelper { /** * Loads a class file by a given class loader. * This method uses a default protection domain for the class - * but it may not work with a security manager or a sigend jar file. + * but it may not work with a security manager or a signed jar file. * * @see #toClass(ClassFile,ClassLoader,ProtectionDomain) */ @@ -125,7 +125,10 @@ public class FactoryHelper { { try { byte[] b = toBytecode(cf); - return DefineClassHelper.toClass(cf.getName(), loader, domain, b); + if (ProxyFactory.onlyPublicMethods) + return DefineClassHelper.toPublicClass(cf.getName(), b); + else + return DefineClassHelper.toClass(cf.getName(), loader, domain, b); } catch (IOException e) { throw new CannotCompileException(e); diff --git a/src/main/javassist/util/proxy/ProxyFactory.java b/src/main/javassist/util/proxy/ProxyFactory.java index 81792585..35c6c11e 100644 --- a/src/main/javassist/util/proxy/ProxyFactory.java +++ b/src/main/javassist/util/proxy/ProxyFactory.java @@ -173,6 +173,29 @@ public class ProxyFactory { */ private boolean factoryWriteReplace; + /** + *

If true, only public/protected methods are forwarded to a proxy object. + * The class for that proxy object is loaded by the {@code defineClass} method + * in {@code java.lang.invoke.MethodHandles.Lookup}, which is available in + * Java 9 or later. This works even when {@code sun.misc.Unsafe} is not + * available for some reasons (it is already deprecated in Java 9).

+ * + *

To load a class, Javassist first tries to use {@code sun.misc.Unsafe} and, + * if not available, it uses a {@code protected} method in {@code java.lang.ClassLoader} + * via {@code PrivilegedAction}. Since the latter approach is not available + * any longer by default in Java 9 or later, the JVM argument + * {@code --add-opens java.base/java.lang=ALL-UNNAMED} must be given to the JVM + * when it is used (because of lack of {@code sun.misc.Unsafe}). + * If this argument cannot be given to the JVM, {@code onlyPublicMethods} should + * be set to {@code true}. Javassist will try to load by using + * {@code java.lang.invoke.MethodHandles.Lookup}.

+ * + *

The default value is {@code false}.

+ * + * @see DefineClassHelper#toClass(String, ClassLoader, ProtectionDomain, byte[]) + * @since 3.22 + */ + public static boolean onlyPublicMethods = false; /** * If the value of this variable is not null, the class file of @@ -803,7 +826,7 @@ public class ProxyFactory { if (Modifier.isFinal(superClass.getModifiers())) throw new RuntimeException(superName + " is final"); - if (basename.startsWith("java.")) + if (basename.startsWith("java.") || onlyPublicMethods) basename = "javassist.util.proxy." + basename.replace('.', '_'); } diff --git a/src/test/test/javassist/proxy/ProxySimpleTest.java b/src/test/test/javassist/proxy/ProxySimpleTest.java index 249a2542..bbae9d8d 100644 --- a/src/test/test/javassist/proxy/ProxySimpleTest.java +++ b/src/test/test/javassist/proxy/ProxySimpleTest.java @@ -1,7 +1,6 @@ package test.javassist.proxy; import junit.framework.TestCase; -import testproxy.ProxyTester.ReadWriteData; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -209,4 +208,72 @@ public class ProxySimpleTest extends TestCase { public static class Default4 extends Default3 { public int baz() { return super.baz(); } } + + public void testPublicProxy() throws Exception { + ProxyFactory f = new ProxyFactory(); + f.writeDirectory = "./proxy"; + f.setSuperclass(PubProxy.class); + Class c = f.createClass(); + MethodHandler mi = new MethodHandler() { + public Object invoke(Object self, Method m, Method proceed, + Object[] args) throws Throwable { + PubProxy.result += args[0].toString(); + return proceed.invoke(self, args); + } + }; + PubProxy.result = ""; + PubProxy foo = (PubProxy)c.getConstructor().newInstance(); + ((Proxy)foo).setHandler(mi); + foo.foo(1); + foo.bar(2); + foo.baz(3); + assertEquals("c1p2q3r", PubProxy.result); + } + + public static class PubProxy { + public static String result; + public PubProxy() { result += "c"; } + PubProxy(int i) { result += "d"; } + void foo(int i) { result += "p"; } + protected void bar(int i) { result += "q"; } + public void baz(int i) { result += "r"; } + } + + public void testPublicProxy2() throws Exception { + boolean b = ProxyFactory.onlyPublicMethods; + ProxyFactory.onlyPublicMethods = true; + ProxyFactory f = new ProxyFactory(); + f.writeDirectory = "./proxy"; + f.setSuperclass(PubProxy2.class); + Class c = f.createClass(); + MethodHandler mi = new MethodHandler() { + public Object invoke(Object self, Method m, Method proceed, + Object[] args) throws Throwable { + PubProxy2.result += args[0].toString(); + return proceed.invoke(self, args); + } + }; + + PubProxy2.result = ""; + try { + PubProxy2 foo = (PubProxy2)c.getConstructor().newInstance(); + ((Proxy)foo).setHandler(mi); + foo.foo(1); // mi does not intercept this call. + foo.bar(2); + foo.baz(3); + assertEquals("cp2q3r", PubProxy2.result); + } + finally { + ProxyFactory.onlyPublicMethods = b; + } + } + + public static class PubProxy2 { + public static String result; + public PubProxy2() { result += "c"; } + PubProxy2(int i) { result += "d"; } + void foo(int i) { result += "p"; } + protected void bar(int i) { result += "q"; } + public void baz(int i) { result += "r"; } + } } -- cgit v1.2.3