1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @requires vm.compMode != "Xcomp" 27 * @modules java.base/jdk.internal.misc 28 * java.base/sun.security.x509 29 * java.activation 30 * @library /test/lib /lib/testlibrary modules 31 * @build IllegalAccessTest TryAccess JarUtils 32 * jdk.test.lib.compiler.CompilerUtils 33 * jdk.testlibrary.* 34 * @build m/* 35 * @run testng/othervm/timeout=180 IllegalAccessTest 36 * @summary Basic test for java --illegal-access=$VALUE 37 */ 38 39 import java.nio.file.Files; 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.jar.Attributes; 45 import java.util.jar.Manifest; 46 import java.util.stream.Stream; 47 48 import jdk.test.lib.compiler.CompilerUtils; 49 import jdk.testlibrary.ProcessTools; 50 import jdk.testlibrary.OutputAnalyzer; 51 52 import org.testng.annotations.DataProvider; 53 import org.testng.annotations.Test; 54 import static org.testng.Assert.*; 55 56 /** 57 * Basic test of --illegal-access=value to deny or permit access to JDK internals. 58 */ 59 60 @Test 61 public class IllegalAccessTest { 62 63 static final String TEST_SRC = System.getProperty("test.src"); 64 static final String TEST_CLASSES = System.getProperty("test.classes"); 65 static final String MODULE_PATH = System.getProperty("jdk.module.path"); 66 67 /** 68 * Represents the expected result of a test. 69 */ 70 static final class Result { 71 private final boolean success; 72 private final List<String> expectedOutput = new ArrayList<>(); 73 private final List<String> notExpectedOutput = new ArrayList<>(); 74 75 Result(boolean success) { 76 this.success = success; 77 } 78 79 Result expect(String msg) { 80 expectedOutput.add(msg); 81 return this; 82 } 83 84 Result doNotExpect(String msg) { 85 notExpectedOutput.add(msg); 86 return this; 87 } 88 89 boolean shouldSucceed() { 90 return success; 91 } 92 93 Stream<String> expectedOutput() { 94 return expectedOutput.stream(); 95 } 96 97 Stream<String> notExpectedOutput() { 98 return notExpectedOutput.stream(); 99 } 100 101 @Override 102 public String toString() { 103 String s = (success) ? "success" : "failure"; 104 for (String msg : expectedOutput) { 105 s += "/" + msg; 106 } 107 return s; 108 } 109 } 110 111 static Result success() { 112 return new Result(true); 113 } 114 115 static Result successNoWarning() { 116 return success().doNotExpect("WARNING"); 117 } 118 119 static Result successWithWarning() { 120 return success().expect("WARNING"); 121 } 122 123 static Result fail(String expectedOutput) { 124 return new Result(false).expect(expectedOutput).doNotExpect("WARNING"); 125 } 126 127 @DataProvider(name = "denyCases") 128 public Object[][] denyCases() { 129 return new Object[][] { 130 { "accessPublicClassNonExportedPackage", fail("IllegalAccessError") }, 131 { "accessPublicClassJdk9NonExportedPackage", fail("IllegalAccessError") }, 132 133 { "reflectPublicMemberExportedPackage", successNoWarning() }, 134 { "reflectNonPublicMemberExportedPackage", fail("IllegalAccessException") }, 135 { "reflectPublicMemberNonExportedPackage", fail("IllegalAccessException") }, 136 { "reflectNonPublicMemberNonExportedPackage", fail("IllegalAccessException") }, 137 { "reflectPublicMemberJdk9NonExportedPackage", fail("IllegalAccessException") }, 138 { "reflectPublicMemberApplicationModule", successNoWarning() }, 139 140 { "setAccessiblePublicMemberExportedPackage", successNoWarning() }, 141 { "setAccessibleNonPublicMemberExportedPackage", fail("InaccessibleObjectException") }, 142 { "setAccessiblePublicMemberNonExportedPackage", fail("InaccessibleObjectException") }, 143 { "setAccessibleNonPublicMemberNonExportedPackage", fail("InaccessibleObjectException") }, 144 { "setAccessiblePublicMemberJdk9NonExportedPackage", fail("InaccessibleObjectException") }, 145 { "setAccessiblePublicMemberApplicationModule", successNoWarning() }, 146 { "setAccessibleNotPublicMemberApplicationModule", fail("InaccessibleObjectException") }, 147 148 { "privateLookupPublicClassExportedPackage", fail("IllegalAccessException") }, 149 { "privateLookupNonPublicClassExportedPackage", fail("IllegalAccessException") }, 150 { "privateLookupPublicClassNonExportedPackage", fail("IllegalAccessException") }, 151 { "privateLookupNonPublicClassNonExportedPackage", fail("IllegalAccessException") }, 152 { "privateLookupPublicClassJdk9NonExportedPackage", fail("IllegalAccessException") }, 153 }; 154 } 155 156 @DataProvider(name = "permitCases") 157 public Object[][] permitCases() { 158 return new Object[][] { 159 { "accessPublicClassNonExportedPackage", successNoWarning() }, 160 { "accessPublicClassJdk9NonExportedPackage", fail("IllegalAccessError") }, 161 162 { "reflectPublicMemberExportedPackage", successNoWarning() }, 163 { "reflectNonPublicMemberExportedPackage", fail("IllegalAccessException") }, 164 { "reflectPublicMemberNonExportedPackage", successWithWarning() }, 165 { "reflectNonPublicMemberNonExportedPackage", fail("IllegalAccessException") }, 166 { "reflectPublicMemberJdk9NonExportedPackage", fail("IllegalAccessException") }, 167 168 { "setAccessiblePublicMemberExportedPackage", successNoWarning()}, 169 { "setAccessibleNonPublicMemberExportedPackage", successWithWarning() }, 170 { "setAccessiblePublicMemberNonExportedPackage", successWithWarning() }, 171 { "setAccessibleNonPublicMemberNonExportedPackage", successWithWarning() }, 172 { "setAccessiblePublicMemberJdk9NonExportedPackage", fail("InaccessibleObjectException") }, 173 { "setAccessiblePublicMemberApplicationModule", successNoWarning() }, 174 { "setAccessibleNotPublicMemberApplicationModule", fail("InaccessibleObjectException") }, 175 176 { "privateLookupPublicClassExportedPackage", successWithWarning() }, 177 { "privateLookupNonPublicClassExportedPackage", successWithWarning() }, 178 { "privateLookupPublicClassNonExportedPackage", successWithWarning() }, 179 { "privateLookupNonPublicClassNonExportedPackage", successWithWarning() }, 180 { "privateLookupPublicClassJdk9NonExportedPackage", fail("IllegalAccessException") }, 181 { "privateLookupPublicClassApplicationModule", fail("IllegalAccessException") }, 182 }; 183 } 184 185 /** 186 * Checks an expected result with the output captured by the given 187 * OutputAnalyzer. 188 */ 189 void checkResult(Result expectedResult, OutputAnalyzer outputAnalyzer) { 190 expectedResult.expectedOutput().forEach(outputAnalyzer::shouldContain); 191 expectedResult.notExpectedOutput().forEach(outputAnalyzer::shouldNotContain); 192 int exitValue = outputAnalyzer.getExitValue(); 193 if (expectedResult.shouldSucceed()) { 194 assertTrue(exitValue == 0); 195 } else { 196 assertTrue(exitValue != 0); 197 } 198 } 199 200 /** 201 * Runs the test to execute the given test action. The VM is run with the 202 * given VM options and the output checked to see that it matches the 203 * expected result. 204 */ 205 OutputAnalyzer run(String action, Result expectedResult, String... vmopts) 206 throws Exception 207 { 208 Stream<String> s1 = Stream.of(vmopts); 209 Stream<String> s2 = Stream.of("-p", MODULE_PATH, "--add-modules=m", 210 "-cp", TEST_CLASSES, "TryAccess", action); 211 String[] opts = Stream.concat(s1, s2).toArray(String[]::new); 212 OutputAnalyzer outputAnalyzer = ProcessTools 213 .executeTestJava(opts) 214 .outputTo(System.out) 215 .errorTo(System.out); 216 if (expectedResult != null) 217 checkResult(expectedResult, outputAnalyzer); 218 return outputAnalyzer; 219 } 220 221 OutputAnalyzer run(String action, String... vmopts) throws Exception { 222 return run(action, null, vmopts); 223 } 224 225 /** 226 * Runs an executable JAR to execute the given test action. The VM is run 227 * with the given VM options and the output checked to see that it matches 228 * the expected result. 229 */ 230 void run(Path jarFile, String action, Result expectedResult, String... vmopts) 231 throws Exception 232 { 233 Stream<String> s1 = Stream.of(vmopts); 234 Stream<String> s2 = Stream.of("-jar", jarFile.toString(), action); 235 String[] opts = Stream.concat(s1, s2).toArray(String[]::new); 236 checkResult(expectedResult, ProcessTools.executeTestJava(opts) 237 .outputTo(System.out) 238 .errorTo(System.out)); 239 } 240 241 @Test(dataProvider = "denyCases") 242 public void testDeny(String action, Result expectedResult) throws Exception { 243 run(action, expectedResult, "--illegal-access=deny"); 244 } 245 246 @Test(dataProvider = "permitCases") 247 public void testDefault(String action, Result expectedResult) throws Exception { 248 run(action, expectedResult); 249 } 250 251 @Test(dataProvider = "permitCases") 252 public void testPermit(String action, Result expectedResult) throws Exception { 253 run(action, expectedResult, "--illegal-access=permit"); 254 } 255 256 @Test(dataProvider = "permitCases") 257 public void testWarn(String action, Result expectedResult) throws Exception { 258 run(action, expectedResult, "--illegal-access=warn"); 259 } 260 261 @Test(dataProvider = "permitCases") 262 public void testDebug(String action, Result expectedResult) throws Exception { 263 // expect stack trace with WARNING 264 if (expectedResult.expectedOutput().anyMatch("WARNING"::equals)) { 265 expectedResult.expect("TryAccess.main"); 266 } 267 run(action, expectedResult, "--illegal-access=debug"); 268 } 269 270 /** 271 * Test accessing internals of upgradeable module 272 */ 273 public void testWithUpgradedModule() throws Exception { 274 // upgradeable module loaded from run-time image 275 run("setAccessibleNotPublicMemberUpgradeableModule", successWithWarning(), 276 "--add-modules=java.activation"); 277 278 // upgradeable module loaded from upgrade module path 279 Path upgradesrc = Paths.get(TEST_SRC, "upgradesrc"); 280 Path upgrademods = Files.createDirectory(Paths.get("upgrademods")); 281 Path output = upgrademods.resolve("java.activation"); 282 assertTrue(CompilerUtils.compile(upgradesrc, output)); 283 run("setAccessibleNotPublicMemberUpgradeableModule", 284 fail("InaccessibleObjectException"), 285 "--upgrade-module-path=" + upgrademods, 286 "--add-modules=java.activation"); 287 } 288 289 /** 290 * Specify --add-exports to export a package 291 */ 292 public void testWithAddExportsOption() throws Exception { 293 // warning 294 run("reflectPublicMemberNonExportedPackage", successWithWarning()); 295 296 // no warning due to --add-exports 297 run("reflectPublicMemberNonExportedPackage", successNoWarning(), 298 "--add-exports", "java.base/sun.security.x509=ALL-UNNAMED"); 299 300 // attempt two illegal accesses, one allowed by --add-exports 301 run("reflectPublicMemberNonExportedPackage" 302 + ",setAccessibleNonPublicMemberExportedPackage", 303 successWithWarning(), 304 "--add-exports", "java.base/sun.security.x509=ALL-UNNAMED"); 305 } 306 307 /** 308 * Specify --add-open to open a package 309 */ 310 public void testWithAddOpensOption() throws Exception { 311 // warning 312 run("setAccessibleNonPublicMemberExportedPackage", successWithWarning()); 313 314 // no warning due to --add-opens 315 run("setAccessibleNonPublicMemberExportedPackage", successNoWarning(), 316 "--add-opens", "java.base/java.lang=ALL-UNNAMED"); 317 318 // attempt two illegal accesses, one allowed by --add-opens 319 run("reflectPublicMemberNonExportedPackage" 320 + ",setAccessibleNonPublicMemberExportedPackage", 321 successWithWarning(), 322 "--add-opens", "java.base/java.lang=ALL-UNNAMED"); 323 } 324 325 /** 326 * Test reflective API to export a package 327 */ 328 public void testWithReflectiveExports() throws Exception { 329 // compile patch for java.base 330 Path src = Paths.get(TEST_SRC, "patchsrc", "java.base"); 331 Path patch = Files.createDirectories(Paths.get("patches", "java.base")); 332 assertTrue(CompilerUtils.compile(src, patch, 333 "--patch-module", "java.base=" + src)); 334 335 // reflectively export, then access 336 run("exportNonExportedPackages,reflectPublicMemberNonExportedPackage", 337 successNoWarning(), 338 "--patch-module", "java.base=" + patch); 339 340 // access, reflectively export, access again 341 List<String> output = run("reflectPublicMemberNonExportedPackage," 342 + "exportNonExportedPackages," 343 + "reflectPublicMemberNonExportedPackage", 344 "--patch-module", "java.base="+patch, 345 "--illegal-access=warn").asLines(); 346 assertTrue(count(output, "WARNING") == 1); // one warning 347 } 348 349 /** 350 * Test reflective API to open a package 351 */ 352 public void testWithReflectiveOpens() throws Exception { 353 // compile patch for java.base 354 Path src = Paths.get(TEST_SRC, "patchsrc", "java.base"); 355 Path patch = Files.createDirectories(Paths.get("patches", "java.base")); 356 assertTrue(CompilerUtils.compile(src, patch, 357 "--patch-module", "java.base=" + src)); 358 359 // reflectively open exported package, then access 360 run("openExportedPackage,setAccessibleNonPublicMemberExportedPackage", 361 successNoWarning(), 362 "--patch-module", "java.base=" + patch); 363 364 // access, reflectively open exported package, access again 365 List<String> output1 = run("setAccessibleNonPublicMemberExportedPackage" 366 + ",openExportedPackage" 367 + ",setAccessibleNonPublicMemberExportedPackage", 368 "--patch-module", "java.base=" + patch, 369 "--illegal-access=warn").asLines(); 370 assertTrue(count(output1, "WARNING") == 1); // one warning 371 372 // reflectively open non-exported packages, then access 373 run("openNonExportedPackages,setAccessibleNonPublicMemberNonExportedPackage", 374 successNoWarning(), 375 "--patch-module", "java.base=" + patch); 376 377 // access, reflectively open non-exported package, access again 378 List<String> output2 = run("setAccessibleNonPublicMemberNonExportedPackage" 379 + ",openNonExportedPackages" 380 + ",setAccessibleNonPublicMemberNonExportedPackage", 381 "--patch-module", "java.base=" + patch, 382 "--illegal-access=warn").asLines(); 383 assertTrue(count(output2, "WARNING") == 1); // one warning 384 } 385 386 /** 387 * Specify Add-Exports in JAR file manifest 388 */ 389 public void testWithAddExportsInManifest() throws Exception { 390 Manifest man = new Manifest(); 391 Attributes attrs = man.getMainAttributes(); 392 attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 393 attrs.put(Attributes.Name.MAIN_CLASS, "TryAccess"); 394 attrs.put(new Attributes.Name("Add-Exports"), "java.base/sun.security.x509"); 395 Path jarfile = Paths.get("x.jar"); 396 Path classes = Paths.get(TEST_CLASSES); 397 JarUtils.createJarFile(jarfile, man, classes, Paths.get("TryAccess.class")); 398 399 run(jarfile, "reflectPublicMemberNonExportedPackage", successNoWarning()); 400 401 run(jarfile, "setAccessibleNonPublicMemberExportedPackage", successWithWarning()); 402 403 // attempt two illegal accesses, one allowed by Add-Exports 404 run(jarfile, "reflectPublicMemberNonExportedPackage," 405 + "setAccessibleNonPublicMemberExportedPackage", 406 successWithWarning()); 407 } 408 409 /** 410 * Specify Add-Opens in JAR file manifest 411 */ 412 public void testWithAddOpensInManifest() throws Exception { 413 Manifest man = new Manifest(); 414 Attributes attrs = man.getMainAttributes(); 415 attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 416 attrs.put(Attributes.Name.MAIN_CLASS, "TryAccess"); 417 attrs.put(new Attributes.Name("Add-Opens"), "java.base/java.lang"); 418 Path jarfile = Paths.get("x.jar"); 419 Path classes = Paths.get(TEST_CLASSES); 420 JarUtils.createJarFile(jarfile, man, classes, Paths.get("TryAccess.class")); 421 422 run(jarfile, "setAccessibleNonPublicMemberExportedPackage", successNoWarning()); 423 424 run(jarfile, "reflectPublicMemberNonExportedPackage", successWithWarning()); 425 426 // attempt two illegal accesses, one allowed by Add-Opens 427 run(jarfile, "reflectPublicMemberNonExportedPackage," 428 + "setAccessibleNonPublicMemberExportedPackage", 429 successWithWarning()); 430 } 431 432 /** 433 * Test that default behavior is to print a warning on the first illegal 434 * access only. 435 */ 436 public void testWarnOnFirstIllegalAccess() throws Exception { 437 String action1 = "reflectPublicMemberNonExportedPackage"; 438 String action2 = "setAccessibleNonPublicMemberExportedPackage"; 439 int warningCount = count(run(action1).asLines(), "WARNING"); 440 441 // same illegal access 442 List<String> output1 = run(action1 + "," + action1).asLines(); 443 assertTrue(count(output1, "WARNING") == warningCount); 444 445 // different illegal access 446 List<String> output2 = run(action1 + "," + action2).asLines(); 447 assertTrue(count(output2, "WARNING") == warningCount); 448 } 449 450 /** 451 * Test that --illegal-access=warn prints a one-line warning per each unique 452 * illegal access. 453 */ 454 public void testWarnPerIllegalAccess() throws Exception { 455 String action1 = "reflectPublicMemberNonExportedPackage"; 456 String action2 = "setAccessibleNonPublicMemberExportedPackage"; 457 458 // same illegal access 459 String repeatedActions = action1 + "," + action1; 460 List<String> output1 = run(repeatedActions, "--illegal-access=warn").asLines(); 461 assertTrue(count(output1, "WARNING") == 1); 462 463 // different illegal access 464 String differentActions = action1 + "," + action2; 465 List<String> output2 = run(differentActions, "--illegal-access=warn").asLines(); 466 assertTrue(count(output2, "WARNING") == 2); 467 } 468 469 /** 470 * Specify --illegal-access more than once, last one wins 471 */ 472 public void testRepeatedOption() throws Exception { 473 run("accessPublicClassNonExportedPackage", successNoWarning(), 474 "--illegal-access=deny", "--illegal-access=permit"); 475 run("accessPublicClassNonExportedPackage", fail("IllegalAccessError"), 476 "--illegal-access=permit", "--illegal-access=deny"); 477 } 478 479 /** 480 * Specify bad value to --illegal-access 481 */ 482 public void testBadValue() throws Exception { 483 run("accessPublicClassNonExportedPackage", 484 fail("Value specified to --illegal-access not recognized"), 485 "--illegal-access=BAD"); 486 } 487 488 private int count(Iterable<String> lines, CharSequence cs) { 489 int count = 0; 490 for (String line : lines) { 491 if (line.contains(cs)) count++; 492 } 493 return count; 494 } 495 }