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