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