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 extends T> 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);
+ }
+ }
+
}