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 }