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 modules 30 * @build IllegalAccessTest TryAccess 31 * jdk.test.lib.compiler.CompilerUtils 32 * jdk.test.lib.util.JarUtils 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.test.lib.process.ProcessTools; 49 import jdk.test.lib.process.OutputAnalyzer; 50 import jdk.test.lib.util.JarUtils; 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 /** 272 * Specify --add-exports to export a package 273 */ 274 public void testWithAddExportsOption() throws Exception { 275 // warning 276 run("reflectPublicMemberNonExportedPackage", successWithWarning()); 277 278 // no warning due to --add-exports 279 run("reflectPublicMemberNonExportedPackage", successNoWarning(), 280 "--add-exports", "java.base/sun.security.x509=ALL-UNNAMED"); 281 282 // attempt two illegal accesses, one allowed by --add-exports 283 run("reflectPublicMemberNonExportedPackage" 284 + ",setAccessibleNonPublicMemberExportedPackage", 285 successWithWarning(), 286 "--add-exports", "java.base/sun.security.x509=ALL-UNNAMED"); 287 } 288 289 /** 290 * Specify --add-open to open a package 291 */ 292 public void testWithAddOpensOption() throws Exception { 293 // warning 294 run("setAccessibleNonPublicMemberExportedPackage", successWithWarning()); 295 296 // no warning due to --add-opens 297 run("setAccessibleNonPublicMemberExportedPackage", successNoWarning(), 298 "--add-opens", "java.base/java.lang=ALL-UNNAMED"); 299 300 // attempt two illegal accesses, one allowed by --add-opens 301 run("reflectPublicMemberNonExportedPackage" 302 + ",setAccessibleNonPublicMemberExportedPackage", 303 successWithWarning(), 304 "--add-opens", "java.base/java.lang=ALL-UNNAMED"); 305 } 306 307 /** 308 * Test reflective API to export a package 309 */ 310 public void testWithReflectiveExports() throws Exception { 311 // compile patch for java.base 312 Path src = Paths.get(TEST_SRC, "patchsrc", "java.base"); 313 Path patch = Files.createDirectories(Paths.get("patches", "java.base")); 314 assertTrue(CompilerUtils.compile(src, patch, 315 "--patch-module", "java.base=" + src)); 316 317 // reflectively export, then access 318 run("exportNonExportedPackages,reflectPublicMemberNonExportedPackage", 319 successNoWarning(), 320 "--patch-module", "java.base=" + patch); 321 322 // access, reflectively export, access again 323 List<String> output = run("reflectPublicMemberNonExportedPackage," 324 + "exportNonExportedPackages," 325 + "reflectPublicMemberNonExportedPackage", 326 "--patch-module", "java.base="+patch, 327 "--illegal-access=warn").asLines(); 328 assertTrue(count(output, "WARNING") == 1); // one warning 329 } 330 331 /** 332 * Test reflective API to open a package 333 */ 334 public void testWithReflectiveOpens() throws Exception { 335 // compile patch for java.base 336 Path src = Paths.get(TEST_SRC, "patchsrc", "java.base"); 337 Path patch = Files.createDirectories(Paths.get("patches", "java.base")); 338 assertTrue(CompilerUtils.compile(src, patch, 339 "--patch-module", "java.base=" + src)); 340 341 // reflectively open exported package, then access 342 run("openExportedPackage,setAccessibleNonPublicMemberExportedPackage", 343 successNoWarning(), 344 "--patch-module", "java.base=" + patch); 345 346 // access, reflectively open exported package, access again 347 List<String> output1 = run("setAccessibleNonPublicMemberExportedPackage" 348 + ",openExportedPackage" 349 + ",setAccessibleNonPublicMemberExportedPackage", 350 "--patch-module", "java.base=" + patch, 351 "--illegal-access=warn").asLines(); 352 assertTrue(count(output1, "WARNING") == 1); // one warning 353 354 // reflectively open non-exported packages, then access 355 run("openNonExportedPackages,setAccessibleNonPublicMemberNonExportedPackage", 356 successNoWarning(), 357 "--patch-module", "java.base=" + patch); 358 359 // access, reflectively open non-exported package, access again 360 List<String> output2 = run("setAccessibleNonPublicMemberNonExportedPackage" 361 + ",openNonExportedPackages" 362 + ",setAccessibleNonPublicMemberNonExportedPackage", 363 "--patch-module", "java.base=" + patch, 364 "--illegal-access=warn").asLines(); 365 assertTrue(count(output2, "WARNING") == 1); // one warning 366 } 367 368 /** 369 * Specify Add-Exports in JAR file manifest 370 */ 371 public void testWithAddExportsInManifest() throws Exception { 372 Manifest man = new Manifest(); 373 Attributes attrs = man.getMainAttributes(); 374 attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 375 attrs.put(Attributes.Name.MAIN_CLASS, "TryAccess"); 376 attrs.put(new Attributes.Name("Add-Exports"), "java.base/sun.security.x509"); 377 Path jarfile = Paths.get("x.jar"); 378 Path classes = Paths.get(TEST_CLASSES); 379 JarUtils.createJarFile(jarfile, man, classes, Paths.get("TryAccess.class")); 380 381 run(jarfile, "reflectPublicMemberNonExportedPackage", successNoWarning()); 382 383 run(jarfile, "setAccessibleNonPublicMemberExportedPackage", successWithWarning()); 384 385 // attempt two illegal accesses, one allowed by Add-Exports 386 run(jarfile, "reflectPublicMemberNonExportedPackage," 387 + "setAccessibleNonPublicMemberExportedPackage", 388 successWithWarning()); 389 } 390 391 /** 392 * Specify Add-Opens in JAR file manifest 393 */ 394 public void testWithAddOpensInManifest() throws Exception { 395 Manifest man = new Manifest(); 396 Attributes attrs = man.getMainAttributes(); 397 attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 398 attrs.put(Attributes.Name.MAIN_CLASS, "TryAccess"); 399 attrs.put(new Attributes.Name("Add-Opens"), "java.base/java.lang"); 400 Path jarfile = Paths.get("x.jar"); 401 Path classes = Paths.get(TEST_CLASSES); 402 JarUtils.createJarFile(jarfile, man, classes, Paths.get("TryAccess.class")); 403 404 run(jarfile, "setAccessibleNonPublicMemberExportedPackage", successNoWarning()); 405 406 run(jarfile, "reflectPublicMemberNonExportedPackage", successWithWarning()); 407 408 // attempt two illegal accesses, one allowed by Add-Opens 409 run(jarfile, "reflectPublicMemberNonExportedPackage," 410 + "setAccessibleNonPublicMemberExportedPackage", 411 successWithWarning()); 412 } 413 414 /** 415 * Test that default behavior is to print a warning on the first illegal 416 * access only. 417 */ 418 public void testWarnOnFirstIllegalAccess() throws Exception { 419 String action1 = "reflectPublicMemberNonExportedPackage"; 420 String action2 = "setAccessibleNonPublicMemberExportedPackage"; 421 int warningCount = count(run(action1).asLines(), "WARNING"); 422 423 // same illegal access 424 List<String> output1 = run(action1 + "," + action1).asLines(); 425 assertTrue(count(output1, "WARNING") == warningCount); 426 427 // different illegal access 428 List<String> output2 = run(action1 + "," + action2).asLines(); 429 assertTrue(count(output2, "WARNING") == warningCount); 430 } 431 432 /** 433 * Test that --illegal-access=warn prints a one-line warning per each unique 434 * illegal access. 435 */ 436 public void testWarnPerIllegalAccess() throws Exception { 437 String action1 = "reflectPublicMemberNonExportedPackage"; 438 String action2 = "setAccessibleNonPublicMemberExportedPackage"; 439 440 // same illegal access 441 String repeatedActions = action1 + "," + action1; 442 List<String> output1 = run(repeatedActions, "--illegal-access=warn").asLines(); 443 assertTrue(count(output1, "WARNING") == 1); 444 445 // different illegal access 446 String differentActions = action1 + "," + action2; 447 List<String> output2 = run(differentActions, "--illegal-access=warn").asLines(); 448 assertTrue(count(output2, "WARNING") == 2); 449 } 450 451 /** 452 * Specify --illegal-access more than once, last one wins 453 */ 454 public void testRepeatedOption() throws Exception { 455 run("accessPublicClassNonExportedPackage", successNoWarning(), 456 "--illegal-access=deny", "--illegal-access=permit"); 457 run("accessPublicClassNonExportedPackage", fail("IllegalAccessError"), 458 "--illegal-access=permit", "--illegal-access=deny"); 459 } 460 461 /** 462 * Specify bad value to --illegal-access 463 */ 464 public void testBadValue() throws Exception { 465 run("accessPublicClassNonExportedPackage", 466 fail("Value specified to --illegal-access not recognized"), 467 "--illegal-access=BAD"); 468 } 469 470 private int count(Iterable<String> lines, CharSequence cs) { 471 int count = 0; 472 for (String line : lines) { 473 if (line.contains(cs)) count++; 474 } 475 return count; 476 } 477 }