Skip to content

Commit

Permalink
Fix issues #8 and #25: Support for forbidden annotations, report forb…
Browse files Browse the repository at this point in the history
…idden class use in field/method declarations, refactor error reporting
  • Loading branch information
uschindler committed Apr 15, 2014
1 parent 0e35fcd commit b1bc440
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 52 deletions.
221 changes: 171 additions & 50 deletions src/main/java/de/thetaphi/forbiddenapis/Checker.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* limitations under the License.
*/

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Label;
import org.objectweb.asm.ClassVisitor;
Expand All @@ -26,6 +27,7 @@
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.commons.Method;

import java.io.BufferedReader;
Expand Down Expand Up @@ -395,7 +397,7 @@ private boolean isInternalClass(String className) {
boolean checkClassUse(String internalName) {
final String printout = forbiddenClasses.get(internalName);
if (printout != null) {
logError("Forbidden class/interface use: " + printout);
logError("Forbidden class/interface/annotation use: " + printout);
return true;
}
if (internalRuntimeForbidden) {
Expand All @@ -404,7 +406,7 @@ boolean checkClassUse(String internalName) {
final ClassSignatureLookup c = lookupRelatedClass(internalName);
if (c == null || c.isRuntimeClass) {
logError(String.format(Locale.ENGLISH,
"Forbidden class/interface use: %s [non-public internal runtime class]",
"Forbidden class/interface/annotation use: %s [non-public internal runtime class]",
referencedClassName
));
return true;
Expand Down Expand Up @@ -438,24 +440,116 @@ private boolean checkClassDefinition(String superName, String[] interfaces) {
return false;
}

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if (checkClassDefinition(superName, interfaces)) {
boolean checkType(Type type) {
while (type != null) {
switch (type.getSort()) {
case Type.OBJECT:
// don't check superclasses (TODO: investigate):
return checkClassUse(type.getInternalName());
case Type.ARRAY:
type = type.getElementType();
break;
case Type.METHOD:
boolean violation = checkType(type.getReturnType());
for (final Type t : type.getArgumentTypes()) {
violation |= checkType(t);
}
return violation;
default:
return false;
}
}
return false;
}

boolean checkDescriptor(String desc) {
return checkType(Type.getType(desc));
}

private void reportClassViolation(boolean violation) {
if (violation) {
violations[0]++;
logError(" in " + className + " (class declaration)");
final StringBuilder sb = new StringBuilder(" in ").append(className);
if (source != null) {
new Formatter(sb, Locale.ENGLISH).format(" (%s, class declaration)", source).flush();
} else {
sb.append(" (class declaration)");
}
logError(sb.toString());
}
}

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
reportClassViolation(checkClassDefinition(superName, interfaces));
}

@Override
public void visitSource(String source, String debug) {
this.source = source;
}

@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
reportClassViolation(checkDescriptor(desc));
return null;
}

@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
reportClassViolation(checkDescriptor(desc));
return null;
}

@Override
public FieldVisitor visitField(final int access, final String name, final String desc, String signature, Object value) {
return new FieldVisitor(Opcodes.ASM5) {
{
// only check signature, if field is not synthetic
if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
reportFieldViolation(checkDescriptor(desc));
}
}

@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
reportFieldViolation(checkDescriptor(desc));
return null;
}

@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
reportFieldViolation(checkDescriptor(desc));
return null;
}

private void reportFieldViolation(boolean violation) {
if (violation) {
violations[0]++;
final StringBuilder sb = new StringBuilder(" in ").append(className);
if (source != null) {
new Formatter(sb, Locale.ENGLISH).format(" (%s, field declaration of '%s')", source, name).flush();
} else {
new Formatter(sb, Locale.ENGLISH).format(" (field declaration of '%s')", name).flush();
}
logError(sb.toString());
}
}
};
}

@Override
public MethodVisitor visitMethod(final int access, final String name, final String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM5) {
private int lineNo = -1;

{
// only check signature, if method is not synthetic
if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
reportMethodViolation(checkDescriptor(desc));
}
}

