From 2a210f3955ea2d24c2a35022a92c9328f5e11e08 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Sat, 4 Jan 2025 14:05:03 +0100 Subject: [PATCH] Allow to set severity per signature This closes #252 and #219 --- .../de/thetaphi/forbiddenapis/Checker.java | 33 +++++- .../thetaphi/forbiddenapis/ClassScanner.java | 101 ++++++++++-------- .../forbiddenapis/ForbiddenViolation.java | 10 +- .../de/thetaphi/forbiddenapis/Signatures.java | 75 ++++++++++--- .../maven/AbstractCheckMojo.java | 28 +++++ src/test/antunit/TestFailOnViolation.xml | 2 +- 6 files changed, 184 insertions(+), 65 deletions(-) diff --git a/src/main/java/de/thetaphi/forbiddenapis/Checker.java b/src/main/java/de/thetaphi/forbiddenapis/Checker.java index 7efbf5b3..2b8d5f5b 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Checker.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Checker.java @@ -58,6 +58,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 +364,10 @@ public boolean noSignaturesFilesParsed() { return forbiddenSignatures.noSignaturesFilesParsed(); } + public void setSignatureSeverity(String signature, ViolationSeverity severity) { + forbiddenSignatures.setSignatureSeverity(signature, 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 +425,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 +460,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 { diff --git a/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java b/src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java index 149136a5..1f6a1cbc 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); 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 56406342..2ed43d52 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 9e9a746e..c5a09849 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java @@ -40,6 +40,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 +95,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) { @@ -337,6 +349,15 @@ public boolean noSignaturesFilesParsed() { return numberOfFiles == 0; } + public void setSignatureSeverity(String signature, ViolationSeverity severity) { + // is it pattern or regular signature? + if (AsmUtils.isGlob(signature)) { + severityPerClassPattern.put(AsmUtils.glob2Pattern(signature), severity); + } else { + severityPerSignature.put(signature, 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 +367,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/maven/AbstractCheckMojo.java b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java index 7cc79f6f..070b4024 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java +++ b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java @@ -35,6 +35,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import org.apache.maven.plugin.AbstractMojo; @@ -116,6 +117,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 Set 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 Set 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. @@ -468,6 +485,17 @@ public void debug(String msg) { } } + if (signaturesWithSeverityWarn != null) { + for (String s : signaturesWithSeverityWarn) { + checker.setSignatureSeverity(s, Checker.ViolationSeverity.WARNING); + } + } + if (signaturesWithSeveritySuppress != null) { + for (String s : signaturesWithSeveritySuppress) { + checker.setSignatureSeverity(s, Checker.ViolationSeverity.SUPPRESS); + } + } + try { checker.addClassesToCheck(classesDirectory, files); } catch (IOException ioe) { diff --git a/src/test/antunit/TestFailOnViolation.xml b/src/test/antunit/TestFailOnViolation.xml index f7dc998c..f7ec54c4 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