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..f380ed07 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,12 +342,20 @@ 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) { + 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: " + printout; + } + } String violation; if (checkClassUse && c.methods.contains(method)) { violation = checkClassUse(owner, "class/interface"); diff --git a/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java b/src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java index 0fd37522..18b71bbe 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) { 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 00000000..bb770fdb Binary files /dev/null and b/src/test/antunit/Java7MethodHandles.class differ 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..b222c0f1 --- /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); + } + } + }