From 002e42316787a26a7d411be121fc21606d9167d9 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Wed, 15 Jun 2016 14:59:01 +0200 Subject: [PATCH 1/2] Add support for signature polymorphic methods --- build.xml | 12 ++++- .../thetaphi/forbiddenapis/ClassScanner.java | 20 ++++---- .../forbiddenapis/ClassSignature.java | 45 +++++++++++++----- .../de/thetaphi/forbiddenapis/Constants.java | 14 ++++++ src/test/antunit/Java7MethodHandles.class | Bin 0 -> 1235 bytes src/test/antunit/Java7MethodHandles.java | 30 ++++++++++++ src/test/antunit/TestJava7.xml | 34 +++++++++++++ .../forbiddenapis/CheckerSetupTest.java | 23 ++++++++- 8 files changed, 155 insertions(+), 23 deletions(-) create mode 100644 src/test/antunit/Java7MethodHandles.class create mode 100644 src/test/antunit/Java7MethodHandles.java create mode 100644 src/test/antunit/TestJava7.xml diff --git a/build.xml b/build.xml index 59be6336..3156c7ad 100644 --- a/build.xml +++ b/build.xml @@ -633,6 +633,9 @@ + + + @@ -655,6 +658,13 @@ nowarn="true" source="1.6" target="1.6" debug="true" deprecation="false" encoding="${build.encoding}"/> + + + + + + @@ -662,7 +672,7 @@ nowarn="true" source="1.8" target="1.8" debug="true" deprecation="false" encoding="${build.encoding}"/> - + diff --git a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java index 0a0b2eaf..32e50365 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java +++ b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java @@ -39,15 +39,7 @@ import org.objectweb.asm.TypePath; import org.objectweb.asm.commons.Method; -final class ClassScanner extends ClassVisitor { - static final Type DEPRECATED_TYPE = Type.getType(Deprecated.class); - static final String DEPRECATED_DESCRIPTOR = DEPRECATED_TYPE.getDescriptor(); - - static final String LAMBDA_META_FACTORY_INTERNALNAME = "java/lang/invoke/LambdaMetafactory"; - static final String LAMBDA_METHOD_NAME_PREFIX = "lambda$"; - static final String CLASS_CONSTRUCTOR_METHOD_NAME = ""; - static final String CONSTRUCTOR_METHOD_NAME = ""; - +final class ClassScanner extends ClassVisitor implements Constants { private final boolean forbidNonPortableRuntime; final RelatedClassLookup lookup; final List violations = new ArrayList(); @@ -350,13 +342,21 @@ private String checkMethodAccess(String owner, Method method) { } private String checkMethodAccessRecursion(String owner, Method method, boolean checkClassUse) { - final String printout = forbiddenMethods.get(owner + '\000' + method); + String printout = forbiddenMethods.get(owner + '\000' + method); if (printout != null) { return "Forbidden method invocation: " + printout; } final ClassSignature c = lookup.lookupRelatedClass(owner); if (c != null) { String violation; + if (c.signaturePolymorphicMethods.contains(method.getName())) { + // convert the invoked descriptor to a signature polymorphic one for the lookup + final Method lookupMethod = new Method(method.getName(), SIGNATURE_POLYMORPHIC_DESCRIPTOR); + printout = forbiddenMethods.get(owner + '\000' + lookupMethod); + if (printout != null) { + return "Forbidden method invocation (signature polymorphic): " + printout; + } + } if (checkClassUse && c.methods.contains(method)) { violation = checkClassUse(owner, "class/interface"); if (violation != null) { diff --git a/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java b/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java index 0fd37522..34ca4470 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java +++ b/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java @@ -18,6 +18,11 @@ package de.thetaphi.forbiddenapis; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; @@ -26,18 +31,14 @@ import org.objectweb.asm.Type; import org.objectweb.asm.commons.Method; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - /** Utility class that is used to get an overview of all fields and implemented * methods of a class. It make the signatures available as Sets. */ -final class ClassSignature { +final class ClassSignature implements Constants { private ClassReader reader; public final boolean isRuntimeClass; public final Set methods; - public final Set fields; + public final Set fields, signaturePolymorphicMethods; public final String className, superName; public final String[] interfaces; @@ -50,11 +51,19 @@ public ClassSignature(final ClassReader classReader, boolean isRuntimeClass, boo this.interfaces = classReader.getInterfaces(); final Set methods = new HashSet(); final Set fields = new HashSet(); + final Set signaturePolymorphicMethods = new HashSet(); classReader.accept(new ClassVisitor(Opcodes.ASM5) { @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + public MethodVisitor visitMethod(int access, final String name, String desc, String signature, String[] exceptions) { final Method m = new Method(name, desc); methods.add(m); + if (className.startsWith(SIGNATURE_POLYMORPHIC_PKG_INTERNALNAME) && + (access & Opcodes.ACC_VARARGS) != 0 && + (access & Opcodes.ACC_NATIVE) != 0 && + SIGNATURE_POLYMORPHIC_DESCRIPTOR.equals(desc) + ) { + signaturePolymorphicMethods.add(name); + } return null; } @@ -64,8 +73,9 @@ public FieldVisitor visitField(int access, String name, String desc, String sign return null; } }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - this.methods = Collections.unmodifiableSet(methods); - this.fields = Collections.unmodifiableSet(fields); + this.methods = createSet(methods); + this.fields = createSet(fields); + this.signaturePolymorphicMethods = createSet(signaturePolymorphicMethods); } /** Alternative ctor that can be used to build the information via reflection from an already loaded class. Useful for Java 9 Jigsaw. */ @@ -82,8 +92,16 @@ public ClassSignature(final Class clazz, boolean isRuntimeClass) { } final Set methods = new HashSet(); final Set fields = new HashSet(); + final Set signaturePolymorphicMethods = new HashSet(); for (final java.lang.reflect.Method m : clazz.getDeclaredMethods()) { methods.add(Method.getMethod(m)); + if (className.startsWith(SIGNATURE_POLYMORPHIC_PKG_INTERNALNAME) && + m.isVarArgs() && + (m.getModifiers() & Modifier.NATIVE) != 0 && + SIGNATURE_POLYMORPHIC_DESCRIPTOR.equals(Type.getMethodDescriptor(m)) + ) { + signaturePolymorphicMethods.add(m.getName()); + } } for (final java.lang.reflect.Constructor m : clazz.getDeclaredConstructors()) { methods.add(Method.getMethod(m)); @@ -91,8 +109,13 @@ public ClassSignature(final Class clazz, boolean isRuntimeClass) { for (final java.lang.reflect.Field f : clazz.getDeclaredFields()) { fields.add(f.getName()); } - this.methods = Collections.unmodifiableSet(methods); - this.fields = Collections.unmodifiableSet(fields); + this.methods = createSet(methods); + this.fields = createSet(fields); + this.signaturePolymorphicMethods = createSet(signaturePolymorphicMethods); + } + + private static Set createSet(Set s) { + return s.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(s); } public ClassReader getReader() { diff --git a/src/main/java/de/thetaphi/forbiddenapis/Constants.java b/src/main/java/de/thetaphi/forbiddenapis/Constants.java index 616fff06..eb8a38d1 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Constants.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Constants.java @@ -21,6 +21,8 @@ import java.util.Locale; import java.util.regex.Pattern; +import org.objectweb.asm.Type; + public interface Constants { final String BS_JDK_NONPORTABLE = "jdk-non-portable"; @@ -29,5 +31,17 @@ public interface Constants { final String DEPRECATED_WARN_INTERNALRUNTIME = String.format(Locale.ENGLISH, "The setting 'internalRuntimeForbidden' was deprecated and will be removed in next version. For backwards compatibility task/mojo is using '%s' bundled signatures instead.", BS_JDK_NONPORTABLE); + + final Type DEPRECATED_TYPE = Type.getType(Deprecated.class); + final String DEPRECATED_DESCRIPTOR = DEPRECATED_TYPE.getDescriptor(); + + final String LAMBDA_META_FACTORY_INTERNALNAME = "java/lang/invoke/LambdaMetafactory"; + final String LAMBDA_METHOD_NAME_PREFIX = "lambda$"; + + final String SIGNATURE_POLYMORPHIC_PKG_INTERNALNAME = "java/lang/invoke/"; + final String SIGNATURE_POLYMORPHIC_DESCRIPTOR = Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object[].class)); + + final String CLASS_CONSTRUCTOR_METHOD_NAME = ""; + final String CONSTRUCTOR_METHOD_NAME = ""; } diff --git a/src/test/antunit/Java7MethodHandles.class b/src/test/antunit/Java7MethodHandles.class new file mode 100644 index 0000000000000000000000000000000000000000..bb770fdbef87f27bb6258fc274a3fe760a2f99cf GIT binary patch literal 1235 zcmaJ>>rN9v6#k|yU07Cb<$kw-wxCo+L@k0!Fk*_D&@>_bnzmy*u$^tTyQpvA>*x<4 zn)m=dl=19tO&8nJWOvT>`_4IYw!i=U_yu4C&vo=;LB}F)YDo5CsSnFo(Qym6b)=Bi z5%t`wSkrJv!(E2>wp7yDVTh*E1%}w3RTd0`IjO{3w^kH(o)=BBjO45mHw)aBp58RZ zoT_XvjOO0(Grn~woT^oR%~jbHjVxi+xKs?|sSmjm@@Gu0Dw!k4ma3BVxwu`S>_i7Q zL!?$^SO_Ibb!MH4Oqhf?;!!rXr z*rgG+GcgRcaNiYAM2T8Sv`qP`ZG8$ZV7_JTFbbNZQFn``Eafcg)U8ul{{-VeDPixK zywMO8mQJNZqv`~;9AwhqhFhEm$`KV|)2-%@-|v$v1ma^;_)IJO3U2;Ood&Jf{QU@& z7pb=VHbd7sJqV&(=|b1sz-Ai^{U4<&7o_dD++^U_GB?*VR7+bY*{*d3#8lbbwUyD` z1fUF?;bN|??LnC4s*65v?>qN?5qfA(!7xBlhE7Ij&*}x(H{U%-@3?QA#1P3w5P)Hf zkoFY`j1q{KEYU;%4#3hSA_+uCV)Xiso=e2fq2<YhHT>mH%8zSm?YXHJyVz_x*4*^NSh^jj^qgD@qkX9Y$@vQKBoQx4c|g? literal 0 HcmV?d00001 diff --git a/src/test/antunit/Java7MethodHandles.java b/src/test/antunit/Java7MethodHandles.java new file mode 100644 index 00000000..df40bc87 --- /dev/null +++ b/src/test/antunit/Java7MethodHandles.java @@ -0,0 +1,30 @@ +/* + * (C) Copyright Uwe Schindler (Generics Policeman) and others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* The binary class file is packaged together with the source distribution. + */ + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +class Java7MethodHandles { + static void main(String... args) throws Throwable { + MethodHandle mh = MethodHandles.publicLookup().findVirtual(StringBuilder.class, "append", + MethodType.methodType(StringBuilder.class, int.class)); + StringBuilder result = (StringBuilder) mh.invoke(new StringBuilder(), 1); + } +} diff --git a/src/test/antunit/TestJava7.xml b/src/test/antunit/TestJava7.xml new file mode 100644 index 00000000..659c0e08 --- /dev/null +++ b/src/test/antunit/TestJava7.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + java.lang.invoke.MethodHandle#invoke(java.lang.Object[]) @ Forbidden signature polymorphic method + + + + + + + diff --git a/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java index e576dc3d..ad23d80e 100644 --- a/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java +++ b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java @@ -17,8 +17,9 @@ package de.thetaphi.forbiddenapis; import static de.thetaphi.forbiddenapis.Checker.Option.*; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import static org.junit.Assume.assumeTrue; +import static org.junit.Assume.assumeNoException; import java.util.Collections; import java.util.EnumSet; @@ -87,4 +88,24 @@ public void testEmptyCtor() throws Exception { assertEquals(EnumSet.noneOf(Checker.Option.class), chk.options); } + @Test + public void testRuntimeClassSignatures() throws Exception { + ClassSignature cs = checker.lookupRelatedClass("java/lang/String"); + assertTrue(cs.isRuntimeClass); + assertTrue(cs.signaturePolymorphicMethods.isEmpty()); + } + + @Test + public void testSignaturePolymorphic() throws Exception { + try { + ClassSignature cs = checker.lookupRelatedClass("java/lang/invoke/MethodHandle"); + assertTrue(cs.signaturePolymorphicMethods.contains("invoke")); + assertTrue(cs.signaturePolymorphicMethods.contains("invokeExact")); + // System.out.println(cs.signaturePolymorphicMethods); + } catch (WrapperRuntimeException we) { + assertTrue(we.getCause() instanceof ClassNotFoundException); + assumeNoException("This test only works with Java 7+", we); + } + } + } From 8b8fed53f3c1b33cdeacbe74b949b923eb70563c Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Sat, 18 Jun 2016 19:48:06 +0200 Subject: [PATCH 2/2] Small cleanup; remove debug message --- src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java | 4 ++-- src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java | 2 +- src/test/antunit/TestJava7.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java index 32e50365..f380ed07 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java +++ b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java @@ -348,15 +348,15 @@ private String checkMethodAccessRecursion(String owner, Method method, boolean c } final ClassSignature c = lookup.lookupRelatedClass(owner); if (c != null) { - String violation; if (c.signaturePolymorphicMethods.contains(method.getName())) { // convert the invoked descriptor to a signature polymorphic one for the lookup final Method lookupMethod = new Method(method.getName(), SIGNATURE_POLYMORPHIC_DESCRIPTOR); printout = forbiddenMethods.get(owner + '\000' + lookupMethod); if (printout != null) { - return "Forbidden method invocation (signature polymorphic): " + printout; + return "Forbidden method invocation: " + printout; } } + String violation; if (checkClassUse && c.methods.contains(method)) { violation = checkClassUse(owner, "class/interface"); if (violation != null) { diff --git a/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java b/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java index 34ca4470..18b71bbe 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java +++ b/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java @@ -54,7 +54,7 @@ public ClassSignature(final ClassReader classReader, boolean isRuntimeClass, boo final Set signaturePolymorphicMethods = new HashSet(); classReader.accept(new ClassVisitor(Opcodes.ASM5) { @Override - public MethodVisitor visitMethod(int access, final String name, String desc, String signature, String[] exceptions) { + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { final Method m = new Method(name, desc); methods.add(m); if (className.startsWith(SIGNATURE_POLYMORPHIC_PKG_INTERNALNAME) && diff --git a/src/test/antunit/TestJava7.xml b/src/test/antunit/TestJava7.xml index 659c0e08..b222c0f1 100644 --- a/src/test/antunit/TestJava7.xml +++ b/src/test/antunit/TestJava7.xml @@ -27,7 +27,7 @@ java.lang.invoke.MethodHandle#invoke(java.lang.Object[]) @ Forbidden signature polymorphic method - +