54
54
/**
55
55
* Forbidden APIs checker class.
56
56
*/
57
- public final class Checker implements RelatedClassLookup {
57
+ public final class Checker implements RelatedClassLookup , Constants {
58
58
59
59
public static enum Option {
60
- INTERNAL_RUNTIME_FORBIDDEN ,
61
60
FAIL_ON_MISSING_CLASSES ,
62
61
FAIL_ON_VIOLATION ,
63
62
FAIL_ON_UNRESOLVABLE_SIGNATURES
@@ -66,27 +65,28 @@ public static enum Option {
66
65
public final boolean isSupportedJDK ;
67
66
68
67
private final long start ;
69
-
68
+ private final NavigableSet <String > runtimePaths ;
69
+
70
70
final Logger logger ;
71
71
72
- final NavigableSet <String > runtimePaths ;
73
-
74
72
final ClassLoader loader ;
75
- final java .lang .reflect .Method method_Class_getModule , method_Module_getResourceAsStream ;
73
+ final java .lang .reflect .Method method_Class_getModule , method_Module_getResourceAsStream , method_Module_getName ;
76
74
final EnumSet <Option > options ;
77
75
78
76
// key is the binary name (dotted):
79
77
final Map <String ,ClassSignature > classesToCheck = new HashMap <String ,ClassSignature >();
80
78
// key is the binary name (dotted):
81
79
final Map <String ,ClassSignature > classpathClassCache = new HashMap <String ,ClassSignature >();
82
80
81
+ // if enabled, the bundled signature to enable heuristics for detection of non-portable runtime calls is used:
82
+ private boolean forbidNonPortableRuntime = false ;
83
83
// key is the internal name (slashed), followed by \000 and the field name:
84
84
final Map <String ,String > forbiddenFields = new HashMap <String ,String >();
85
85
// key is the internal name (slashed), followed by \000 and the method signature:
86
86
final Map <String ,String > forbiddenMethods = new HashMap <String ,String >();
87
87
// key is the internal name (slashed):
88
88
final Map <String ,String > forbiddenClasses = new HashMap <String ,String >();
89
- // key is pattern to binary class name :
89
+ // set of patterns of forbidden classes :
90
90
final Set <ClassPatternRule > forbiddenClassPatterns = new LinkedHashSet <ClassPatternRule >();
91
91
// descriptors (not internal names) of all annotations that suppress:
92
92
final Set <String > suppressAnnotations = new LinkedHashSet <String >();
@@ -129,19 +129,22 @@ public Checker(Logger logger, ClassLoader loader, EnumSet<Option> options) {
129
129
130
130
boolean isSupportedJDK = false ;
131
131
132
- // first try Java 9 mdoule system (Jigsaw)
132
+ // first try Java 9 module system (Jigsaw)
133
133
// Please note: This code is not guaranteed to work with final Java 9 version. This is just for testing!
134
- java .lang .reflect .Method method_Class_getModule , method_Module_getResourceAsStream ;
134
+ java .lang .reflect .Method method_Class_getModule , method_Module_getResourceAsStream , method_Module_getName ;
135
135
try {
136
136
method_Class_getModule = Class .class .getMethod ("getModule" );
137
137
method_Module_getResourceAsStream = method_Class_getModule
138
138
.getReturnType ().getMethod ("getResourceAsStream" , String .class );
139
+ method_Module_getName = method_Class_getModule
140
+ .getReturnType ().getMethod ("getName" );
139
141
isSupportedJDK = true ;
140
142
} catch (NoSuchMethodException e ) {
141
- method_Class_getModule = method_Module_getResourceAsStream = null ;
143
+ method_Class_getModule = method_Module_getResourceAsStream = method_Module_getName = null ;
142
144
}
143
145
this .method_Class_getModule = method_Class_getModule ;
144
146
this .method_Module_getResourceAsStream = method_Module_getResourceAsStream ;
147
+ this .method_Module_getName = method_Module_getName ;
145
148
146
149
final NavigableSet <String > runtimePaths = new TreeSet <String >();
147
150
@@ -222,17 +225,30 @@ public Checker(Logger logger, ClassLoader loader, EnumSet<Option> options) {
222
225
* This code is not guaranteed to work with final Java 9 version.
223
226
* This is just for testing!
224
227
**/
225
- private InputStream getBytecodeFromJigsaw (String classname ) {
226
- if (method_Class_getModule == null || method_Module_getResourceAsStream == null ) {
228
+ private ClassSignature loadClassFromJigsaw (String classname ) throws IOException {
229
+ if (method_Class_getModule == null || method_Module_getResourceAsStream == null || method_Module_getName == null ) {
227
230
return null ; // not Java 9 JIGSAW
228
231
}
232
+
233
+ final InputStream in ;
234
+ final String moduleName ;
229
235
try {
230
236
final Class <?> clazz = Class .forName (classname , false , loader );
231
237
final Object module = method_Class_getModule .invoke (clazz );
232
- return (InputStream ) method_Module_getResourceAsStream .invoke (module , AsmUtils .getClassResourceName (classname ));
238
+ moduleName = (String ) method_Module_getName .invoke (module );
239
+ in = (InputStream ) method_Module_getResourceAsStream .invoke (module , AsmUtils .getClassResourceName (classname ));
240
+ if (in == null ) {
241
+ return null ;
242
+ }
233
243
} catch (Exception e ) {
234
244
return null ; // not found
235
245
}
246
+
247
+ try {
248
+ return new ClassSignature (new ClassReader (in ), AsmUtils .isRuntimeModule (moduleName ), false );
249
+ } finally {
250
+ in .close ();
251
+ }
236
252
}
237
253
238
254
private boolean isRuntimePath (URL url ) throws IOException {
@@ -260,7 +276,7 @@ private boolean isRuntimeClass(URLConnection conn) throws IOException {
260
276
// all 'jrt:' URLs refer to a module in the Java 9+ runtime (see http://openjdk.java.net/jeps/220)
261
277
// This may still be different with module system. We support both variants for now.
262
278
// Please note: This code is not guaranteed to work with final Java 9 version. This is just for testing!
263
- return true ;
279
+ return AsmUtils . isRuntimeModule ( AsmUtils . getModuleName ( url )) ;
264
280
}
265
281
return false ;
266
282
}
@@ -287,14 +303,9 @@ private ClassSignature getClassFromClassLoader(final String clazz) throws ClassN
287
303
}
288
304
return c ;
289
305
} else {
290
- final InputStream in = getBytecodeFromJigsaw (clazz );
291
- if (in != null ) {
292
- try {
293
- // we mark it as runtime class because it was derived from the module system:
294
- classpathClassCache .put (clazz , c = new ClassSignature (new ClassReader (in ), true , false ));
295
- } finally {
296
- in .close ();
297
- }
306
+ final ClassSignature jigsawCl = loadClassFromJigsaw (clazz );
307
+ if (jigsawCl != null ) {
308
+ classpathClassCache .put (clazz , c = jigsawCl );
298
309
return c ;
299
310
}
300
311
}
@@ -336,7 +347,8 @@ public ClassSignature lookupRelatedClass(String internalName) {
336
347
337
348
/** Adds the method signature to the list of disallowed methods. The Signature is checked against the given ClassLoader. */
338
349
private void addSignature (final String line , final String defaultMessage , final UnresolvableReporting report ) throws ParseException ,IOException {
339
- final String clazz , field , signature , message ;
350
+ final String clazz , field , signature ;
351
+ String message = null ;
340
352
final Method method ;
341
353
int p = line .indexOf ('@' );
342
354
if (p >= 0 ) {
@@ -371,15 +383,17 @@ private void addSignature(final String line, final String defaultMessage, final
371
383
method = null ;
372
384
field = null ;
373
385
}
386
+ if (message != null && message .isEmpty ()) {
387
+ message = null ;
388
+ }
374
389
// create printout message:
375
- final String printout = (message != null && message .length () > 0 ) ?
376
- (signature + " [" + message + "]" ) : signature ;
390
+ final String printout = (message != null ) ? (signature + " [" + message + "]" ) : signature ;
377
391
// check class & method/field signature, if it is really existent (in classpath), but we don't really load the class into JVM:
378
392
if (AsmUtils .isGlob (clazz )) {
379
393
if (method != null || field != null ) {
380
394
throw new ParseException (String .format (Locale .ENGLISH , "Class level glob pattern cannot be combined with methods/fields: %s" , signature ));
381
395
}
382
- forbiddenClassPatterns .add (new ClassPatternRule (clazz , printout ));
396
+ forbiddenClassPatterns .add (new ClassPatternRule (clazz , message ));
383
397
} else {
384
398
final ClassSignature c ;
385
399
try {
@@ -419,14 +433,19 @@ private void addSignature(final String line, final String defaultMessage, final
419
433
}
420
434
421
435
/** Reads a list of bundled API signatures from classpath. */
422
- public void parseBundledSignatures (String name , String jdkTargetVersion ) throws IOException ,ParseException {
423
- parseBundledSignatures (name , jdkTargetVersion , true );
436
+ public void addBundledSignatures (String name , String jdkTargetVersion ) throws IOException ,ParseException {
437
+ addBundledSignatures (name , jdkTargetVersion , true );
424
438
}
425
439
426
- private void parseBundledSignatures (String name , String jdkTargetVersion , boolean logging ) throws IOException ,ParseException {
440
+ private void addBundledSignatures (String name , String jdkTargetVersion , boolean logging ) throws IOException ,ParseException {
427
441
if (!name .matches ("[A-Za-z0-9\\ -\\ .]+" )) {
428
442
throw new ParseException ("Invalid bundled signature reference: " + name );
429
443
}
444
+ if (BS_JDK_NONPORTABLE .equals (name )) {
445
+ if (logging ) logger .info ("Reading bundled API signatures: " + name );
446
+ forbidNonPortableRuntime = true ;
447
+ return ;
448
+ }
430
449
// use Checker.class hardcoded (not getClass) so we have a fixed package name:
431
450
InputStream in = Checker .class .getResourceAsStream ("signatures/" + name + ".txt" );
432
451
// automatically expand the compiler version in here (for jdk-* signatures without version):
@@ -487,7 +506,7 @@ private void parseSignaturesFile(Reader reader, boolean isBundled) throws IOExce
487
506
if (line .startsWith ("@" )) {
488
507
if (isBundled && line .startsWith (BUNDLED_PREFIX )) {
489
508
final String name = line .substring (BUNDLED_PREFIX .length ()).trim ();
490
- parseBundledSignatures (name , null , false );
509
+ addBundledSignatures (name , null , false );
491
510
} else if (line .startsWith (DEFAULT_MESSAGE_PREFIX )) {
492
511
defaultMessage = line .substring (DEFAULT_MESSAGE_PREFIX .length ()).trim ();
493
512
if (defaultMessage .length () == 0 ) defaultMessage = null ;
@@ -549,7 +568,11 @@ public void addClassesToCheck(File basedir, String... relativeNames) throws IOEx
549
568
}
550
569
551
570
public boolean hasNoSignatures () {
552
- return forbiddenMethods .isEmpty () && forbiddenFields .isEmpty () && forbiddenClasses .isEmpty () && forbiddenClassPatterns .isEmpty () && (!options .contains (Option .INTERNAL_RUNTIME_FORBIDDEN ));
571
+ return 0 == forbiddenMethods .size () +
572
+ forbiddenFields .size () +
573
+ forbiddenClasses .size () +
574
+ forbiddenClassPatterns .size () +
575
+ (forbidNonPortableRuntime ? 1 : 0 );
553
576
}
554
577
555
578
/** Adds the given annotation class for suppressing errors. */
@@ -565,7 +588,7 @@ public void addSuppressAnnotation(String annoName) {
565
588
/** Parses a class and checks for valid method invocations */
566
589
private int checkClass (final ClassReader reader , Pattern suppressAnnotationsPattern ) {
567
590
final String className = Type .getObjectType (reader .getClassName ()).getClassName ();
568
- final ClassScanner scanner = new ClassScanner (this , forbiddenClasses , forbiddenClassPatterns , forbiddenMethods , forbiddenFields , suppressAnnotationsPattern , options . contains ( Option . INTERNAL_RUNTIME_FORBIDDEN ) );
591
+ final ClassScanner scanner = new ClassScanner (this , forbiddenClasses , forbiddenClassPatterns , forbiddenMethods , forbiddenFields , suppressAnnotationsPattern , forbidNonPortableRuntime );
569
592
reader .accept (scanner , ClassReader .SKIP_FRAMES );
570
593
final List <ForbiddenViolation > violations = scanner .getSortedViolations ();
571
594
final Pattern splitter = Pattern .compile (Pattern .quote (ForbiddenViolation .SEPARATOR ));
0 commit comments