diff --git a/src/main/docs/ant-task.html b/src/main/docs/ant-task.html index 7aa7496f..58ac7012 100644 --- a/src/main/docs/ant-task.html +++ b/src/main/docs/ant-task.html @@ -139,7 +139,8 @@

Parameters

RetentionPolicy#CLASS. It can be applied to classes, their methods, or fields. By default, @de.thetaphi.forbiddenapis.SuppressForbidden can always be used, but needs the forbidden-apis.jar file in classpath - of compiled project, which may not be wanted. + of compiled project, which may not be wanted. Instead of a full class name, a glob + pattern may be used (e.g., **.SuppressForbidden). @@ -164,7 +165,8 @@

Parameters specified as nested elements

You can include multiple <suppressAnnotation classname="..."> elements to specify class names of custom Java annotations that are used in the checked code to suppress errors. Those annotations must have at least RetentionPolicy#CLASS. They can be applied to classes, their methods, or fields. By default, @de.thetaphi.forbiddenapis.SuppressForbidden -can always be used, but needs the forbidden-apis.jar file in classpath of compiled project, which may not be wanted.

+can always be used, but needs the forbidden-apis.jar file in classpath of compiled project, which may not be wanted. Instead of a full class name, a glob + pattern may be used (e.g., **.SuppressForbidden).