private boolean checkMethodAccess(String owner, Method method) {
if (checkClassUse(owner)) {
return true;
Expand Down Expand Up @@ -508,22 +602,6 @@ private boolean checkFieldAccess(String owner, String field) {
return false;
}

private boolean checkType(Type type) {
while (type != null) {
switch (type.getSort()) {
case Type.OBJECT:
// don't check superclasses (TODO: investigate):
return checkClassUse(type.getInternalName());
case Type.ARRAY:
type = type.getElementType();
break;
default:
return false;
}
}
return false;
}

private boolean checkHandle(Handle handle) {
switch (handle.getTag()) {
case Opcodes.H_GETFIELD:
Expand Down Expand Up @@ -556,60 +634,103 @@ private boolean checkConstant(Object cst) {

@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (checkMethodAccess(owner, new Method(name, desc))) {
reportViolation();
}
reportMethodViolation(checkMethodAccess(owner, new Method(name, desc)));
}

@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (checkFieldAccess(owner, name)) {
reportViolation();
}
reportMethodViolation(checkFieldAccess(owner, name));
}

@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
reportMethodViolation(checkDescriptor(desc));
return null;
}

@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
reportMethodViolation(checkDescriptor(desc));
return null;
}

@Override
public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
reportMethodViolation(checkDescriptor(desc));
return null;
}

@Override
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) {
reportMethodViolation(checkDescriptor(desc));
return null;
}

@Override
public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
reportMethodViolation(checkDescriptor(desc));
return null;
}

@Override
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
reportMethodViolation(checkDescriptor(desc));
return null;
}

@Override
public void visitTypeInsn(int opcode, String type) {
if (opcode == Opcodes.ANEWARRAY) {
if (checkType(Type.getObjectType(type))) {
reportViolation();
}
reportMethodViolation(checkType(Type.getObjectType(type)));
}
}

@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
if (checkType(Type.getType(desc))) {
reportViolation();
}
reportMethodViolation(checkDescriptor(desc));
}

@Override
public void visitLdcInsn(Object cst) {
if (checkConstant(cst)) {
reportViolation();
}
reportMethodViolation(checkConstant(cst));
}

@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
if (checkHandle(bsm)) {
reportViolation();
}
reportMethodViolation(checkHandle(bsm));
for (final Object cst : bsmArgs) {
if (checkConstant(cst)) {
reportViolation();
}
reportMethodViolation(checkConstant(cst));
}
}

private String getHumanReadableMethodSignature() {
final Type[] args = Type.getType(desc).getArgumentTypes();
final StringBuilder sb = new StringBuilder(name).append('(');
boolean comma = false;
for (final Type t : args) {
if (comma) sb.append(',');
sb.append(t.getClassName());
comma = true;
}
sb.append(')');
return sb.toString();
}

private void reportViolation() {
violations[0]++;
final StringBuilder sb = new StringBuilder(" in ").append(className);
if (source != null && lineNo >= 0) {
new Formatter(sb, Locale.ENGLISH).format(" (%s:%d)", source, lineNo).flush();
private void reportMethodViolation(boolean violation) {
if (violation) {
violations[0]++;
final StringBuilder sb = new StringBuilder(" in ").append(className);
if (source != null) {
if (lineNo >= 0) {
new Formatter(sb, Locale.ENGLISH).format(" (%s:%d)", source, lineNo).flush();
} else {
new Formatter(sb, Locale.ENGLISH).format(" (%s, method declaration of '%s')", source, getHumanReadableMethodSignature()).flush();
}
} else {
new Formatter(sb, Locale.ENGLISH).format(" (method declaration of '%s')", getHumanReadableMethodSignature()).flush();
}
logError(sb.toString());
}
logError(sb.toString());
}

@Override
Expand Down
Binary file modified src/test/antunit/Java5ClassReferences.class
Binary file not shown.
5 changes: 4 additions & 1 deletion src/test/antunit/Java5ClassReferences.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright 2013 Uwe Schindler (Generics Policeman) and others.
* (C) Copyright 2014 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.
Expand All @@ -25,4 +25,7 @@ static Integer[][] test() {
arr[0] = new Integer(0);
return new Integer[1][1];
}

private Integer field1;
private final Integer[] field2 = null;
}
Binary file added src/test/antunit/Java8Annotations$FooBar.class
Binary file not shown.
Binary file added src/test/antunit/Java8Annotations.class
Binary file not shown.
50 changes: 50 additions & 0 deletions src/test/antunit/Java8Annotations.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* (C) Copyright 2014 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.
*/

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

/* Needs JDK 8 to compile!
* The binary class file is packaged together with the source distribution,
* because it cannot be regenerated on every Java installation!
*/

@Java8Annotations.FooBar
@Deprecated
class Java8Annotations<@Java8Annotations.FooBar X> {

@Deprecated
static void test(@FooBar int param1, @FooBar long param2) {
try {
new @FooBar Java8Annotations();
test(param1, param2);
} catch (@FooBar StackOverflowError e) {
System.out.println(FooBar.class.getName());
}
}

@Deprecated
public int testField1;

@FooBar
public int testField2;

@Retention(value=RetentionPolicy.CLASS)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER, ElementType.METHOD, ElementType.TYPE})
static @interface FooBar {}
}
Loading

0 comments on commit b1bc440

Please sign in to comment.