Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b66a66b

Browse files
committedJan 4, 2025·
Allow to set severity per signature
This closes #252 and #219
1 parent 535ecf7 commit b66a66b

File tree

6 files changed

+185
-66
lines changed

6 files changed

+185
-66
lines changed
 

‎src/main/java/de/thetaphi/forbiddenapis/Checker.java

+30-3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public static enum Option {
5858
DISABLE_CLASSLOADING_CACHE
5959
}
6060

61+
public enum ViolationSeverity {
62+
ERROR, WARNING, INFO, DEBUG, SUPPRESS
63+
}
64+
6165
public final boolean isSupportedJDK;
6266

6367
private final long start;
@@ -360,6 +364,10 @@ public boolean noSignaturesFilesParsed() {
360364
return forbiddenSignatures.noSignaturesFilesParsed();
361365
}
362366

367+
public void setSignatureSeverity(String signature, ViolationSeverity severity) {
368+
forbiddenSignatures.setSignatureSeverity(signature, severity);
369+
}
370+
363371
/** 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. */
364372
public void addClassToCheck(final InputStream in, String name) throws IOException {
365373
final ClassReader reader;
@@ -417,7 +425,7 @@ public void addSuppressAnnotation(String annoName) {
417425
/** Parses a class and checks for valid method invocations */
418426
private int checkClass(ClassMetadata c, Pattern suppressAnnotationsPattern) throws ForbiddenApiException {
419427
final String className = c.getBinaryClassName();
420-
final ClassScanner scanner = new ClassScanner(c, this, forbiddenSignatures, suppressAnnotationsPattern);
428+
final ClassScanner scanner = new ClassScanner(c, this, forbiddenSignatures, suppressAnnotationsPattern, options.contains(Option.FAIL_ON_VIOLATION));
421429
try {
422430
c.getReader().accept(scanner, ClassReader.SKIP_FRAMES);
423431
} catch (RelatedClassLoadingException rcle) {
@@ -452,12 +460,31 @@ private int checkClass(ClassMetadata c, Pattern suppressAnnotationsPattern) thro
452460
}
453461
final List<ForbiddenViolation> violations = scanner.getSortedViolations();
454462
final Pattern splitter = Pattern.compile(Pattern.quote(ForbiddenViolation.SEPARATOR));
463+
int numErrors = 0;
455464
for (final ForbiddenViolation v : violations) {
465+
if (v.severity == ViolationSeverity.ERROR) {
466+
numErrors++;
467+
}
456468
for (final String line : splitter.split(v.format(className, scanner.getSourceFile()))) {
457-
logger.error(line);
469+
switch (v.severity) {
470+
case DEBUG:
471+
logger.debug(line);
472+
break;
473+
case INFO:
474+
logger.info(line);
475+
break;
476+
case WARNING:
477+
logger.warn(line);
478+
break;
479+
case ERROR:
480+
logger.error(line);
481+
break;
482+
default:
483+
break;
484+
}
458485
}
459486
}
460-
return violations.size();
487+
return numErrors;
461488
}
462489

463490
public void run() throws ForbiddenApiException {

‎src/main/java/de/thetaphi/forbiddenapis/ClassScanner.java

+56-47
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
import org.objectweb.asm.TypePath;
4242
import org.objectweb.asm.commons.Method;
4343

44+
import de.thetaphi.forbiddenapis.Checker.ViolationSeverity;
45+
import de.thetaphi.forbiddenapis.Signatures.ViolationResult;
46+
4447
public final class ClassScanner extends ClassVisitor implements Constants {
4548
private final boolean forbidNonPortableRuntime;
4649
final ClassMetadata metadata;
@@ -63,14 +66,16 @@ public final class ClassScanner extends ClassVisitor implements Constants {
6366
// all groups that were disabled due to suppressing annotation
6467
final BitSet suppressedGroups = new BitSet();
6568
boolean classSuppressed = false;
69+
private final boolean failOnViolation;
6670

67-
public ClassScanner(ClassMetadata metadata, RelatedClassLookup lookup, Signatures forbiddenSignatures, final Pattern suppressAnnotations) {
71+
public ClassScanner(ClassMetadata metadata, RelatedClassLookup lookup, Signatures forbiddenSignatures, final Pattern suppressAnnotations, boolean failOnViolation) {
6872
super(Opcodes.ASM9);
6973
this.metadata = metadata;
7074
this.lookup = lookup;
7175
this.forbiddenSignatures = forbiddenSignatures;
7276
this.suppressAnnotations = suppressAnnotations;
7377
this.forbidNonPortableRuntime = forbiddenSignatures.isNonPortableRuntimeForbidden();
78+
this.failOnViolation = failOnViolation;
7479
}
7580

7681
private void checkDone() {
@@ -87,14 +92,14 @@ public String getSourceFile() {
8792
return source;
8893
}
8994

90-
String checkClassUse(Type type, String what, boolean isAnnotation, String origInternalName) {
95+
ViolationResult checkClassUse(Type type, String what, boolean isAnnotation, String origInternalName) {
9196
while (type.getSort() == Type.ARRAY) {
9297
type = type.getElementType(); // unwrap array
9398
}
9499
if (type.getSort() != Type.OBJECT) {
95100
return null; // we don't know this type, just pass!
96101
}
97-
final String violation = forbiddenSignatures.checkType(type, what);
102+
final ViolationResult violation = forbiddenSignatures.checkType(type, what);
98103
if (violation != null) {
99104
return violation;
100105
}
@@ -103,10 +108,9 @@ String checkClassUse(Type type, String what, boolean isAnnotation, String origIn
103108
final String binaryClassName = type.getClassName();
104109
final ClassMetadata c = lookup.lookupRelatedClass(type.getInternalName(), origInternalName);
105110
if (c != null && c.isNonPortableRuntime) {
106-
return String.format(Locale.ENGLISH,
111+
return new ViolationResult(String.format(Locale.ENGLISH,
107112
"Forbidden %s use: %s [non-portable or internal runtime class]",
108-
what, binaryClassName
109-
);
113+
what, binaryClassName), failOnViolation ? ViolationSeverity.ERROR : ViolationSeverity.WARNING);
110114
}
111115
} catch (RelatedClassLoadingException e) {
112116
// only throw exception if it is not an annotation
@@ -115,20 +119,20 @@ String checkClassUse(Type type, String what, boolean isAnnotation, String origIn
115119
return null;
116120
}
117121

118-
String checkClassUse(String internalName, String what, String origInternalName) {
122+
ViolationResult checkClassUse(String internalName, String what, String origInternalName) {
119123
return checkClassUse(Type.getObjectType(internalName), what, false, origInternalName);
120124
}
121125

122126
// TODO: @FunctionalInterface from Java 8 on
123127
static interface AncestorVisitor {
124-
final String STOP = new String("STOP");
128+
final ViolationResult STOP = new ViolationResult("STOP", null);
125129

126-
String visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime);
130+
ViolationResult visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime);
127131
}
128132

129-
String visitAncestors(ClassMetadata cls, AncestorVisitor visitor, boolean visitSelf, boolean visitInterfacesFirst) {
133+
ViolationResult visitAncestors(ClassMetadata cls, AncestorVisitor visitor, boolean visitSelf, boolean visitInterfacesFirst) {
130134
if (visitSelf) {
131-
final String result = visitor.visit(cls, cls.className, cls.isInterface, cls.isRuntimeClass);
135+
final ViolationResult result = visitor.visit(cls, cls.className, cls.isInterface, cls.isRuntimeClass);
132136
if (result == AncestorVisitor.STOP) {
133137
return null;
134138
}
@@ -139,11 +143,11 @@ String visitAncestors(ClassMetadata cls, AncestorVisitor visitor, boolean visitS
139143
return visitAncestorsRecursive(cls, cls.className, visitor, cls.isRuntimeClass, visitInterfacesFirst);
140144
}
141145

142-
private String visitSuperclassRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
146+
private ViolationResult visitSuperclassRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
143147
if (cls.superName != null) {
144148
final ClassMetadata c = lookup.lookupRelatedClass(cls.superName, origName);
145149
if (c != null) {
146-
String result = visitor.visit(c, origName, false, previousInRuntime);
150+
ViolationResult result = visitor.visit(c, origName, false, previousInRuntime);
147151
if (result != AncestorVisitor.STOP) {
148152
if (result != null) {
149153
return result;
@@ -158,12 +162,12 @@ private String visitSuperclassRecursive(ClassMetadata cls, String origName, Ance
158162
return null;
159163
}
160164

161-
private String visitInterfacesRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
165+
private ViolationResult visitInterfacesRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
162166
if (cls.interfaces != null) {
163167
for (String intf : cls.interfaces) {
164168
final ClassMetadata c = lookup.lookupRelatedClass(intf, origName);
165169
if (c == null) continue;
166-
String result = visitor.visit(c, origName, true, previousInRuntime);
170+
ViolationResult result = visitor.visit(c, origName, true, previousInRuntime);
167171
if (result != AncestorVisitor.STOP) {
168172
if (result != null) {
169173
return result;
@@ -178,8 +182,8 @@ private String visitInterfacesRecursive(ClassMetadata cls, String origName, Ance
178182
return null;
179183
}
180184

181-
private String visitAncestorsRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
182-
String result;
185+
private ViolationResult visitAncestorsRecursive(ClassMetadata cls, String origName, AncestorVisitor visitor, boolean previousInRuntime, boolean visitInterfacesFirst) {
186+
ViolationResult result;
183187
if (visitInterfacesFirst) {
184188
result = visitInterfacesRecursive(cls, origName, visitor, previousInRuntime, visitInterfacesFirst);
185189
if (result != null) {
@@ -202,17 +206,17 @@ private String visitAncestorsRecursive(ClassMetadata cls, String origName, Ances
202206
// TODO: convert to lambda method with method reference
203207
private final AncestorVisitor classRelationAncestorVisitor = new AncestorVisitor() {
204208
@Override
205-
public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
209+
public ViolationResult visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
206210
if (previousInRuntime && c.isNonPortableRuntime) {
207211
return null; // something inside the JVM is extending internal class/interface
208212
}
209213
return checkClassUse(c.className, isInterfaceOfAncestor ? "interface" : "class", origName);
210214
}
211215
};
212216

213-
String checkType(Type type) {
217+
ViolationResult checkType(Type type) {
214218
while (type != null) {
215-
String violation;
219+
ViolationResult violation;
216220
switch (type.getSort()) {
217221
case Type.OBJECT:
218222
final String internalName = type.getInternalName();
@@ -226,7 +230,7 @@ String checkType(Type type) {
226230
type = type.getElementType();
227231
break;
228232
case Type.METHOD:
229-
final ArrayList<String> violations = new ArrayList<>();
233+
final ArrayList<ViolationResult> violations = new ArrayList<>();
230234
violation = checkType(type.getReturnType());
231235
if (violation != null) {
232236
violations.add(violation);
@@ -244,12 +248,17 @@ String checkType(Type type) {
244248
} else {
245249
final StringBuilder sb = new StringBuilder();
246250
boolean nl = false;
247-
for (final String v : violations) {
251+
ViolationSeverity severity = null;
252+
for (final ViolationResult v : violations) {
248253
if (nl) sb.append(ForbiddenViolation.SEPARATOR);
249-
sb.append(v);
254+
sb.append(v.message);
250255
nl = true;
256+
// use the highest severity reported on this method
257+
if (severity == null || v.severity.ordinal() > severity.ordinal()) {
258+
severity = v.severity;
259+
}
251260
}
252-
return sb.toString();
261+
return new ViolationResult(sb.toString(), severity);
253262
}
254263
default:
255264
return null;
@@ -258,11 +267,11 @@ String checkType(Type type) {
258267
return null;
259268
}
260269

261-
String checkDescriptor(String desc) {
270+
ViolationResult checkDescriptor(String desc) {
262271
return checkType(Type.getType(desc));
263272
}
264273

265-
String checkAnnotationDescriptor(Type type, boolean visible) {
274+
ViolationResult checkAnnotationDescriptor(Type type, boolean visible) {
266275
// for annotations, we don't need to look into super-classes, interfaces,...
267276
return checkClassUse(type, "annotation", true, type.getInternalName());
268277
}
@@ -273,9 +282,9 @@ void maybeSuppressCurrentGroup(Type annotation) {
273282
}
274283
}
275284

276-
private void reportClassViolation(String violation, String where) {
285+
private void reportClassViolation(ViolationResult violation, String where) {
277286
if (violation != null) {
278-
violations.add(new ForbiddenViolation(currentGroupId, violation, where, -1));
287+
violations.add(new ForbiddenViolation(currentGroupId, violation.message, where, -1, violation.severity));
279288
}
280289
}
281290

@@ -352,9 +361,9 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str
352361
return null;
353362
}
354363

355-
private void reportFieldViolation(String violation, String where) {
356-
if (violation != null) {
357-
violations.add(new ForbiddenViolation(currentGroupId, violation, String.format(Locale.ENGLISH, "%s of '%s'", where, name), -1));
364+
private void reportFieldViolation(ViolationResult violationResult, String where) {
365+
if (violationResult != null) {
366+
violations.add(new ForbiddenViolation(currentGroupId, violationResult.message, String.format(Locale.ENGLISH, "%s of '%s'", where, name), -1, violationResult.severity));
358367
}
359368
}
360369
};
@@ -382,12 +391,12 @@ public MethodVisitor visitMethod(final int access, final String name, final Stri
382391
}
383392
}
384393

385-
private String checkMethodAccess(String owner, final Method method, final boolean callIsVirtual) {
394+
private ViolationResult checkMethodAccess(String owner, final Method method, final boolean callIsVirtual) {
386395
if (CLASS_CONSTRUCTOR_METHOD_NAME.equals(method.getName())) {
387396
// we don't check for violations on class constructors
388397
return null;
389398
}
390-
String violation = checkClassUse(owner, "class/interface", owner);
399+
ViolationResult violation = checkClassUse(owner, "class/interface", owner);
391400
if (violation != null) {
392401
return violation;
393402
}
@@ -405,7 +414,7 @@ private String checkMethodAccess(String owner, final Method method, final boolea
405414
}
406415
return visitAncestors(c, new AncestorVisitor() {
407416
@Override
408-
public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
417+
public ViolationResult visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
409418
final Method lookupMethod;
410419
if (c.signaturePolymorphicMethods.contains(method.getName())) {
411420
// 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
417426
return null;
418427
}
419428
// is we have a virtual call, look into superclasses, otherwise stop:
420-
final String notFoundRet = callIsVirtual ? null : AncestorVisitor.STOP;
429+
final ViolationResult notFoundRet = callIsVirtual ? null : AncestorVisitor.STOP;
421430
if (previousInRuntime && c.isNonPortableRuntime) {
422431
return notFoundRet; // something inside the JVM is extending internal class/interface
423432
}
424-
String violation = forbiddenSignatures.checkMethod(c.className, lookupMethod);
433+
ViolationResult violation = forbiddenSignatures.checkMethod(c.className, lookupMethod);
425434
if (violation != null) {
426435
return violation;
427436
}
@@ -437,8 +446,8 @@ public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAnces
437446
}, true, false /* JVM spec says: interfaces after superclasses */);
438447
}
439448

440-
private String checkFieldAccess(String owner, final String field) {
441-
String violation = checkClassUse(owner, "class/interface", owner);
449+
private ViolationResult checkFieldAccess(String owner, final String field) {
450+
ViolationResult violation = checkClassUse(owner, "class/interface", owner);
442451
if (violation != null) {
443452
return violation;
444453
}
@@ -453,15 +462,15 @@ private String checkFieldAccess(String owner, final String field) {
453462
}
454463
return visitAncestors(c, new AncestorVisitor() {
455464
@Override
456-
public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
465+
public ViolationResult visit(ClassMetadata c, String origName, boolean isInterfaceOfAncestor, boolean previousInRuntime) {
457466
if (!c.fields.contains(field)) {
458467
return null;
459468
}
460469
// we found the field: from now on we use STOP to exit, because fields are not virtual!
461470
if (previousInRuntime && c.isNonPortableRuntime) {
462471
return STOP; // something inside the JVM is extending internal class/interface
463472
}
464-
String violation = forbiddenSignatures.checkField(c.className, field);
473+
ViolationResult violation = forbiddenSignatures.checkField(c.className, field);
465474
if (violation != null) {
466475
return violation;
467476
}
@@ -478,7 +487,7 @@ public String visit(ClassMetadata c, String origName, boolean isInterfaceOfAnces
478487
}, true, true /* JVM spec says: superclasses after interfaces */);
479488
}
480489

481-
private String checkHandle(Handle handle, boolean checkLambdaHandle) {
490+
private ViolationResult checkHandle(Handle handle, boolean checkLambdaHandle) {
482491
switch (handle.getTag()) {
483492
case Opcodes.H_GETFIELD:
484493
case Opcodes.H_PUTFIELD:
@@ -503,7 +512,7 @@ private String checkHandle(Handle handle, boolean checkLambdaHandle) {
503512
return null;
504513
}
505514

506-
private String checkConstant(Object cst, boolean checkLambdaHandle) {
515+
private ViolationResult checkConstant(Object cst, boolean checkLambdaHandle) {
507516
if (cst instanceof Type) {
508517
return checkType((Type) cst);
509518
} else if (cst instanceof Handle) {
@@ -604,9 +613,9 @@ private String getHumanReadableMethodSignature() {
604613
return sb.toString();
605614
}
606615

607-
private void reportMethodViolation(String violation, String where) {
616+
private void reportMethodViolation(ViolationResult violation, String where) {
608617
if (violation != null) {
609-
violations.add(new ForbiddenViolation(currentGroupId, myself, violation, String.format(Locale.ENGLISH, "%s of '%s'", where, getHumanReadableMethodSignature()), lineNo));
618+
violations.add(new ForbiddenViolation(currentGroupId, myself, violation.message, String.format(Locale.ENGLISH, "%s of '%s'", where, getHumanReadableMethodSignature()), lineNo, violation.severity));
610619
}
611620
}
612621

@@ -642,9 +651,9 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str
642651
return null;
643652
}
644653

645-
private void reportRecordComponentViolation(String violation, String where) {
646-
if (violation != null) {
647-
violations.add(new ForbiddenViolation(currentGroupId, violation, String.format(Locale.ENGLISH, "%s of '%s'", where, name), -1));
654+
private void reportRecordComponentViolation(ViolationResult violationResult, String where) {
655+
if (violationResult != null) {
656+
violations.add(new ForbiddenViolation(currentGroupId, violationResult.message, String.format(Locale.ENGLISH, "%s of '%s'", where, name), -1, violationResult.severity));
648657
}
649658
}
650659
};

‎src/main/java/de/thetaphi/forbiddenapis/ForbiddenViolation.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import org.objectweb.asm.commons.Method;
2323

24+
import de.thetaphi.forbiddenapis.Checker.ViolationSeverity;
25+
2426
public final class ForbiddenViolation implements Comparable<ForbiddenViolation> {
2527

2628
/** Separator used to allow multiple description lines per violation. */
@@ -31,17 +33,19 @@ public final class ForbiddenViolation implements Comparable<ForbiddenViolation>
3133
public final String description;
3234
public final String locationInfo;
3335
public final int lineNo;
36+
public final ViolationSeverity severity;
3437

35-
ForbiddenViolation(int groupId, String description, String locationInfo, int lineNo) {
36-
this(groupId, null, description, locationInfo, lineNo);
38+
ForbiddenViolation(int groupId, String description, String locationInfo, int lineNo, ViolationSeverity severity) {
39+
this(groupId, null, description, locationInfo, lineNo, severity);
3740
}
3841

39-
ForbiddenViolation(int groupId, Method targetMethod, String description, String locationInfo, int lineNo) {
42+
ForbiddenViolation(int groupId, Method targetMethod, String description, String locationInfo, int lineNo, ViolationSeverity severity) {
4043
this.groupId = groupId;
4144
this.targetMethod = targetMethod;
4245
this.description = description;
4346
this.locationInfo = locationInfo;
4447
this.lineNo = lineNo;
48+
this.severity = severity;
4549
}
4650

4751
public void setGroupId(int groupId) {

‎src/main/java/de/thetaphi/forbiddenapis/Signatures.java

+63-12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.objectweb.asm.commons.Method;
4141

4242
import de.thetaphi.forbiddenapis.Checker.Option;
43+
import de.thetaphi.forbiddenapis.Checker.ViolationSeverity;
4344

4445
/** Utility class that is used to get an overview of all fields and implemented
4546
* methods of a class. It make the signatures available as Sets. */
@@ -94,21 +95,32 @@ private UnresolvableReporting(boolean reportClassNotFound) {
9495
/** set of patterns of forbidden classes */
9596
final Set<ClassPatternRule> classPatterns = new LinkedHashSet<>();
9697

98+
/** Key is used to lookup forbidden signature in following formats. Keys are generated by the corresponding
99+
* {@link #getKey(String)} (classes), {@link #getKey(String, Method)} (methods),
100+
* {@link #getKey(String, String)} (fields) call.
101+
*/
102+
final Map<String, ViolationSeverity> severityPerSignature = new HashMap<>();
103+
final Map<Pattern, ViolationSeverity> severityPerClassPattern = new HashMap<>();
104+
97105
/** if enabled, the bundled signature to enable heuristics for detection of non-portable runtime calls is used */
98106
private boolean forbidNonPortableRuntime = false;
99107

100108
/** number of files that were interpreted as signatures file. If 0, no (bundled) signatures files were added at all */
101109
private int numberOfFiles = 0;
102110

111+
/** determines default severity for violations if no severity on signature level is overridden. true = ERROR, false = WARNING */
112+
private boolean failOnViolation;
113+
103114
public Signatures(Checker checker) {
104-
this(checker, checker.logger, checker.options.contains(Option.IGNORE_SIGNATURES_OF_MISSING_CLASSES), checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES));
115+
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));
105116
}
106117

107-
public Signatures(RelatedClassLookup lookup, Logger logger, boolean ignoreSignaturesOfMissingClasses, boolean failOnUnresolvableSignatures) {
118+
public Signatures(RelatedClassLookup lookup, Logger logger, boolean ignoreSignaturesOfMissingClasses, boolean failOnUnresolvableSignatures, boolean failOnViolation) {
108119
this.lookup = lookup;
109120
this.logger = logger;
110121
this.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses;
111122
this.failOnUnresolvableSignatures = failOnUnresolvableSignatures;
123+
this.failOnViolation = failOnViolation;
112124
}
113125

114126
static String getKey(String internalClassName) {
@@ -337,6 +349,15 @@ public boolean noSignaturesFilesParsed() {
337349
return numberOfFiles == 0;
338350
}
339351

352+
public void setSignatureSeverity(String signature, ViolationSeverity severity) {
353+
// is it pattern or regular signature?
354+
if (AsmUtils.isGlob(signature)) {
355+
severityPerClassPattern.put(AsmUtils.glob2Pattern(signature), severity);
356+
} else {
357+
severityPerSignature.put(signature, severity);
358+
}
359+
}
360+
340361
/** Returns if bundled signature to enable heuristics for detection of non-portable runtime calls is used */
341362
public boolean isNonPortableRuntimeForbidden() {
342363
return this.forbidNonPortableRuntime;
@@ -346,33 +367,63 @@ private static String formatTypePrintout(String printout, String what) {
346367
return String.format(Locale.ENGLISH, "Forbidden %s use: %s", what, printout);
347368
}
348369

349-
public String checkType(Type type, String what) {
370+
/**
371+
* Represents a violation (usage of a forbidden method/field/class).
372+
* Encapsulates both message and severity.
373+
*/
374+
public static class ViolationResult {
375+
public final String message;
376+
public final ViolationSeverity severity;
377+
378+
public ViolationResult(String message, ViolationSeverity severity) {
379+
this.message = message;
380+
this.severity = severity;
381+
}
382+
}
383+
384+
public ViolationResult checkType(Type type, String what) {
350385
if (type.getSort() != Type.OBJECT) {
351386
return null; // we don't know this type, just pass!
352387
}
388+
final String key = getKey(type.getInternalName());
353389
final String printout = signatures.get(getKey(type.getInternalName()));
354390
if (printout != null) {
355-
return formatTypePrintout(printout, what);
391+
return new ViolationResult(formatTypePrintout(printout, what), getSeverityForKey(key));
356392
}
357393
final String binaryClassName = type.getClassName();
358394
for (final ClassPatternRule r : classPatterns) {
359395
if (r.matches(binaryClassName)) {
360-
return formatTypePrintout(r.getPrintout(binaryClassName), what);
396+
return new ViolationResult(formatTypePrintout(r.getPrintout(binaryClassName), what), getSeverityForClassName(binaryClassName));
361397
}
362398
}
363399
return null;
364400
}
365401

366-
public String checkMethod(String internalClassName, Method method) {
367-
final String printout = signatures.get(getKey(internalClassName, method));
368-
return (printout == null) ? null : "Forbidden method invocation: ".concat(printout);
402+
public ViolationResult checkMethod(String internalClassName, Method method) {
403+
final String key = getKey(internalClassName, method);
404+
final String printout = signatures.get(key);
405+
return (printout == null) ? null : new ViolationResult("Forbidden method invocation: ".concat(printout), getSeverityForKey(key));
369406
}
370407

371-
public String checkField(String internalClassName, String field) {
372-
final String printout = signatures.get(getKey(internalClassName, field));
373-
return (printout == null) ? null : "Forbidden field access: ".concat(printout);
408+
public ViolationResult checkField(String internalClassName, String field) {
409+
final String key = getKey(internalClassName, field);
410+
final String printout = signatures.get(key);
411+
return (printout == null) ? null : new ViolationResult("Forbidden field access: ".concat(printout), getSeverityForKey(key));
374412
}
375-
413+
414+
private ViolationSeverity getSeverityForKey(String key) {
415+
return severityPerSignature.getOrDefault(key, failOnViolation ? ViolationSeverity.ERROR : ViolationSeverity.WARNING);
416+
}
417+
418+
private ViolationSeverity getSeverityForClassName(String className) {
419+
for (final Map.Entry<Pattern, ViolationSeverity> e : severityPerClassPattern.entrySet()) {
420+
if (e.getKey().matcher(className).matches()) {
421+
return e.getValue();
422+
}
423+
}
424+
return failOnViolation ? ViolationSeverity.ERROR : ViolationSeverity.WARNING;
425+
}
426+
376427
public static String fixTargetVersion(String name) throws ParseException {
377428
final Matcher m = JDK_SIG_PATTERN.matcher(name);
378429
if (m.matches()) {

‎src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java

+28
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.LinkedHashSet;
3636
import java.util.List;
3737
import java.util.Locale;
38+
import java.util.Map;
3839
import java.util.Set;
3940

4041
import org.apache.maven.plugin.AbstractMojo;
@@ -116,6 +117,22 @@ public abstract class AbstractCheckMojo extends AbstractMojo implements Constant
116117
@Parameter(required = false)
117118
private String[] bundledSignatures;
118119

120+
/**
121+
* 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}.
122+
* In order to be effective the signature must be given in either {@link #bundledSignatures}, {@link #signaturesFiles}, {@link #signaturesArtifacts}, or {@link #signatures}.
123+
* @since 3.9
124+
*/
125+
@Parameter(required = false)
126+
private Set<String> signaturesWithSeverityWarn;
127+
128+
/**
129+
* 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}.
130+
* In order to be effective the signature must be given in either {@link #bundledSignatures}, {@link #signaturesFiles}, {@link #signaturesArtifacts}, or {@link #signatures}.
131+
* @since 3.9
132+
*/
133+
@Parameter(required = false)
134+
private Set<String> signaturesWithSeveritySuppress;
135+
119136
/**
120137
* Fail the build, if the bundled ASM library cannot read the class file format
121138
* of the runtime library or the runtime library cannot be discovered.
@@ -468,6 +485,17 @@ public void debug(String msg) {
468485
}
469486
}
470487

488+
if (signaturesWithSeverityWarn != null) {
489+
for (String s : signaturesWithSeverityWarn) {
490+
checker.setSignatureSeverity(s, Checker.ViolationSeverity.WARNING);
491+
}
492+
}
493+
if (signaturesWithSeveritySuppress != null) {
494+
for (String s : signaturesWithSeveritySuppress) {
495+
checker.setSignatureSeverity(s, Checker.ViolationSeverity.SUPPRESS);
496+
}
497+
}
498+
471499
try {
472500
checker.addClassesToCheck(classesDirectory, files);
473501
} catch (IOException ioe) {

‎src/test/antunit/TestFailOnViolation.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@
3535
java.awt.Color @ Color is disallowed, thats not bad, because ANT has no colors...
3636
java.lang.String @ You are crazy that you disallow strings
3737
</forbiddenapis>
38-
<au:assertLogContains level="error" text="java.lang.String [You are crazy that you disallow strings]"/>
38+
<au:assertLogContains level="warning" text="java.lang.String [You are crazy that you disallow strings]"/>
3939
</target>
4040
</project>

0 commit comments

Comments
 (0)
Please sign in to comment.