49
49
import java .util .Objects ;
50
50
import java .util .Set ;
51
51
import java .util .function .BooleanSupplier ;
52
+ import java .util .function .Function ;
52
53
import java .util .function .Predicate ;
53
54
import java .util .stream .Collectors ;
54
55
55
56
import com .oracle .svm .hosted .code .FactoryMethod ;
57
+ import com .oracle .svm .util .LogUtils ;
56
58
import org .graalvm .nativeimage .ImageSingletons ;
57
59
import org .graalvm .nativeimage .hosted .Feature ;
58
60
import org .graalvm .polyglot .io .FileSystem ;
@@ -110,6 +112,12 @@ public class PermissionsFeature implements Feature {
110
112
111
113
private static final String CONFIG = "truffle-language-permissions-config.json" ;
112
114
115
+ public enum ActionKind {
116
+ Ignore ,
117
+ Warn ,
118
+ Throw
119
+ }
120
+
113
121
public static class Options {
114
122
@ Option (help = "Path to file where to store report of Truffle language privilege access." )//
115
123
public static final HostedOptionKey <String > TruffleTCKPermissionsReportFile = new HostedOptionKey <>(null );
@@ -124,6 +132,13 @@ public static class Options {
124
132
125
133
@ Option (help = "Maximum number of erroneous privileged accesses reported." , type = OptionType .Expert )//
126
134
public static final HostedOptionKey <Integer > TruffleTCKPermissionsMaxErrors = new HostedOptionKey <>(100 );
135
+
136
+ @ Option (help = {"Specifies how unused methods in the language allow list should be handled." ,
137
+ "Available options are:" ,
138
+ " \" Ignore\" : Do not report unused methods in the allow list." ,
139
+ " \" Warn\" : Log a warning message to stderr." ,
140
+ " \" Throw\" (default): Throw an exception and abort the native-image build process." }, type = OptionType .Expert )//
141
+ public static final HostedOptionKey <ActionKind > TruffleTCKUnusedAllowListEntriesAction = new HostedOptionKey <>(ActionKind .Throw );
127
142
}
128
143
129
144
/**
@@ -178,17 +193,22 @@ public boolean getAsBoolean() {
178
193
/**
179
194
* Methods which should not be found.
180
195
*/
181
- private Set <BaseMethodNode > deniedMethods = new HashSet <>();
196
+ private final Set <BaseMethodNode > deniedMethods = new HashSet <>();
182
197
183
198
/**
184
199
* Path to store report into.
185
200
*/
186
201
private Path reportFilePath ;
187
202
188
203
/**
189
- * Methods which are allowed to do privileged calls without being reported.
204
+ * JDK methods which are allowed to do privileged calls without being reported.
190
205
*/
191
- private Set <? extends BaseMethodNode > whiteList ;
206
+ private Set <? extends BaseMethodNode > platformAllowList ;
207
+
208
+ /**
209
+ * Language methods which are allowed to do privileged calls without being reported.
210
+ */
211
+ private Map <BaseMethodNode , Boolean > languageAllowList ;
192
212
193
213
private Set <CallGraphFilter > contextFilters ;
194
214
@@ -229,12 +249,13 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
229
249
new SafeServiceLoaderRecognizer (bb , accessImpl .getImageClassLoader ()), new SafeSetThreadNameRecognizer (bb ));
230
250
231
251
/*
232
- * Ensure methods which are either deniedMethods or on the whiteList are never inlined into
252
+ * Ensure methods which are either deniedMethods or on the allow list are never inlined into
233
253
* methods. These methods are important for identifying violations.
234
254
*/
235
255
Set <AnalysisMethod > preventInlineBeforeAnalysis = new HashSet <>();
236
256
deniedMethods .stream ().map (BaseMethodNode ::getMethod ).forEach (preventInlineBeforeAnalysis ::add );
237
- whiteList .stream ().map (BaseMethodNode ::getMethod ).forEach (preventInlineBeforeAnalysis ::add );
257
+ platformAllowList .stream ().map (BaseMethodNode ::getMethod ).forEach (preventInlineBeforeAnalysis ::add );
258
+ languageAllowList .keySet ().stream ().map (BaseMethodNode ::getMethod ).forEach (preventInlineBeforeAnalysis ::add );
238
259
contextFilters .stream ().map (CallGraphFilter ::getInspectedMethods ).forEach (preventInlineBeforeAnalysis ::addAll );
239
260
240
261
accessImpl .getHostVM ().registerNeverInlineTrivialHandler ((caller , callee ) -> {
@@ -257,14 +278,19 @@ private void initializeDeniedMethods(FeatureImpl.BeforeAnalysisAccessImpl access
257
278
if (sunMiscUnsafe != null ) {
258
279
inlinedUnsafeCall = new InlinedUnsafeMethodNode (bb .getMetaAccess ().lookupJavaType (sunMiscUnsafe ));
259
280
}
260
- WhiteListParser parser = new WhiteListParser (accessImpl .getImageClassLoader (), bb );
261
- ConfigurationParserUtils .parseAndRegisterConfigurations (parser ,
262
- accessImpl .getImageClassLoader (),
263
- ClassUtil .getUnqualifiedName (getClass ()),
264
- Options .TruffleTCKPermissionsExcludeFiles ,
265
- new ResourceAsOptionDecorator (getClass ().getPackage ().getName ().replace ('.' , '/' ) + "/resources/jre.json" ),
266
- CONFIG );
267
- whiteList = parser .getLoadedWhiteList ();
281
+ String featureName = ClassUtil .getUnqualifiedName (getClass ());
282
+ AllowListParser parser = new AllowListParser (accessImpl .getImageClassLoader (), bb );
283
+ ConfigurationParserUtils .parseAndRegisterConfigurations (parser , accessImpl .getImageClassLoader (), featureName ,
284
+ CONFIG ,
285
+ List .of (),
286
+ List .of (getClass ().getPackage ().getName ().replace ('.' , '/' ) + "/resources/jre.json" ));
287
+ platformAllowList = parser .getLoadedAllowList ();
288
+ parser = new AllowListParser (accessImpl .getImageClassLoader (), bb );
289
+ ConfigurationParserUtils .parseAndRegisterConfigurations (parser , accessImpl .getImageClassLoader (), featureName ,
290
+ CONFIG ,
291
+ Options .TruffleTCKPermissionsExcludeFiles .getValue ().values (),
292
+ List .of ());
293
+ languageAllowList = parser .getLoadedAllowList ().stream ().collect (Collectors .toMap (Function .identity (), key -> false ));
268
294
deniedMethods .addAll (findMethods (bb , SecurityManager .class , (m ) -> m .getName ().startsWith ("check" )));
269
295
if (sunMiscUnsafe != null ) {
270
296
deniedMethods .addAll (findMethods (bb , sunMiscUnsafe , ModifiersProvider ::isPublic ));
@@ -321,6 +347,21 @@ public void afterAnalysis(AfterAnalysisAccess access) {
321
347
pw .print (builder );
322
348
});
323
349
}
350
+ List <BaseMethodNode > unusedLanguageAllowListEntries = languageAllowList .entrySet ().stream ().filter ((e ) -> !e .getValue ()).map (Map .Entry ::getKey ).toList ();
351
+ if (!unusedLanguageAllowListEntries .isEmpty ()) {
352
+ StringBuilder errorMessageBuilder = new StringBuilder (
353
+ "The following methods in the language allow list were not statically reachable during points-to analysis. " + "Please review and remove them from the allow list:\n " );
354
+ for (BaseMethodNode unused : unusedLanguageAllowListEntries ) {
355
+ errorMessageBuilder .append (" - " ).append (unused .getMethod ().format ("%H.%n(%p)" )).append ("\n " );
356
+ }
357
+ switch (Options .TruffleTCKUnusedAllowListEntriesAction .getValue ()) {
358
+ case Ignore -> {
359
+ }
360
+ case Warn -> LogUtils .warning ("[%s] %s" , ClassUtil .getUnqualifiedName (getClass ()), errorMessageBuilder );
361
+ case Throw -> throw UserError .abort (errorMessageBuilder .toString ());
362
+ default -> throw new AssertionError (Options .TruffleTCKUnusedAllowListEntriesAction .getValue ());
363
+ }
364
+ }
324
365
}
325
366
}
326
367
@@ -533,7 +574,7 @@ private int collectViolations(
533
574
if (isSafeClass (mNode )) {
534
575
return numReports ;
535
576
}
536
- // The denied method can be excluded by a white list
577
+ // The denied method can be excluded by a allow list
537
578
if (isExcludedClass (mNode )) {
538
579
return numReports ;
539
580
}
@@ -618,12 +659,15 @@ private static boolean isClassInPackage(String javaName, Collection<? extends St
618
659
}
619
660
620
661
/**
621
- * Tests if the given {@link BaseMethodNode} is excluded by white list.
662
+ * Tests if the given {@link BaseMethodNode} is excluded by allow list.
622
663
*
623
664
* @param methodNode the {@link BaseMethodNode} to check
624
665
*/
625
666
private boolean isExcludedClass (BaseMethodNode methodNode ) {
626
- return whiteList .contains (methodNode );
667
+ if (platformAllowList .contains (methodNode )) {
668
+ return true ;
669
+ }
670
+ return languageAllowList .computeIfPresent (methodNode , (n , v ) -> true ) != null ;
627
671
}
628
672
629
673
/**
@@ -783,7 +827,7 @@ public boolean test(BaseMethodNode methodNode, BaseMethodNode callerNode, List<B
783
827
if (args .isEmpty ()) {
784
828
return false ;
785
829
}
786
- ValueNode arg0 = args .get ( 0 );
830
+ ValueNode arg0 = args .getFirst ( );
787
831
ResolvedJavaType newType = null ;
788
832
if (arg0 instanceof NewInstanceNode newInstanceNode ) {
789
833
newType = newInstanceNode .instanceClass ();
@@ -938,7 +982,7 @@ public boolean test(BaseMethodNode methodNode, BaseMethodNode callerNode, List<B
938
982
for (Invoke invoke : graph .getInvokes ()) {
939
983
if (method .equals (invoke .callTarget ().targetMethod ())) {
940
984
NodeInputList <ValueNode > args = invoke .callTarget ().arguments ();
941
- ValueNode arg0 = args .get ( 0 );
985
+ ValueNode arg0 = args .getFirst ( );
942
986
boolean isTruffleThread = false ;
943
987
if (arg0 instanceof PiNode piNode ) {
944
988
arg0 = piNode .getOriginalNode ();
@@ -962,16 +1006,6 @@ public Collection<AnalysisMethod> getInspectedMethods() {
962
1006
}
963
1007
}
964
1008
965
- /**
966
- * Options facade for a resource containing the JRE white list.
967
- */
968
- private static final class ResourceAsOptionDecorator extends HostedOptionKey <AccumulatingLocatableMultiOptionValue .Strings > {
969
-
970
- ResourceAsOptionDecorator (String defaultValue ) {
971
- super (AccumulatingLocatableMultiOptionValue .Strings .buildWithDefaults (defaultValue ));
972
- }
973
- }
974
-
975
1009
abstract static class BaseMethodNode {
976
1010
abstract StackTraceElement asStackTraceElement ();
977
1011
0 commit comments