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 }