Skip to content

Commit

Permalink
Merge pull request #106 from /issues/105
Browse files Browse the repository at this point in the history
Add support for signature polymorphic methods
  • Loading branch information
uschindler authored Jun 19, 2016
2 parents c9166f3 + 8b8fed5 commit 9bb6aba
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 22 deletions.
12 changes: 11 additions & 1 deletion build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,9 @@
<condition property="-gen.sunmisc">
<available classname="sun.misc.BASE64Encoder"/>
</condition>
<condition property="-gen.jdk7">
<available classname="java.lang.invoke.MethodHandle"/>
</condition>
<condition property="-gen.jdk8">
<hasmethod classname="java.util.Collections" method="emptySortedSet"/>
</condition>
Expand All @@ -655,14 +658,21 @@
nowarn="true" source="1.6" target="1.6" debug="true" deprecation="false" encoding="${build.encoding}"/>
</target>

<target name="-generate-test-classes-jdk7" if="-gen.jdk7">
<echo level="info" message="Generating test classes for Java 7:"/>
<delete dir="src/test/antunit" includes="Java7*.class"/>
<javac includeantruntime="false" srcdir="src/test/antunit" destdir="src/test/antunit" includes="Java7*.java"
nowarn="true" source="1.7" target="1.7" debug="true" deprecation="false" encoding="${build.encoding}"/>
</target>

<target name="-generate-test-classes-jdk8" if="-gen.jdk8">
<echo level="info" message="Generating test classes for Java 8:"/>
<delete dir="src/test/antunit" includes="Java8*.class"/>
<javac includeantruntime="false" srcdir="src/test/antunit" destdir="src/test/antunit" includes="Java8*.java"
nowarn="true" source="1.8" target="1.8" debug="true" deprecation="false" encoding="${build.encoding}"/>
</target>

<target name="generate-test-classes" depends="-generate-test-classes-init,-generate-test-classes-sunmisc,-generate-test-classes-jdk6,-generate-test-classes-jdk8" description="Regenerates .class files used by tests if the current JDK version supports it"/>
<target name="generate-test-classes" depends="-generate-test-classes-init,-generate-test-classes-sunmisc,-generate-test-classes-jdk6,-generate-test-classes-jdk7,-generate-test-classes-jdk8" description="Regenerates .class files used by tests if the current JDK version supports it"/>

<target name="show-help-mojo" depends="install-maven-artifacts" description="Shows help about mojo usage">
<artifact:mvn mavenVersion="${maven.version}" failonerror="true" fork="${maven.fork}" taskname="help">
Expand Down
20 changes: 10 additions & 10 deletions src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<clinit>";
static final String CONSTRUCTOR_METHOD_NAME = "<init>";

final class ClassScanner extends ClassVisitor implements Constants {
private final boolean forbidNonPortableRuntime;
final RelatedClassLookup lookup;
final List<ForbiddenViolation> violations = new ArrayList<ForbiddenViolation>();
Expand Down Expand Up @@ -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");
Expand Down
43 changes: 33 additions & 10 deletions src/main/java/de/thetaphi/forbiddenapis/ClassSignature.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Method> methods;
public final Set<String> fields;
public final Set<String> fields, signaturePolymorphicMethods;
public final String className, superName;
public final String[] interfaces;

Expand All @@ -50,11 +51,19 @@ public ClassSignature(final ClassReader classReader, boolean isRuntimeClass, boo
this.interfaces = classReader.getInterfaces();
final Set<Method> methods = new HashSet<Method>();
final Set<String> fields = new HashSet<String>();
final Set<String> signaturePolymorphicMethods = new HashSet<String>();
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;
}

Expand All @@ -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. */
Expand All @@ -82,17 +92,30 @@ public ClassSignature(final Class<?> clazz, boolean isRuntimeClass) {
}
final Set<Method> methods = new HashSet<Method>();
final Set<String> fields = new HashSet<String>();
final Set<String> signaturePolymorphicMethods = new HashSet<String>();
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));
}
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 <T> Set<T> createSet(Set<? extends T> s) {
return s.isEmpty() ? Collections.<T>emptySet() : Collections.<T>unmodifiableSet(s);
}

public ClassReader getReader() {
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/de/thetaphi/forbiddenapis/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 = "<clinit>";
final String CONSTRUCTOR_METHOD_NAME = "<init>";

}
Binary file added src/test/antunit/Java7MethodHandles.class
Binary file not shown.
30 changes: 30 additions & 0 deletions src/test/antunit/Java7MethodHandles.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
34 changes: 34 additions & 0 deletions src/test/antunit/TestJava7.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* (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.
-->
<project xmlns:au="antlib:org.apache.ant.antunit">

<condition property="has.MethodHandle">
<available classname="java.lang.invoke.MethodHandle"/>
</condition>

<target name="testSignaturePolymorphic" if="has.MethodHandle">
<au:expectfailure expectedMessage="Check for forbidden API calls failed, see log">
<forbiddenapis failOnMissingClasses="false">
<file file="Java7MethodHandles.class"/>
java.lang.invoke.MethodHandle#invoke(java.lang.Object[]) @ Forbidden signature polymorphic method
</forbiddenapis>
</au:expectfailure>
<au:assertLogContains level="error" text="java.lang.invoke.MethodHandle#invoke(java.lang.Object[]) [Forbidden signature polymorphic method]"/>
<au:assertLogContains level="error" text=" 1 error(s)"/>
</target>

</project>
23 changes: 22 additions & 1 deletion src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

}

0 comments on commit 9bb6aba

Please sign in to comment.