\ No newline at end of file diff --git a/src/main/java/de/thetaphi/forbiddenapis/AbstractCheckMojo.java b/src/main/java/de/thetaphi/forbiddenapis/AbstractCheckMojo.java index 0994a0e7..4db2b566 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/AbstractCheckMojo.java +++ b/src/main/java/de/thetaphi/forbiddenapis/AbstractCheckMojo.java @@ -132,6 +132,8 @@ public abstract class AbstractCheckMojo extends AbstractMojo { * or fields. By default, {@code @de.thetaphi.forbiddenapis.SuppressForbidden} * can always be used, but needs the {@code forbidden-apis.jar} file in classpath * of compiled project, which may not be wanted. + * Instead of a full class name, a glob pattern may be used (e.g., + * {@code **.SuppressForbidden}). * @since 1.8 */ @Parameter(required = false) diff --git a/src/main/java/de/thetaphi/forbiddenapis/AsmUtils.java b/src/main/java/de/thetaphi/forbiddenapis/AsmUtils.java index 6ae68f87..0e2fea2f 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/AsmUtils.java +++ b/src/main/java/de/thetaphi/forbiddenapis/AsmUtils.java @@ -59,35 +59,42 @@ public static boolean isGlob(String s) { return s.indexOf('*') >= 0 || s.indexOf('?') >= 0; } - /** Returns a regex pattern that matches the glob on class names (e.g., "sun.misc.**") */ - public static Pattern glob2Pattern(String glob) { + /** Returns a regex pattern that matches on any of the globs on class names (e.g., "sun.misc.**") */ + public static Pattern glob2Pattern(String... globs) { final StringBuilder regex = new StringBuilder(); - int i = 0, len = glob.length(); - while (i < len) { - char c = glob.charAt(i++); - switch (c) { - case '*': - if (i < len && glob.charAt(i) == '*') { - // crosses package boundaries - regex.append(".*"); - i++; - } else { + boolean needOr = false; + for (String glob : globs) { + if (needOr) { + regex.append('|'); + } + int i = 0, len = glob.length(); + while (i < len) { + char c = glob.charAt(i++); + switch (c) { + case '*': + if (i < len && glob.charAt(i) == '*') { + // crosses package boundaries + regex.append(".*"); + i++; + } else { + // do not cross package boundaries + regex.append("[^.]*"); + } + break; + + case '?': // do not cross package boundaries - regex.append("[^.]*"); - } - break; + regex.append("[^.]"); + break; - case '?': - // do not cross package boundaries - regex.append("[^.]"); - break; - - default: - if (isRegexMeta(c)) { - regex.append('\\'); - } - regex.append(c); + default: + if (isRegexMeta(c)) { + regex.append('\\'); + } + regex.append(c); + } } + needOr = true; } return Pattern.compile(regex.toString(), 0); } diff --git a/src/main/java/de/thetaphi/forbiddenapis/Checker.java b/src/main/java/de/thetaphi/forbiddenapis/Checker.java index 86a26994..234feab6 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Checker.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Checker.java @@ -37,7 +37,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -81,7 +80,7 @@ public abstract class Checker implements RelatedClassLookup { // key is pattern to binary class name: final Set forbiddenClassPatterns = new LinkedHashSet(); // descriptors (not internal names) of all annotations that suppress: - final Set suppressAnnotations = new HashSet(); + final Set suppressAnnotations = new LinkedHashSet(); protected abstract void logError(String msg); protected abstract void logWarn(String msg); @@ -402,22 +401,18 @@ public final boolean hasNoSignatures() { /** Adds the given annotation class for suppressing errors. */ public final void addSuppressAnnotation(Class anno) { - suppressAnnotations.add(Type.getDescriptor(anno)); + suppressAnnotations.add(anno.getName()); } - /** Adds suppressing annotation name in binary form (dotted). The class name is not checked for existence. */ + /** Adds suppressing annotation name in binary form (dotted). It may also be a glob pattern. The class name is not checked for existence. */ public final void addSuppressAnnotation(String annoName) { - final Type type = Type.getObjectType(AsmUtils.binaryToInternal(annoName)); - if (type.getSort() != Type.OBJECT) { - throw new IllegalArgumentException("Descriptor is not of OBJECT sort: " + type.getDescriptor()); - } - suppressAnnotations.add(type.getDescriptor()); + suppressAnnotations.add(annoName); } /** Parses a class and checks for valid method invocations */ - private int checkClass(final ClassReader reader) { + private int checkClass(final ClassReader reader, Pattern suppressAnnotationsPattern) { final String className = Type.getObjectType(reader.getClassName()).getClassName(); - final ClassScanner scanner = new ClassScanner(this, forbiddenClasses, forbiddenClassPatterns, forbiddenMethods, forbiddenFields, suppressAnnotations, internalRuntimeForbidden); + final ClassScanner scanner = new ClassScanner(this, forbiddenClasses, forbiddenClassPatterns, forbiddenMethods, forbiddenFields, suppressAnnotationsPattern, internalRuntimeForbidden); reader.accept(scanner, ClassReader.SKIP_FRAMES); final List violations = scanner.getSortedViolations(); final Pattern splitter = Pattern.compile(Pattern.quote("\n")); @@ -431,9 +426,10 @@ private int checkClass(final ClassReader reader) { public final void run() throws ForbiddenApiException { int errors = 0; + final Pattern suppressAnnotationsPattern = AsmUtils.glob2Pattern(suppressAnnotations.toArray(new String[suppressAnnotations.size()])); try { for (final ClassSignature c : classesToCheck.values()) { - errors += checkClass(c.getReader()); + errors += checkClass(c.getReader(), suppressAnnotationsPattern); } } catch (WrapperRuntimeException wre) { Throwable cause = wre.getCause(); diff --git a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java index 595e2cdc..74d603e8 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java +++ b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; +import java.util.regex.Pattern; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; @@ -58,8 +58,8 @@ final class ClassScanner extends ClassVisitor { final Map forbiddenClasses; // key is pattern to binary class name: final Iterable forbiddenClassPatterns; - // descriptors (not internal names) of all annotations that suppress: - final Set suppressAnnotations; + // pattern that matches binary (dotted) class name of all annotations that suppress: + final Pattern suppressAnnotations; private String source = null; private boolean isDeprecated = false; @@ -77,7 +77,7 @@ final class ClassScanner extends ClassVisitor { public ClassScanner(RelatedClassLookup lookup, final Map forbiddenClasses, final Iterable forbiddenClassPatterns, final Map forbiddenMethods, final Map forbiddenFields, - final Set suppressAnnotations, + final Pattern suppressAnnotations, final boolean internalRuntimeForbidden) { super(Opcodes.ASM5); this.lookup = lookup; @@ -215,19 +215,18 @@ String checkDescriptor(String desc) { return checkType(Type.getType(desc)); } - String checkAnnotationDescriptor(String desc, boolean visible) { - final Type type = Type.getType(desc); + String checkAnnotationDescriptor(Type type, boolean visible) { if (type.getSort() != Type.OBJECT) { // should never happen for annotations! - throw new IllegalArgumentException("Annotation descriptor '" + desc + "' has wrong sort: " + type.getSort()); + throw new IllegalArgumentException("Annotation descriptor '" + type.getDescriptor() + "' has wrong sort: " + type.getSort()); } // for annotations, we don't need to look into super-classes, interfaces,... // -> we just check if its disallowed or internal runtime (only if visible)! return checkClassUse(type, "annotation", visible); } - void maybeSuppressCurrentGroup(String annotationDesc) { - if (suppressAnnotations.contains(annotationDesc)) { + void maybeSuppressCurrentGroup(Type annotation) { + if (suppressAnnotations.matcher(annotation.getClassName()).matches()) { suppressedGroups.set(currentGroupId); } } @@ -244,7 +243,7 @@ public void visit(int version, int access, String name, String signature, String this.isDeprecated = (access & Opcodes.ACC_DEPRECATED) != 0; reportClassViolation(checkClassDefinition(superName, interfaces), "class declaration"); if (this.isDeprecated) { - classSuppressed |= suppressAnnotations.contains(DEPRECATED_DESCRIPTOR); + classSuppressed |= suppressAnnotations.matcher(DEPRECATED_TYPE.getClassName()).matches(); reportClassViolation(checkType(DEPRECATED_TYPE), "deprecation on class declaration"); } } @@ -260,14 +259,15 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { // don't report 2 times! return null; } - classSuppressed |= suppressAnnotations.contains(desc); - reportClassViolation(checkAnnotationDescriptor(desc, visible), "annotation on class declaration"); + final Type type = Type.getType(desc); + classSuppressed |= suppressAnnotations.matcher(type.getClassName()).matches(); + reportClassViolation(checkAnnotationDescriptor(type, visible), "annotation on class declaration"); return null; } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - reportClassViolation(checkAnnotationDescriptor(desc, visible), "type annotation on class declaration"); + reportClassViolation(checkAnnotationDescriptor(Type.getType(desc), visible), "type annotation on class declaration"); return null; } @@ -285,7 +285,7 @@ public FieldVisitor visitField(final int access, final String name, final String reportFieldViolation(checkDescriptor(desc), "field declaration"); } if (this.isDeprecated) { - maybeSuppressCurrentGroup(DEPRECATED_DESCRIPTOR); + maybeSuppressCurrentGroup(DEPRECATED_TYPE); reportFieldViolation(checkType(DEPRECATED_TYPE), "deprecation on field declaration"); } } @@ -296,14 +296,15 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { // don't report 2 times! return null; } - maybeSuppressCurrentGroup(desc); - reportFieldViolation(checkAnnotationDescriptor(desc, visible), "annotation on field declaration"); + final Type type = Type.getType(desc); + maybeSuppressCurrentGroup(type); + reportFieldViolation(checkAnnotationDescriptor(type, visible), "annotation on field declaration"); return null; } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - reportFieldViolation(checkAnnotationDescriptor(desc, visible), "type annotation on field declaration"); + reportFieldViolation(checkAnnotationDescriptor(Type.getType(desc), visible), "type annotation on field declaration"); return null; } @@ -332,7 +333,7 @@ public MethodVisitor visitMethod(final int access, final String name, final Stri reportMethodViolation(checkDescriptor(desc), "method declaration"); } if (this.isDeprecated) { - maybeSuppressCurrentGroup(DEPRECATED_DESCRIPTOR); + maybeSuppressCurrentGroup(DEPRECATED_TYPE); reportMethodViolation(checkType(DEPRECATED_TYPE), "deprecation on method declaration"); } } @@ -428,38 +429,39 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { // don't report 2 times! return null; } - maybeSuppressCurrentGroup(desc); - reportMethodViolation(checkAnnotationDescriptor(desc, visible), "annotation on method declaration"); + final Type type = Type.getType(desc); + maybeSuppressCurrentGroup(type); + reportMethodViolation(checkAnnotationDescriptor(type, visible), "annotation on method declaration"); return null; } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { - reportMethodViolation(checkAnnotationDescriptor(desc, visible), "parameter annotation on method declaration"); + reportMethodViolation(checkAnnotationDescriptor(Type.getType(desc), visible), "parameter annotation on method declaration"); return null; } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - reportMethodViolation(checkAnnotationDescriptor(desc, visible), "type annotation on method declaration"); + reportMethodViolation(checkAnnotationDescriptor(Type.getType(desc), visible), "type annotation on method declaration"); return null; } @Override public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - reportMethodViolation(checkAnnotationDescriptor(desc, visible), "annotation in method body"); + reportMethodViolation(checkAnnotationDescriptor(Type.getType(desc), visible), "annotation in method body"); return null; } @Override public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { - reportMethodViolation(checkAnnotationDescriptor(desc, visible), "annotation in method body"); + reportMethodViolation(checkAnnotationDescriptor(Type.getType(desc), visible), "annotation in method body"); return null; } @Override public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - reportMethodViolation(checkAnnotationDescriptor(desc, visible), "annotation in method body"); + reportMethodViolation(checkAnnotationDescriptor(Type.getType(desc), visible), "annotation in method body"); return null; } diff --git a/src/main/java/de/thetaphi/forbiddenapis/CliMain.java b/src/main/java/de/thetaphi/forbiddenapis/CliMain.java index 579d26e9..3e516ef1 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/CliMain.java +++ b/src/main/java/de/thetaphi/forbiddenapis/CliMain.java @@ -110,7 +110,7 @@ public CliMain(String... args) throws ExitException { .withArgName("name") .create('b')); options.addOption(suppressannotationsOpt = OptionBuilder - .withDescription("class name of annotation that suppresses error reporting in classes/methods/fields (separated by commas or option can be given multiple times)") + .withDescription("class name or glob pattern of annotation that suppresses error reporting in classes/methods/fields (separated by commas or option can be given multiple times)") .withLongOpt("suppressannotation") .hasArgs() .withValueSeparator(',') diff --git a/src/test/antunit/TestAnnotations.xml b/src/test/antunit/TestAnnotations.xml index da85b5c4..4958eccd 100644 --- a/src/test/antunit/TestAnnotations.xml +++ b/src/test/antunit/TestAnnotations.xml @@ -62,7 +62,7 @@ - + Java8Annotations$ClassFileOnly @ Forbidden class-only annotation diff --git a/src/test/antunit/TestCli.xml b/src/test/antunit/TestCli.xml index 9eb1d322..26fb241f 100644 --- a/src/test/antunit/TestCli.xml +++ b/src/test/antunit/TestCli.xml @@ -66,7 +66,7 @@ - + diff --git a/src/test/antunit/pom-incex-suppress.xml b/src/test/antunit/pom-incex-suppress.xml index eb8e0927..d2b0adb7 100644 --- a/src/test/antunit/pom-incex-suppress.xml +++ b/src/test/antunit/pom-incex-suppress.xml @@ -47,7 +47,7 @@ - Java8Annotations$FooBar + Java8Annotations$*