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 }