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 }