Skip to content

Commit 2a210f3

Browse files
committed
Allow to set severity per signature
This closes #252 and #219
1 parent 535ecf7 commit 2a210f3

File tree

6 files changed

+184
-65
lines changed

6 files changed

+184
-65
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

+55-46
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);
249254
sb.append(v);
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
};

0 commit comments

Comments
 (0)