2222import java .util .List ;
2323import java .util .Map ;
2424import java .util .Set ;
25+ import java .util .stream .Stream ;
2526
2627import static java .util .Map .entry ;
2728import static org .elasticsearch .entitlement .runtime .policy .PolicyManager .ALL_UNNAMED ;
3738@ ESTestCase .WithoutSecurityManager
3839public class PolicyManagerTests extends ESTestCase {
3940
41+ private static final Module NO_ENTITLEMENTS_MODULE = null ;
42+
4043 public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule () {
4144 var policyManager = new PolicyManager (
4245 createEmptyTestServerPolicy (),
4346 Map .of ("plugin1" , createPluginPolicy ("plugin.module" )),
44- c -> "plugin1"
47+ c -> "plugin1" ,
48+ NO_ENTITLEMENTS_MODULE
4549 );
4650
4751 // Any class from the current module (unnamed) will do
@@ -62,7 +66,7 @@ public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule() {
6266 }
6367
6468 public void testGetEntitlementsThrowsOnMissingPolicyForPlugin () {
65- var policyManager = new PolicyManager (createEmptyTestServerPolicy (), Map .of (), c -> "plugin1" );
69+ var policyManager = new PolicyManager (createEmptyTestServerPolicy (), Map .of (), c -> "plugin1" , NO_ENTITLEMENTS_MODULE );
6670
6771 // Any class from the current module (unnamed) will do
6872 var callerClass = this .getClass ();
@@ -82,7 +86,7 @@ public void testGetEntitlementsThrowsOnMissingPolicyForPlugin() {
8286 }
8387
8488 public void testGetEntitlementsFailureIsCached () {
85- var policyManager = new PolicyManager (createEmptyTestServerPolicy (), Map .of (), c -> "plugin1" );
89+ var policyManager = new PolicyManager (createEmptyTestServerPolicy (), Map .of (), c -> "plugin1" , NO_ENTITLEMENTS_MODULE );
8690
8791 // Any class from the current module (unnamed) will do
8892 var callerClass = this .getClass ();
@@ -103,7 +107,8 @@ public void testGetEntitlementsReturnsEntitlementsForPluginUnnamedModule() {
103107 var policyManager = new PolicyManager (
104108 createEmptyTestServerPolicy (),
105109 Map .ofEntries (entry ("plugin2" , createPluginPolicy (ALL_UNNAMED ))),
106- c -> "plugin2"
110+ c -> "plugin2" ,
111+ NO_ENTITLEMENTS_MODULE
107112 );
108113
109114 // Any class from the current module (unnamed) will do
@@ -115,7 +120,7 @@ public void testGetEntitlementsReturnsEntitlementsForPluginUnnamedModule() {
115120 }
116121
117122 public void testGetEntitlementsThrowsOnMissingPolicyForServer () throws ClassNotFoundException {
118- var policyManager = new PolicyManager (createTestServerPolicy ("example" ), Map .of (), c -> null );
123+ var policyManager = new PolicyManager (createTestServerPolicy ("example" ), Map .of (), c -> null , NO_ENTITLEMENTS_MODULE );
119124
120125 // Tests do not run modular, so we cannot use a server class.
121126 // But we know that in production code the server module and its classes are in the boot layer.
@@ -138,7 +143,7 @@ public void testGetEntitlementsThrowsOnMissingPolicyForServer() throws ClassNotF
138143 }
139144
140145 public void testGetEntitlementsReturnsEntitlementsForServerModule () throws ClassNotFoundException {
141- var policyManager = new PolicyManager (createTestServerPolicy ("jdk.httpserver" ), Map .of (), c -> null );
146+ var policyManager = new PolicyManager (createTestServerPolicy ("jdk.httpserver" ), Map .of (), c -> null , NO_ENTITLEMENTS_MODULE );
142147
143148 // Tests do not run modular, so we cannot use a server class.
144149 // But we know that in production code the server module and its classes are in the boot layer.
@@ -155,12 +160,13 @@ public void testGetEntitlementsReturnsEntitlementsForServerModule() throws Class
155160 public void testGetEntitlementsReturnsEntitlementsForPluginModule () throws IOException , ClassNotFoundException {
156161 final Path home = createTempDir ();
157162
158- Path jar = creteMockPluginJar (home );
163+ Path jar = createMockPluginJar (home );
159164
160165 var policyManager = new PolicyManager (
161166 createEmptyTestServerPolicy (),
162167 Map .of ("mock-plugin" , createPluginPolicy ("org.example.plugin" )),
163- c -> "mock-plugin"
168+ c -> "mock-plugin" ,
169+ NO_ENTITLEMENTS_MODULE
164170 );
165171
166172 var layer = createLayerForJar (jar , "org.example.plugin" );
@@ -179,7 +185,8 @@ public void testGetEntitlementsResultIsCached() {
179185 var policyManager = new PolicyManager (
180186 createEmptyTestServerPolicy (),
181187 Map .ofEntries (entry ("plugin2" , createPluginPolicy (ALL_UNNAMED ))),
182- c -> "plugin2"
188+ c -> "plugin2" ,
189+ NO_ENTITLEMENTS_MODULE
183190 );
184191
185192 // Any class from the current module (unnamed) will do
@@ -197,6 +204,73 @@ public void testGetEntitlementsResultIsCached() {
197204 assertThat (entitlementsAgain , sameInstance (cachedResult ));
198205 }
199206
207+ public void testRequestingModuleFastPath () throws IOException , ClassNotFoundException {
208+ var callerClass = makeClassInItsOwnModule ();
209+ assertEquals (callerClass .getModule (), policyManagerWithEntitlementsModule (NO_ENTITLEMENTS_MODULE ).requestingModule (callerClass ));
210+ }
211+
212+ public void testRequestingModuleWithStackWalk () throws IOException , ClassNotFoundException {
213+ var requestingClass = makeClassInItsOwnModule ();
214+ var runtimeClass = makeClassInItsOwnModule (); // A class in the entitlements library itself
215+ var ignorableClass = makeClassInItsOwnModule ();
216+ var systemClass = Object .class ;
217+
218+ var policyManager = policyManagerWithEntitlementsModule (runtimeClass .getModule ());
219+
220+ var requestingModule = requestingClass .getModule ();
221+
222+ assertEquals (
223+ "Skip one system frame" ,
224+ requestingModule ,
225+ policyManager .findRequestingModule (Stream .of (systemClass , requestingClass , ignorableClass )).orElse (null )
226+ );
227+ assertEquals (
228+ "Skip multiple system frames" ,
229+ requestingModule ,
230+ policyManager .findRequestingModule (Stream .of (systemClass , systemClass , systemClass , requestingClass , ignorableClass ))
231+ .orElse (null )
232+ );
233+ assertEquals (
234+ "Skip system frame between runtime frames" ,
235+ requestingModule ,
236+ policyManager .findRequestingModule (Stream .of (runtimeClass , systemClass , runtimeClass , requestingClass , ignorableClass ))
237+ .orElse (null )
238+ );
239+ assertEquals (
240+ "Skip runtime frame between system frames" ,
241+ requestingModule ,
242+ policyManager .findRequestingModule (Stream .of (systemClass , runtimeClass , systemClass , requestingClass , ignorableClass ))
243+ .orElse (null )
244+ );
245+ assertEquals (
246+ "No system frames" ,
247+ requestingModule ,
248+ policyManager .findRequestingModule (Stream .of (requestingClass , ignorableClass )).orElse (null )
249+ );
250+ assertEquals (
251+ "Skip runtime frames up to the first system frame" ,
252+ requestingModule ,
253+ policyManager .findRequestingModule (Stream .of (runtimeClass , runtimeClass , systemClass , requestingClass , ignorableClass ))
254+ .orElse (null )
255+ );
256+ assertThrows (
257+ "Non-modular caller frames are not supported" ,
258+ NullPointerException .class ,
259+ () -> policyManager .findRequestingModule (Stream .of (systemClass , null ))
260+ );
261+ }
262+
263+ private static Class <?> makeClassInItsOwnModule () throws IOException , ClassNotFoundException {
264+ final Path home = createTempDir ();
265+ Path jar = createMockPluginJar (home );
266+ var layer = createLayerForJar (jar , "org.example.plugin" );
267+ return layer .findLoader ("org.example.plugin" ).loadClass ("q.B" );
268+ }
269+
270+ private static PolicyManager policyManagerWithEntitlementsModule (Module entitlementsModule ) {
271+ return new PolicyManager (createEmptyTestServerPolicy (), Map .of (), c -> "test" , entitlementsModule );
272+ }
273+
200274 private static Policy createEmptyTestServerPolicy () {
201275 return new Policy ("server" , List .of ());
202276 }
@@ -219,7 +293,7 @@ private static Policy createPluginPolicy(String... pluginModules) {
219293 );
220294 }
221295
222- private static Path creteMockPluginJar (Path home ) throws IOException {
296+ private static Path createMockPluginJar (Path home ) throws IOException {
223297 Path jar = home .resolve ("mock-plugin.jar" );
224298
225299 Map <String , CharSequence > sources = Map .ofEntries (
0 commit comments