diff --git a/src/main/docs/ant-task.html b/src/main/docs/ant-task.html
index 9a7c629..bd78c06 100644
--- a/src/main/docs/ant-task.html
+++ b/src/main/docs/ant-task.html
@@ -73,6 +73,20 @@
Parameters
Name of a built-in signatures file. |
+
+ signaturesWithSeveritySuppress |
+ String |
+ |
+ A forbidden API signature for which violations should not be reported at all (i.e. neither fail the build nor appear in the logs). This takes precedence overfailOnViolation and signaturesWithSeverityWarn . |
+
+
+
+ signaturesWithSeverityWarn |
+ String |
+ |
+ A forbidden API signature for which violations should be reported as warnings (i.e. not fail the build). This takes precedence overfailOnViolation . |
+
+
classpath |
Path |
@@ -184,7 +198,7 @@ Parameters specified as nested elements
This task supports all Ant resource types
(fileset
, filelist
, file
, tarfileset
, zipfileset
,...)
-and uses all class files from them. It automatically adds an implcit filter to file names ending in '.class'
,
+and uses all class files from them. It automatically adds an implicit filter to file names ending in '.class'
,
so you don't need to add this as include attribute to those collections.
You can also pass one or multiple classpath
elements to form a classpath. Ideally use the same configuration like the javac
task.
diff --git a/src/main/java/de/thetaphi/forbiddenapis/Checker.java b/src/main/java/de/thetaphi/forbiddenapis/Checker.java
index 7efbf5b..8998405 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/Checker.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/Checker.java
@@ -30,6 +30,7 @@
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
+import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -45,6 +46,8 @@
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
+import de.thetaphi.forbiddenapis.Checker.ViolationSeverity;
+
/**
* Forbidden APIs checker class.
*/
@@ -58,6 +61,10 @@ public static enum Option {
DISABLE_CLASSLOADING_CACHE
}
+ public enum ViolationSeverity {
+ ERROR, WARNING, INFO, DEBUG, SUPPRESS
+ }
+
public final boolean isSupportedJDK;
private final long start;
@@ -360,6 +367,11 @@ public boolean noSignaturesFilesParsed() {
return forbiddenSignatures.noSignaturesFilesParsed();
}
+ /** Adjusts the severity of a specific signature. */
+ public void setSignaturesSeverity(Collection signatures, ViolationSeverity severity) throws ParseException, IOException {
+ forbiddenSignatures.setSignaturesSeverity(signatures, severity);
+ }
+
/** Parses and adds a class from the given stream to the list of classes to check. Closes the stream when parsed (on Exception, too)! Does not log anything. */
public void addClassToCheck(final InputStream in, String name) throws IOException {
final ClassReader reader;
@@ -417,7 +429,7 @@ public void addSuppressAnnotation(String annoName) {
/** Parses a class and checks for valid method invocations */
private int checkClass(ClassMetadata c, Pattern suppressAnnotationsPattern) throws ForbiddenApiException {
final String className = c.getBinaryClassName();
- final ClassScanner scanner = new ClassScanner(c, this, forbiddenSignatures, suppressAnnotationsPattern);
+ final ClassScanner scanner = new ClassScanner(c, this, forbiddenSignatures, suppressAnnotationsPattern, options.contains(Option.FAIL_ON_VIOLATION));
try {
c.getReader().accept(scanner, ClassReader.SKIP_FRAMES);
} catch (RelatedClassLoadingException rcle) {
@@ -452,12 +464,31 @@ private int checkClass(ClassMetadata c, Pattern suppressAnnotationsPattern) thro
}
final List violations = scanner.getSortedViolations();
final Pattern splitter = Pattern.compile(Pattern.quote(ForbiddenViolation.SEPARATOR));
+ int numErrors = 0;
for (final ForbiddenViolation v : violations) {
+ if (v.severity == ViolationSeverity.ERROR) {
+ numErrors++;
+ }
for (final String line : splitter.split(v.format(className, scanner.getSourceFile()))) {
- logger.error(line);
+ switch (v.severity) {
+ case DEBUG:
+ logger.debug(line);
+ break;
+ case INFO:
+ logger.info(line);
+ break;
+ case WARNING:
+ logger.warn(line);
+ break;
+ case ERROR:
+ logger.error(line);
+ break;
+ default:
+ break;
+ }
}
}
- return violations.size();
+ return numErrors;
}
public void run() throws ForbiddenApiException {
@@ -483,5 +514,4 @@ public void run() throws ForbiddenApiException {
logger.info(message);
}
}
-
}
diff --git a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java
index 149136a..050c1bf 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java
@@ -41,6 +41,9 @@
import org.objectweb.asm.TypePath;
import org.objectweb.asm.commons.Method;
+import de.thetaphi.forbiddenapis.Checker.ViolationSeverity;
+import de.thetaphi.forbiddenapis.Signatures.ViolationResult;
+
public final class ClassScanner extends ClassVisitor implements Constants {
private final boolean forbidNonPortableRuntime;
final ClassMetadata metadata;
@@ -63,14 +66,16 @@ public final class ClassScanner extends ClassVisitor implements Constants {
// all groups that were disabled due to suppressing annotation
final BitSet suppressedGroups = new BitSet();
boolean classSuppressed = false;
+ private final boolean failOnViolation;
- public ClassScanner(ClassMetadata metadata, RelatedClassLookup lookup, Signatures forbiddenSignatures, final Pattern suppressAnnotations) {
+ public ClassScanner(ClassMetadata metadata, RelatedClassLookup lookup, Signatures forbiddenSignatures, final Pattern suppressAnnotations, boolean failOnViolation) {
super(Opcodes.ASM9);
this.metadata = metadata;
this.lookup = lookup;
this.forbiddenSignatures = forbiddenSignatures;
this.suppressAnnotations = suppressAnnotations;
this.forbidNonPortableRuntime = forbiddenSignatures.isNonPortableRuntimeForbidden();
+ this.failOnViolation = failOnViolation;
}
private void checkDone() {
@@ -87,14 +92,14 @@ public String getSourceFile() {
return source;
}
- String checkClassUse(Type type, String what, boolean isAnnotation, String origInternalName) {
+ ViolationResult checkClassUse(Type type, String what, boolean isAnnotation, String origInternalName) {
while (type.getSort() == Type.ARRAY) {
type = type.getElementType(); // unwrap array
}
if (type.getSort() != Type.OBJECT) {
return null; // we don't know this type, just pass!
}
- final String violation = forbiddenSignatures.checkType(type, what);
+ final ViolationResult violation = forbiddenSignatures.checkType(type, what);
if (violation != null) {
return violation;
}
@@ -103,10 +108,9 @@ String checkClassUse(Type type, String what, boolean isAnnotation, String origIn
final String binaryClassName = type.getClassName();
final ClassMetadata c = lookup.lookupRelatedClass(type.getInternalName(), origInternalName);
if (c != null && c.isNonPortableRuntime) {
- return String.format(Locale.ENGLISH,
+ return new ViolationResult(String.format(Locale.ENGLISH,
"Forbidden %s use: %s [non-portable or internal runtime class]",
- what, binaryClassName
- );
+ what, binaryClassName), failOnViolation ? ViolationSeverity.ERROR : ViolationSeverity.WARNING);
}
} catch (RelatedClassLoadingException e) {
// only throw exception if it is not an annotation
@@ -115,20 +119,20 @@ String checkClassUse(Type type, String what, boolean isAnnotation, String origIn
return null;
}
- String checkClassUse(String internalName, String what, String origInternalName) {
+ ViolationResult checkClassUse(String internalName, String what, String origInternalName) {
return checkClassUse(Type.getObjectType(internalName), what, false, origInternalName);
}
// TODO: @FunctionalInterface from Java 8 on
static interface AncestorVisitor {
- final String STOP = new String("STOP");
+ final ViolationResult STOP = new ViolationResult("STOP", null);
- String visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime);
+ ViolationResult visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime);
}
- String visitAncestors(ClassMetadata cls, AncestorVisitor visitor, boolean visitSelf, boolean visitInterfacesFirst) {
+ ViolationResult visitAncestors(ClassMetadata cls, AncestorVisitor visitor, boolean visitSelf, boolean visitInterfacesFirst) {
if (visitSelf) {
- final String result = visitor.visit(cls, cls.className, cls.isInterface, cls.isRuntimeClass);
+ final ViolationResult result = visitor.visit(cls, cls.className, cls.isInterface, cls.isRuntimeClass);
if (result == AncestorVisitor.STOP) {
return null;
}
@@ -139,11 +143,11 @@ String visitAncestors(ClassMetadata cls, AncestorVisitor visitor, boolean visitS
return visitAncestorsRecursive(cls, cls.className, visitor, cls.isRuntimeClass, visitInterfacesFirst);
}
- private String visitSuperclassRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
+ private ViolationResult visitSuperclassRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
if (cls.superName != null) {
final ClassMetadata c = lookup.lookupRelatedClass(cls.superName, origName);
if (c != null) {
- String result = visitor.visit(c, origName, false, previousInRuntime);
+ ViolationResult result = visitor.visit(c, origName, false, previousInRuntime);
if (result != AncestorVisitor.STOP) {
if (result != null) {
return result;
@@ -158,12 +162,12 @@ private String visitSuperclassRecursive(ClassMetadata cls, String origName, Ance
return null;
}
- private String visitInterfacesRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
+ private ViolationResult visitInterfacesRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
if (cls.interfaces != null) {
for (String intf : cls.interfaces) {
final ClassMetadata c = lookup.lookupRelatedClass(intf, origName);
if (c == null) continue;
- String result = visitor.visit(c, origName, true, previousInRuntime);
+ ViolationResult result = visitor.visit(c, origName, true, previousInRuntime);
if (result != AncestorVisitor.STOP) {
if (result != null) {
return result;
@@ -178,8 +182,8 @@ private String visitInterfacesRecursive(ClassMetadata cls, String origName, Ance
return null;
}
- private String visitAncestorsRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
- String result;
+ private ViolationResult visitAncestorsRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
+ ViolationResult result;
if (visitInterfacesFirst) {
result = visitInterfacesRecursive(cls, origName, visitor, previousInRuntime, visitInterfacesFirst);
if (result != null) {
@@ -202,7 +206,7 @@ private String visitAncestorsRecursive(ClassMetadata cls, String origName, Ances
// TODO: convert to lambda method with method reference
private final AncestorVisitor classRelationAncestorVisitor = new AncestorVisitor() {
@Override
- public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
+ public ViolationResult visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
if (previousInRuntime && c.isNonPortableRuntime) {
return null; // something inside the JVM is extending internal class/interface
}
@@ -210,9 +214,9 @@ public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAnces
}
};
- String checkType(Type type) {
+ ViolationResult checkType(Type type) {
while (type != null) {
- String violation;
+ ViolationResult violation;
switch (type.getSort()) {
case Type.OBJECT:
final String internalName = type.getInternalName();
@@ -226,7 +230,7 @@ String checkType(Type type) {
type = type.getElementType();
break;
case Type.METHOD:
- final ArrayList violations = new ArrayList<>();
+ final ArrayList violations = new ArrayList<>();
violation = checkType(type.getReturnType());
if (violation != null) {
violations.add(violation);
@@ -244,12 +248,17 @@ String checkType(Type type) {
} else {
final StringBuilder sb = new StringBuilder();
boolean nl = false;
- for (final String v : violations) {
+ ViolationSeverity severity = null;
+ for (final ViolationResult v : violations) {
if (nl) sb.append(ForbiddenViolation.SEPARATOR);
- sb.append(v);
+ sb.append(v.message);
nl = true;
+ // use the highest severity reported on this method
+ if (severity == null || v.severity.ordinal() > severity.ordinal()) {
+ severity = v.severity;
+ }
}
- return sb.toString();
+ return new ViolationResult(sb.toString(), severity);
}
default:
return null;
@@ -258,11 +267,11 @@ String checkType(Type type) {
return null;
}
- String checkDescriptor(String desc) {
+ ViolationResult checkDescriptor(String desc) {
return checkType(Type.getType(desc));
}
- String checkAnnotationDescriptor(Type type, boolean visible) {
+ ViolationResult checkAnnotationDescriptor(Type type, boolean visible) {
// for annotations, we don't need to look into super-classes, interfaces,...
return checkClassUse(type, "annotation", true, type.getInternalName());
}
@@ -273,9 +282,9 @@ void maybeSuppressCurrentGroup(Type annotation) {
}
}
- private void reportClassViolation(String violation, String where) {
+ private void reportClassViolation(ViolationResult violation, String where) {
if (violation != null) {
- violations.add(new ForbiddenViolation(currentGroupId, violation, where, -1));
+ violations.add(new ForbiddenViolation(currentGroupId, violation.message, where, -1, violation.severity));
}
}
@@ -352,9 +361,9 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str
return null;
}
- private void reportFieldViolation(String violation, String where) {
- if (violation != null) {
- violations.add(new ForbiddenViolation(currentGroupId, violation, String.format(Locale.ENGLISH, "%s of '%s'", where, name), -1));
+ private void reportFieldViolation(ViolationResult violationResult, String where) {
+ if (violationResult != null) {
+ violations.add(new ForbiddenViolation(currentGroupId, violationResult.message, String.format(Locale.ENGLISH, "%s of '%s'", where, name), -1, violationResult.severity));
}
}
};
@@ -382,12 +391,12 @@ public MethodVisitor visitMethod(final int access, final String name, final Stri
}
}
- private String checkMethodAccess(String owner, final Method method, final boolean callIsVirtual) {
+ private ViolationResult checkMethodAccess(String owner, final Method method, final boolean callIsVirtual) {
if (CLASS_CONSTRUCTOR_METHOD_NAME.equals(method.getName())) {
// we don't check for violations on class constructors
return null;
}
- String violation = checkClassUse(owner, "class/interface", owner);
+ ViolationResult violation = checkClassUse(owner, "class/interface", owner);
if (violation != null) {
return violation;
}
@@ -405,7 +414,7 @@ private String checkMethodAccess(String owner, final Method method, final boolea
}
return visitAncestors(c, new AncestorVisitor() {
@Override
- public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
+ public ViolationResult visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
final Method lookupMethod;
if (c.signaturePolymorphicMethods.contains(method.getName())) {
// convert the invoked descriptor to a signature polymorphic one for the lookup
@@ -417,11 +426,11 @@ public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAnces
return null;
}
// is we have a virtual call, look into superclasses, otherwise stop:
- final String notFoundRet = callIsVirtual ? null : AncestorVisitor.STOP;
+ final ViolationResult notFoundRet = callIsVirtual ? null : AncestorVisitor.STOP;
if (previousInRuntime && c.isNonPortableRuntime) {
return notFoundRet; // something inside the JVM is extending internal class/interface
}
- String violation = forbiddenSignatures.checkMethod(c.className, lookupMethod);
+ ViolationResult violation = forbiddenSignatures.checkMethod(c.className, lookupMethod);
if (violation != null) {
return violation;
}
@@ -437,8 +446,8 @@ public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAnces
}, true, false /* JVM spec says: interfaces after superclasses */);
}
- private String checkFieldAccess(String owner, final String field) {
- String violation = checkClassUse(owner, "class/interface", owner);
+ private ViolationResult checkFieldAccess(String owner, final String field) {
+ ViolationResult violation = checkClassUse(owner, "class/interface", owner);
if (violation != null) {
return violation;
}
@@ -453,7 +462,7 @@ private String checkFieldAccess(String owner, final String field) {
}
return visitAncestors(c, new AncestorVisitor() {
@Override
- public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
+ public ViolationResult visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
if (!c.fields.contains(field)) {
return null;
}
@@ -461,7 +470,7 @@ public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAnces
if (previousInRuntime && c.isNonPortableRuntime) {
return STOP; // something inside the JVM is extending internal class/interface
}
- String violation = forbiddenSignatures.checkField(c.className, field);
+ ViolationResult violation = forbiddenSignatures.checkField(c.className, field);
if (violation != null) {
return violation;
}
@@ -478,7 +487,7 @@ public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAnces
}, true, true /* JVM spec says: superclasses after interfaces */);
}
- private String checkHandle(Handle handle, boolean checkLambdaHandle) {
+ private ViolationResult checkHandle(Handle handle, boolean checkLambdaHandle) {
switch (handle.getTag()) {
case Opcodes.H_GETFIELD:
case Opcodes.H_PUTFIELD:
@@ -503,7 +512,7 @@ private String checkHandle(Handle handle, boolean checkLambdaHandle) {
return null;
}
- private String checkConstant(Object cst, boolean checkLambdaHandle) {
+ private ViolationResult checkConstant(Object cst, boolean checkLambdaHandle) {
if (cst instanceof Type) {
return checkType((Type) cst);
} else if (cst instanceof Handle) {
@@ -604,9 +613,9 @@ private String getHumanReadableMethodSignature() {
return sb.toString();
}
- private void reportMethodViolation(String violation, String where) {
+ private void reportMethodViolation(ViolationResult violation, String where) {
if (violation != null) {
- violations.add(new ForbiddenViolation(currentGroupId, myself, violation, String.format(Locale.ENGLISH, "%s of '%s'", where, getHumanReadableMethodSignature()), lineNo));
+ violations.add(new ForbiddenViolation(currentGroupId, myself, violation.message, String.format(Locale.ENGLISH, "%s of '%s'", where, getHumanReadableMethodSignature()), lineNo, violation.severity));
}
}
@@ -642,9 +651,9 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str
return null;
}
- private void reportRecordComponentViolation(String violation, String where) {
- if (violation != null) {
- violations.add(new ForbiddenViolation(currentGroupId, violation, String.format(Locale.ENGLISH, "%s of '%s'", where, name), -1));
+ private void reportRecordComponentViolation(ViolationResult violationResult, String where) {
+ if (violationResult != null) {
+ violations.add(new ForbiddenViolation(currentGroupId, violationResult.message, String.format(Locale.ENGLISH, "%s of '%s'", where, name), -1, violationResult.severity));
}
}
};
diff --git a/src/main/java/de/thetaphi/forbiddenapis/ForbiddenViolation.java b/src/main/java/de/thetaphi/forbiddenapis/ForbiddenViolation.java
index 5640634..2ed43d5 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/ForbiddenViolation.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/ForbiddenViolation.java
@@ -21,6 +21,8 @@
import org.objectweb.asm.commons.Method;
+import de.thetaphi.forbiddenapis.Checker.ViolationSeverity;
+
public final class ForbiddenViolation implements Comparable {
/** Separator used to allow multiple description lines per violation. */
@@ -31,17 +33,19 @@ public final class ForbiddenViolation implements Comparable
public final String description;
public final String locationInfo;
public final int lineNo;
+ public final ViolationSeverity severity;
- ForbiddenViolation(int groupId, String description, String locationInfo, int lineNo) {
- this(groupId, null, description, locationInfo, lineNo);
+ ForbiddenViolation(int groupId, String description, String locationInfo, int lineNo, ViolationSeverity severity) {
+ this(groupId, null, description, locationInfo, lineNo, severity);
}
- ForbiddenViolation(int groupId, Method targetMethod, String description, String locationInfo, int lineNo) {
+ ForbiddenViolation(int groupId, Method targetMethod, String description, String locationInfo, int lineNo, ViolationSeverity severity) {
this.groupId = groupId;
this.targetMethod = targetMethod;
this.description = description;
this.locationInfo = locationInfo;
this.lineNo = lineNo;
+ this.severity = severity;
}
public void setGroupId(int groupId) {
diff --git a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java
index 9e9a746..c862801 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java
@@ -26,8 +26,12 @@
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
@@ -40,6 +44,7 @@
import org.objectweb.asm.commons.Method;
import de.thetaphi.forbiddenapis.Checker.Option;
+import de.thetaphi.forbiddenapis.Checker.ViolationSeverity;
/** 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. */
@@ -94,21 +99,32 @@ private UnresolvableReporting(boolean reportClassNotFound) {
/** set of patterns of forbidden classes */
final Set classPatterns = new LinkedHashSet<>();
+ /** Key is used to lookup forbidden signature in following formats. Keys are generated by the corresponding
+ * {@link #getKey(String)} (classes), {@link #getKey(String, Method)} (methods),
+ * {@link #getKey(String, String)} (fields) call.
+ */
+ final Map severityPerSignature = new HashMap<>();
+ final Map severityPerClassPattern = new HashMap<>();
+
/** if enabled, the bundled signature to enable heuristics for detection of non-portable runtime calls is used */
private boolean forbidNonPortableRuntime = false;
/** number of files that were interpreted as signatures file. If 0, no (bundled) signatures files were added at all */
private int numberOfFiles = 0;
+ /** determines default severity for violations if no severity on signature level is overridden. true = ERROR, false = WARNING */
+ private boolean failOnViolation;
+
public Signatures(Checker checker) {
- this(checker, checker.logger, checker.options.contains(Option.IGNORE_SIGNATURES_OF_MISSING_CLASSES), checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES));
+ this(checker, checker.logger, checker.options.contains(Option.IGNORE_SIGNATURES_OF_MISSING_CLASSES), checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES), checker.options.contains(Option.FAIL_ON_VIOLATION));
}
- public Signatures(RelatedClassLookup lookup, Logger logger, boolean ignoreSignaturesOfMissingClasses, boolean failOnUnresolvableSignatures) {
+ public Signatures(RelatedClassLookup lookup, Logger logger, boolean ignoreSignaturesOfMissingClasses, boolean failOnUnresolvableSignatures, boolean failOnViolation) {
this.lookup = lookup;
this.logger = logger;
this.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses;
this.failOnUnresolvableSignatures = failOnUnresolvableSignatures;
+ this.failOnViolation = failOnViolation;
}
static String getKey(String internalClassName) {
@@ -126,9 +142,8 @@ static String getKey(String internalClassName, Method method) {
/** Adds the method signature to the list of disallowed methods. The Signature is checked against the given ClassLoader. */
private void addSignature(final String line, final String defaultMessage, final UnresolvableReporting report,
final boolean localIgnoreMissingClasses, final Set missingClasses) throws ParseException,IOException {
- final String clazz, field, signature;
String message = null;
- final Method method;
+ String signature;
int p = line.indexOf('@');
if (p >= 0) {
signature = line.substring(0, p).trim();
@@ -140,6 +155,30 @@ private void addSignature(final String line, final String defaultMessage, final
if (line.isEmpty()) {
throw new ParseException("Empty signature");
}
+ if (message != null && message.isEmpty()) {
+ message = null;
+ }
+ // create printout message:
+ final String printout = (message != null) ? (signature + " [" + message + "]") : signature;
+ Collection keys = getKeys(report, localIgnoreMissingClasses, missingClasses, signature);
+ if (keys != null) {
+ for (String key : keys) {
+ if (key.startsWith("c\000") || key.startsWith("f\000") || key.startsWith("m\000")) {
+ signatures.put(key, printout);
+ }
+ else {
+ classPatterns.add(new ClassPatternRule(key, message));
+ }
+ }
+ }
+ }
+
+private Collection getKeys(final UnresolvableReporting report, final boolean localIgnoreMissingClasses, final Set missingClasses,
+ final String signature) throws ParseException, IOException {
+ final String clazz;
+ final String field;
+ final Method method;
+ int p;
p = signature.indexOf('#');
if (p >= 0) {
clazz = signature.substring(0, p);
@@ -170,31 +209,28 @@ private void addSignature(final String line, final String defaultMessage, final
method = null;
field = null;
}
- if (message != null && message.isEmpty()) {
- message = null;
- }
- // create printout message:
- final String printout = (message != null) ? (signature + " [" + message + "]") : signature;
+
// check class & method/field signature, if it is really existent (in classpath), but we don't really load the class into JVM:
if (AsmUtils.isGlob(clazz)) {
if (method != null || field != null) {
throw new ParseException(String.format(Locale.ENGLISH, "Class level glob pattern cannot be combined with methods/fields: %s", signature));
}
- classPatterns.add(new ClassPatternRule(clazz, message));
+ return Collections.singleton(clazz);
} else {
final ClassMetadata c;
+ Collection keys = new ArrayList<>();
try {
c = lookup.getClassFromClassLoader(clazz);
} catch (ClassNotFoundException cnfe) {
if (this.ignoreSignaturesOfMissingClasses || localIgnoreMissingClasses) {
- return;
+ return null;
}
if (report.reportClassNotFound) {
report.parseFailed(logger, String.format(Locale.ENGLISH, "Class '%s' not found on classpath", cnfe.getMessage()), signature);
} else {
missingClasses.add(clazz);
}
- return;
+ return null;
}
if (method != null) {
assert field == null;
@@ -204,28 +240,29 @@ private void addSignature(final String line, final String defaultMessage, final
if (m.getName().equals(method.getName()) &&
(WILDCARD_ARGS.equals(method.getDescriptor()) || Arrays.equals(m.getArgumentTypes(), method.getArgumentTypes()))) {
found = true;
- signatures.put(getKey(c.className, m), printout);
+ keys.add(getKey(c.className, m));
// don't break when found, as there may be more covariant overrides!
}
}
if (!found) {
report.parseFailed(logger, "Method not found", signature);
- return;
+ return null;
}
} else if (field != null) {
assert method == null;
if (!c.fields.contains(field)) {
report.parseFailed(logger, "Field not found", signature);
- return;
+ return null;
}
- signatures.put(getKey(c.className, field), printout);
+ keys.add(getKey(c.className, field));
} else {
assert field == null && method == null;
// only add the signature as class name
- signatures.put(getKey(c.className), printout);
+ keys.add(getKey(c.className));
}
+ return keys;
}
- }
+}
private void reportMissingSignatureClasses(Set missingClasses) {
if (missingClasses.isEmpty()) {
@@ -337,6 +374,26 @@ public boolean noSignaturesFilesParsed() {
return numberOfFiles == 0;
}
+ public void setSignaturesSeverity(Collection signature, ViolationSeverity severity) throws ParseException, IOException {
+ logger.info("Adjusting severity to " + severity + " for signatures...");
+ for (String s : signature) {
+ setSignatureSeverity(s, severity);
+ }
+ }
+
+ public void setSignatureSeverity(String signature, ViolationSeverity severity) throws ParseException, IOException {
+ Collection keys = getKeys(UnresolvableReporting.SILENT, false, new HashSet(), signature);
+ if (keys != null) {
+ for (String key : keys) {
+ if (key.startsWith("c\000") || key.startsWith("f\000") || key.startsWith("m\000")) {
+ severityPerSignature.put(key, severity);
+ } else {
+ severityPerClassPattern.put(AsmUtils.glob2Pattern(key), severity);
+ }
+ }
+ }
+ }
+
/** Returns if bundled signature to enable heuristics for detection of non-portable runtime calls is used */
public boolean isNonPortableRuntimeForbidden() {
return this.forbidNonPortableRuntime;
@@ -346,33 +403,63 @@ private static String formatTypePrintout(String printout, String what) {
return String.format(Locale.ENGLISH, "Forbidden %s use: %s", what, printout);
}
- public String checkType(Type type, String what) {
+ /**
+ * Represents a violation (usage of a forbidden method/field/class).
+ * Encapsulates both message and severity.
+ */
+ public static class ViolationResult {
+ public final String message;
+ public final ViolationSeverity severity;
+
+ public ViolationResult(String message, ViolationSeverity severity) {
+ this.message = message;
+ this.severity = severity;
+ }
+ }
+
+ public ViolationResult checkType(Type type, String what) {
if (type.getSort() != Type.OBJECT) {
return null; // we don't know this type, just pass!
}
+ final String key = getKey(type.getInternalName());
final String printout = signatures.get(getKey(type.getInternalName()));
if (printout != null) {
- return formatTypePrintout(printout, what);
+ return new ViolationResult(formatTypePrintout(printout, what), getSeverityForKey(key));
}
final String binaryClassName = type.getClassName();
for (final ClassPatternRule r : classPatterns) {
if (r.matches(binaryClassName)) {
- return formatTypePrintout(r.getPrintout(binaryClassName), what);
+ return new ViolationResult(formatTypePrintout(r.getPrintout(binaryClassName), what), getSeverityForClassName(binaryClassName));
}
}
return null;
}
- public String checkMethod(String internalClassName, Method method) {
- final String printout = signatures.get(getKey(internalClassName, method));
- return (printout == null) ? null : "Forbidden method invocation: ".concat(printout);
+ public ViolationResult checkMethod(String internalClassName, Method method) {
+ final String key = getKey(internalClassName, method);
+ final String printout = signatures.get(key);
+ return (printout == null) ? null : new ViolationResult("Forbidden method invocation: ".concat(printout), getSeverityForKey(key));
}
- public String checkField(String internalClassName, String field) {
- final String printout = signatures.get(getKey(internalClassName, field));
- return (printout == null) ? null : "Forbidden field access: ".concat(printout);
+ public ViolationResult checkField(String internalClassName, String field) {
+ final String key = getKey(internalClassName, field);
+ final String printout = signatures.get(key);
+ return (printout == null) ? null : new ViolationResult("Forbidden field access: ".concat(printout), getSeverityForKey(key));
}
-
+
+ private ViolationSeverity getSeverityForKey(String key) {
+ return severityPerSignature.getOrDefault(key, failOnViolation ? ViolationSeverity.ERROR : ViolationSeverity.WARNING);
+ }
+
+ private ViolationSeverity getSeverityForClassName(String className) {
+ for (final Map.Entry e : severityPerClassPattern.entrySet()) {
+ if (e.getKey().matcher(className).matches()) {
+ return e.getValue();
+ }
+ }
+ return failOnViolation ? ViolationSeverity.ERROR : ViolationSeverity.WARNING;
+ }
+
public static String fixTargetVersion(String name) throws ParseException {
final Matcher m = JDK_SIG_PATTERN.matcher(name);
if (m.matches()) {
diff --git a/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java b/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java
index b93b198..48e34f9 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java
@@ -20,6 +20,7 @@
import static de.thetaphi.forbiddenapis.Checker.Option.*;
+import org.apache.maven.plugins.annotations.Parameter;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
@@ -43,6 +44,7 @@
import java.io.IOException;
import java.io.File;
+import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator;
@@ -61,6 +63,9 @@ public class AntTask extends Task implements Constants {
private final Union apiSignatures = new Union();
private final Collection bundledSignatures = new LinkedHashSet<>();
private final Collection suppressAnnotations = new LinkedHashSet<>();
+ private final Collection signaturesWithSeverityWarn = new LinkedHashSet<>();;
+ private final Collection signaturesWithSeveritySuppress = new LinkedHashSet<>();;
+
private Path classpath = null;
private boolean failOnUnsupportedJava = false;
@@ -174,6 +179,12 @@ public void debug(String msg) {
checker.parseSignaturesFile(r.getInputStream(), r.toString());
}
}
+ if (!signaturesWithSeverityWarn.isEmpty()) {
+ checker.setSignaturesSeverity(signaturesWithSeverityWarn, Checker.ViolationSeverity.WARNING);
+ }
+ if (!signaturesWithSeveritySuppress.isEmpty()) {
+ checker.setSignaturesSeverity(signaturesWithSeveritySuppress, Checker.ViolationSeverity.SUPPRESS);
+ }
} catch (IOException ioe) {
throw new BuildException("IO problem while reading files with API signatures: " + ioe.getMessage(), ioe);
} catch (ParseException pe) {
@@ -289,6 +300,23 @@ public void setBundledSignatures(String name) {
createBundledSignatures().setName(name);
}
+ /**
+ * A list of forbidden API signatures for which violations should not be reported at all (i.e. neither fail the build nor appear in the logs). This takes precedence over {@link #failOnViolation} and {@link #signaturesWithSeverityWarn}.
+ * In order to be effective the signature must be given in either {@link #bundledSignatures}, {@link #signaturesFiles}, {@link #signaturesArtifacts}, or {@link #signatures}.
+ * @since 3.9
+ */
+ public void setSignaturesWithSeverityWarn(String signature) {
+ signaturesWithSeverityWarn.add(signature);
+ }
+
+ /** A list of forbidden API signatures for which violations should not be reported at all (i.e. neither fail the build nor appear in the logs). This takes precedence over {@link #failOnViolation} and {@link #signaturesWithSeverityWarn}.
+ * In order to be effective the signature must be given in either {@link #bundledSignatures}, {@link #signaturesFiles}, {@link #signaturesArtifacts}, or {@link #signatures}.
+ * @since 3.9
+ */
+ public void setSignaturesWithSeveritySuppress(String signature) {
+ signaturesWithSeveritySuppress.add(signature);
+ }
+
/** Creates a instance of an annotation class name that suppresses error reporting in classes/methods/fields. */
public SuppressAnnotationType createSuppressAnnotation() {
final SuppressAnnotationType s = new SuppressAnnotationType();
diff --git a/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java b/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java
index 8e6f275..e3c75f0 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java
@@ -53,8 +53,9 @@
public final class CliMain implements Constants {
private final Logger logger;
- private final Option classpathOpt, dirOpt, includesOpt, excludesOpt, signaturesfileOpt, bundledsignaturesOpt, suppressannotationsOpt,
- allowmissingclassesOpt, ignoresignaturesofmissingclassesOpt, allowunresolvablesignaturesOpt, versionOpt, helpOpt, debugOpt;
+ private final Option classpathOpt, dirOpt, includesOpt, excludesOpt, signaturesfileOpt, bundledsignaturesOpt, signatureswithseveritysuppressOpt,
+ signatureswithseveritywarnOpt, suppressannotationsOpt, allowmissingclassesOpt, ignoresignaturesofmissingclassesOpt, allowunresolvablesignaturesOpt,
+ versionOpt, helpOpt, debugOpt;
private final CommandLine cmd;
public static final int EXIT_SUCCESS = 0;
@@ -121,6 +122,20 @@ public CliMain(String... args) throws ExitException {
.valueSeparator(',')
.argName("name")
.build());
+ options.addOption(signatureswithseveritysuppressOpt = Option.builder()
+ .desc("forbidden API signature for which violations should not be reported at all (separated by commas or option can be given multiple times)")
+ .longOpt("signatureswithseveritysuppress")
+ .hasArgs()
+ .valueSeparator(',')
+ .argName("name")
+ .build());
+ options.addOption(signatureswithseveritywarnOpt = Option.builder()
+ .desc("forbidden API signature for which violations just be reported at warn level but not lead to a non-success exit code (separated by commas or option can be given multiple times)")
+ .longOpt("signatureswithseveritywarn")
+ .hasArgs()
+ .valueSeparator(',')
+ .argName("name")
+ .build());
options.addOption(suppressannotationsOpt = Option.builder()
.desc("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)")
.longOpt("suppressannotation")
@@ -140,7 +155,7 @@ public CliMain(String... args) throws ExitException {
.desc("DEPRECATED: don't fail if a signature is not resolving")
.longOpt("allowunresolvablesignatures")
.build());
-
+
try {
this.cmd = new DefaultParser().parse(options, args);
final boolean debugLogging = cmd.hasOption(debugOpt.getLongOpt());
@@ -276,6 +291,14 @@ public void run() throws ExitException {
final File f = new File(sf).getAbsoluteFile();
checker.parseSignaturesFile(f);
}
+ final String[] signaturesWithSeverityWarn = cmd.getOptionValues(signatureswithseveritywarnOpt.getLongOpt());
+ if (signaturesWithSeverityWarn != null) {
+ checker.setSignaturesSeverity(Arrays.asList(signaturesWithSeverityWarn), Checker.ViolationSeverity.WARNING);
+ }
+ final String[] signaturesWithSeveritySuppress = cmd.getOptionValues(signatureswithseveritysuppressOpt.getLongOpt());
+ if (signaturesWithSeveritySuppress != null) {
+ checker.setSignaturesSeverity(Arrays.asList(signaturesWithSeveritySuppress), Checker.ViolationSeverity.SUPPRESS);
+ }
} catch (IOException ioe) {
throw new ExitException(EXIT_ERR_OTHER, "IO problem while reading files with API signatures: " + ioe);
} catch (ParseException pe) {
diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java
index 5b3de23..6b695b7 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java
@@ -25,6 +25,7 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
+import java.util.Arrays;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.List;
@@ -32,6 +33,7 @@
import java.util.Objects;
import java.util.Set;
+import org.apache.maven.plugins.annotations.Parameter;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.InvalidUserDataException;
@@ -231,7 +233,38 @@ public Set getBundledSignatures() {
public void setBundledSignatures(Set bundledSignatures) {
data.bundledSignatures = bundledSignatures;
}
+ /**
+ *Aa list of forbidden API signatures for which violations should not be reported at all (i.e. neither fail the build nor appear in the logs). This takes precedence over {@link #getFailOnViolation()} and {@link #getSignaturesWithSeverityWarn()}.
+ * In order to be effective the signature must be given in either {@link #getSignaturesFiles()}, {@link #getBundledSignatures()}, or {@link #getSignatures}.
+ * @since 3.9
+ */
+ @Input
+ @Optional
+ public Set getSignaturesWithSeveritySuppress() {
+ return data.signaturesWithSeveritySuppress;
+ }
+ /** @see #getSignaturesWithSeveritySuppress */
+ public void setSignaturesWithSeveritySuppress(Set signatures) {
+ data.signaturesWithSeveritySuppress = signatures;
+ }
+
+ /**
+ * A list of forbidden API signatures for which violations should lead to a warning only (i.e. not fail the build). This takes precedence over {@link #getFailOnViolation()}.
+ * In order to be effective the signature must be given in either {@link #getSignaturesFiles()}, {@link #getBundledSignatures()}, or {@link #getSignatures}.
+ * @since 3.9
+ */
+ @Input
+ @Optional
+ public Set getSignaturesWithSeverityWarn() {
+ return data.signaturesWithSeverityWarn;
+ }
+
+ /** @see #getSignaturesWithSeverityWarj */
+ public void setSignaturesWithSeverityWarn(Set signatures) {
+ data.signaturesWithSeverityWarn = signatures;
+ }
+
/**
* Fail the build, if the bundled ASM library cannot read the class file format
* of the runtime library or the runtime library cannot be discovered.
@@ -597,6 +630,14 @@ public void debug(String msg) {
}
checker.parseSignaturesString(sb.toString());
}
+ Set signaturesWithSeverityWarn = getSignaturesWithSeverityWarn();
+ if (signaturesWithSeverityWarn != null && !signaturesWithSeverityWarn.isEmpty()) {
+ checker.setSignaturesSeverity(signaturesWithSeverityWarn, Checker.ViolationSeverity.WARNING);
+ }
+ Set signaturesWithSeveritySuppress = getSignaturesWithSeveritySuppress();
+ if (signaturesWithSeveritySuppress != null && !signaturesWithSeveritySuppress.isEmpty()) {
+ checker.setSignaturesSeverity(signaturesWithSeveritySuppress, Checker.ViolationSeverity.SUPPRESS);
+ }
} catch (IOException ioe) {
throw new GradleException("IO problem while reading files with API signatures.", ioe);
} catch (ParseException pe) {
diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java
index 6b92fa3..9adb140 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java
@@ -41,7 +41,9 @@ public CheckForbiddenApisExtension(Project project) {
public Set signaturesURLs = new LinkedHashSet<>();
public List signatures = new ArrayList<>();
public Set bundledSignatures = new LinkedHashSet<>(),
- suppressAnnotations = new LinkedHashSet<>();
+ suppressAnnotations = new LinkedHashSet<>(),
+ signaturesWithSeveritySuppress = new LinkedHashSet<>(),
+ signaturesWithSeverityWarn = new LinkedHashSet<>();
public boolean failOnUnsupportedJava = false,
failOnMissingClasses = true,
failOnUnresolvableSignatures = true,
diff --git a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java
index 7cc79f6..2813077 100644
--- a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java
+++ b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java
@@ -116,6 +116,22 @@ public abstract class AbstractCheckMojo extends AbstractMojo implements Constant
@Parameter(required = false)
private String[] bundledSignatures;
+ /**
+ * Specifies a list of forbidden API signatures for which violations should lead to a warning only (i.e. not fail the build). This takes precedence over {@link #failOnViolation}.
+ * In order to be effective the signature must be given in either {@link #bundledSignatures}, {@link #signaturesFiles}, {@link #signaturesArtifacts}, or {@link #signatures}.
+ * @since 3.9
+ */
+ @Parameter(required = false)
+ private String[] signaturesWithSeverityWarn;
+
+ /**
+ * Specifies a list of forbidden API signatures for which violations should not be reported at all (i.e. neither fail the build nor appear in the logs). This takes precedence over {@link #failOnViolation} and {@link #signaturesWithSeverityWarn}.
+ * In order to be effective the signature must be given in either {@link #bundledSignatures}, {@link #signaturesFiles}, {@link #signaturesArtifacts}, or {@link #signatures}.
+ * @since 3.9
+ */
+ @Parameter(required = false)
+ private String[] signaturesWithSeveritySuppress;
+
/**
* Fail the build, if the bundled ASM library cannot read the class file format
* of the runtime library or the runtime library cannot be discovered.
@@ -451,6 +467,12 @@ public void debug(String msg) {
if (sig != null && sig.length() != 0) {
checker.parseSignaturesString(sig);
}
+ if (signaturesWithSeverityWarn != null) {
+ checker.setSignaturesSeverity(Arrays.asList(signaturesWithSeverityWarn), Checker.ViolationSeverity.WARNING);
+ }
+ if (signaturesWithSeveritySuppress != null) {
+ checker.setSignaturesSeverity(Arrays.asList(signaturesWithSeveritySuppress), Checker.ViolationSeverity.SUPPRESS);
+ }
} catch (IOException ioe) {
throw new MojoExecutionException("IO problem while reading files with API signatures.", ioe);
} catch (ParseException pe) {
diff --git a/src/test/antunit/TestFailOnViolation.xml b/src/test/antunit/TestFailOnViolation.xml
index f7dc998..f7ec54c 100644
--- a/src/test/antunit/TestFailOnViolation.xml
+++ b/src/test/antunit/TestFailOnViolation.xml
@@ -35,6 +35,6 @@
java.awt.Color @ Color is disallowed, thats not bad, because ANT has no colors...
java.lang.String @ You are crazy that you disallow strings
-
+
\ No newline at end of file
diff --git a/src/test/antunit/TestMavenMojo.xml b/src/test/antunit/TestMavenMojo.xml
index e68e5d3..d79c5ec 100644
--- a/src/test/antunit/TestMavenMojo.xml
+++ b/src/test/antunit/TestMavenMojo.xml
@@ -25,6 +25,7 @@
+
@@ -68,7 +69,18 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/antunit/pom-generator.xsl b/src/test/antunit/pom-generator.xsl
index 8ced350..13e2acb 100644
--- a/src/test/antunit/pom-generator.xsl
+++ b/src/test/antunit/pom-generator.xsl
@@ -56,6 +56,7 @@
jdk-system-out
${antunit.signatures}
+
@@ -83,6 +84,31 @@
+
+
+
+ withAdjustedSeverity
+
+
+ antunit.signatureWithSeveritySuppress
+
+
+
+
+
+ ${groupId}
+ ${artifactId}
+
+
+
+ ${antunit.signatureWithSeveritySuppress}
+
+
+
+
+
+
+