Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for signature polymorphic methods #106

Merged
merged 2 commits into from
Jun 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}

}