/* * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /** * @test * @requires vm.compMode != "Xcomp" * @modules java.base/jdk.internal.misc * java.base/sun.security.x509 * @library /test/lib modules * @build IllegalAccessTest TryAccess * jdk.test.lib.compiler.CompilerUtils * jdk.test.lib.util.JarUtils * @build m/* * @run testng/othervm/timeout=180 IllegalAccessTest * @summary Basic test for java --illegal-access=$VALUE */ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.stream.Stream; import jdk.test.lib.compiler.CompilerUtils; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.util.JarUtils; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.*; /** * Basic test of --illegal-access=value to deny or permit access to JDK internals. */ @Test public class IllegalAccessTest { static final String TEST_SRC = System.getProperty("test.src"); static final String TEST_CLASSES = System.getProperty("test.classes"); static final String MODULE_PATH = System.getProperty("jdk.module.path"); /** * Represents the expected result of a test. */ static final class Result { private final boolean success; private final List expectedOutput = new ArrayList<>(); private final List notExpectedOutput = new ArrayList<>(); Result(boolean success) { this.success = success; } Result expect(String msg) { expectedOutput.add(msg); return this; } Result doNotExpect(String msg) { notExpectedOutput.add(msg); return this; } boolean shouldSucceed() { return success; } Stream expectedOutput() { return expectedOutput.stream(); } Stream notExpectedOutput() { return notExpectedOutput.stream(); } @Override public String toString() { String s = (success) ? "success" : "failure"; for (String msg : expectedOutput) { s += "/" + msg; } return s; } } static Result success() { return new Result(true); } static Result successNoWarning() { return success().doNotExpect("WARNING"); } static Result successWithWarning() { return success().expect("WARNING"); } static Result fail(String expectedOutput) { return new Result(false).expect(expectedOutput).doNotExpect("WARNING"); } @DataProvider(name = "denyCases") public Object[][] denyCases() { return new Object[][] { { "accessPublicClassNonExportedPackage", fail("IllegalAccessError") }, { "accessPublicClassJdk9NonExportedPackage", fail("IllegalAccessError") }, { "reflectPublicMemberExportedPackage", successNoWarning() }, { "reflectNonPublicMemberExportedPackage", fail("IllegalAccessException") }, { "reflectPublicMemberNonExportedPackage", fail("IllegalAccessException") }, { "reflectNonPublicMemberNonExportedPackage", fail("IllegalAccessException") }, { "reflectPublicMemberJdk9NonExportedPackage", fail("IllegalAccessException") }, { "reflectPublicMemberApplicationModule", successNoWarning() }, { "setAccessiblePublicMemberExportedPackage", successNoWarning() }, { "setAccessibleNonPublicMemberExportedPackage", fail("InaccessibleObjectException") }, { "setAccessiblePublicMemberNonExportedPackage", fail("InaccessibleObjectException") }, { "setAccessibleNonPublicMemberNonExportedPackage", fail("InaccessibleObjectException") }, { "setAccessiblePublicMemberJdk9NonExportedPackage", fail("InaccessibleObjectException") }, { "setAccessiblePublicMemberApplicationModule", successNoWarning() }, { "setAccessibleNotPublicMemberApplicationModule", fail("InaccessibleObjectException") }, { "privateLookupPublicClassExportedPackage", fail("IllegalAccessException") }, { "privateLookupNonPublicClassExportedPackage", fail("IllegalAccessException") }, { "privateLookupPublicClassNonExportedPackage", fail("IllegalAccessException") }, { "privateLookupNonPublicClassNonExportedPackage", fail("IllegalAccessException") }, { "privateLookupPublicClassJdk9NonExportedPackage", fail("IllegalAccessException") }, }; } @DataProvider(name = "permitCases") public Object[][] permitCases() { return new Object[][] { { "accessPublicClassNonExportedPackage", successNoWarning() }, { "accessPublicClassJdk9NonExportedPackage", fail("IllegalAccessError") }, { "reflectPublicMemberExportedPackage", successNoWarning() }, { "reflectNonPublicMemberExportedPackage", fail("IllegalAccessException") }, { "reflectPublicMemberNonExportedPackage", successWithWarning() }, { "reflectNonPublicMemberNonExportedPackage", fail("IllegalAccessException") }, { "reflectPublicMemberJdk9NonExportedPackage", fail("IllegalAccessException") }, { "setAccessiblePublicMemberExportedPackage", successNoWarning()}, { "setAccessibleNonPublicMemberExportedPackage", successWithWarning() }, { "setAccessiblePublicMemberNonExportedPackage", successWithWarning() }, { "setAccessibleNonPublicMemberNonExportedPackage", successWithWarning() }, { "setAccessiblePublicMemberJdk9NonExportedPackage", fail("InaccessibleObjectException") }, { "setAccessiblePublicMemberApplicationModule", successNoWarning() }, { "setAccessibleNotPublicMemberApplicationModule", fail("InaccessibleObjectException") }, { "privateLookupPublicClassExportedPackage", successWithWarning() }, { "privateLookupNonPublicClassExportedPackage", successWithWarning() }, { "privateLookupPublicClassNonExportedPackage", successWithWarning() }, { "privateLookupNonPublicClassNonExportedPackage", successWithWarning() }, { "privateLookupPublicClassJdk9NonExportedPackage", fail("IllegalAccessException") }, { "privateLookupPublicClassApplicationModule", fail("IllegalAccessException") }, }; } /** * Checks an expected result with the output captured by the given * OutputAnalyzer. */ void checkResult(Result expectedResult, OutputAnalyzer outputAnalyzer) { expectedResult.expectedOutput().forEach(outputAnalyzer::shouldContain); expectedResult.notExpectedOutput().forEach(outputAnalyzer::shouldNotContain); int exitValue = outputAnalyzer.getExitValue(); if (expectedResult.shouldSucceed()) { assertTrue(exitValue == 0); } else { assertTrue(exitValue != 0); } } /** * Runs the test to execute the given test action. The VM is run with the * given VM options and the output checked to see that it matches the * expected result. */ OutputAnalyzer run(String action, Result expectedResult, String... vmopts) throws Exception { Stream s1 = Stream.of(vmopts); Stream s2 = Stream.of("-p", MODULE_PATH, "--add-modules=m", "-cp", TEST_CLASSES, "TryAccess", action); String[] opts = Stream.concat(s1, s2).toArray(String[]::new); OutputAnalyzer outputAnalyzer = ProcessTools .executeTestJava(opts) .outputTo(System.out) .errorTo(System.out); if (expectedResult != null) checkResult(expectedResult, outputAnalyzer); return outputAnalyzer; } OutputAnalyzer run(String action, String... vmopts) throws Exception { return run(action, null, vmopts); } /** * Runs an executable JAR to execute the given test action. The VM is run * with the given VM options and the output checked to see that it matches * the expected result. */ void run(Path jarFile, String action, Result expectedResult, String... vmopts) throws Exception { Stream s1 = Stream.of(vmopts); Stream s2 = Stream.of("-jar", jarFile.toString(), action); String[] opts = Stream.concat(s1, s2).toArray(String[]::new); checkResult(expectedResult, ProcessTools.executeTestJava(opts) .outputTo(System.out) .errorTo(System.out)); } @Test(dataProvider = "denyCases") public void testDeny(String action, Result expectedResult) throws Exception { run(action, expectedResult, "--illegal-access=deny"); } @Test(dataProvider = "permitCases") public void testDefault(String action, Result expectedResult) throws Exception { run(action, expectedResult); } @Test(dataProvider = "permitCases") public void testPermit(String action, Result expectedResult) throws Exception { run(action, expectedResult, "--illegal-access=permit"); } @Test(dataProvider = "permitCases") public void testWarn(String action, Result expectedResult) throws Exception { run(action, expectedResult, "--illegal-access=warn"); } @Test(dataProvider = "permitCases") public void testDebug(String action, Result expectedResult) throws Exception { // expect stack trace with WARNING if (expectedResult.expectedOutput().anyMatch("WARNING"::equals)) { expectedResult.expect("TryAccess.main"); } run(action, expectedResult, "--illegal-access=debug"); } /** * Specify --add-exports to export a package */ public void testWithAddExportsOption() throws Exception { // warning run("reflectPublicMemberNonExportedPackage", successWithWarning()); // no warning due to --add-exports run("reflectPublicMemberNonExportedPackage", successNoWarning(), "--add-exports", "java.base/sun.security.x509=ALL-UNNAMED"); // attempt two illegal accesses, one allowed by --add-exports run("reflectPublicMemberNonExportedPackage" + ",setAccessibleNonPublicMemberExportedPackage", successWithWarning(), "--add-exports", "java.base/sun.security.x509=ALL-UNNAMED"); } /** * Specify --add-open to open a package */ public void testWithAddOpensOption() throws Exception { // warning run("setAccessibleNonPublicMemberExportedPackage", successWithWarning()); // no warning due to --add-opens run("setAccessibleNonPublicMemberExportedPackage", successNoWarning(), "--add-opens", "java.base/java.lang=ALL-UNNAMED"); // attempt two illegal accesses, one allowed by --add-opens run("reflectPublicMemberNonExportedPackage" + ",setAccessibleNonPublicMemberExportedPackage", successWithWarning(), "--add-opens", "java.base/java.lang=ALL-UNNAMED"); } /** * Test reflective API to export a package */ public void testWithReflectiveExports() throws Exception { // compile patch for java.base Path src = Paths.get(TEST_SRC, "patchsrc", "java.base"); Path patch = Files.createDirectories(Paths.get("patches", "java.base")); assertTrue(CompilerUtils.compile(src, patch, "--patch-module", "java.base=" + src)); // reflectively export, then access run("exportNonExportedPackages,reflectPublicMemberNonExportedPackage", successNoWarning(), "--patch-module", "java.base=" + patch); // access, reflectively export, access again List output = run("reflectPublicMemberNonExportedPackage," + "exportNonExportedPackages," + "reflectPublicMemberNonExportedPackage", "--patch-module", "java.base="+patch, "--illegal-access=warn").asLines(); assertTrue(count(output, "WARNING") == 1); // one warning } /** * Test reflective API to open a package */ public void testWithReflectiveOpens() throws Exception { // compile patch for java.base Path src = Paths.get(TEST_SRC, "patchsrc", "java.base"); Path patch = Files.createDirectories(Paths.get("patches", "java.base")); assertTrue(CompilerUtils.compile(src, patch, "--patch-module", "java.base=" + src)); // reflectively open exported package, then access run("openExportedPackage,setAccessibleNonPublicMemberExportedPackage", successNoWarning(), "--patch-module", "java.base=" + patch); // access, reflectively open exported package, access again List output1 = run("setAccessibleNonPublicMemberExportedPackage" + ",openExportedPackage" + ",setAccessibleNonPublicMemberExportedPackage", "--patch-module", "java.base=" + patch, "--illegal-access=warn").asLines(); assertTrue(count(output1, "WARNING") == 1); // one warning // reflectively open non-exported packages, then access run("openNonExportedPackages,setAccessibleNonPublicMemberNonExportedPackage", successNoWarning(), "--patch-module", "java.base=" + patch); // access, reflectively open non-exported package, access again List output2 = run("setAccessibleNonPublicMemberNonExportedPackage" + ",openNonExportedPackages" + ",setAccessibleNonPublicMemberNonExportedPackage", "--patch-module", "java.base=" + patch, "--illegal-access=warn").asLines(); assertTrue(count(output2, "WARNING") == 1); // one warning } /** * Specify Add-Exports in JAR file manifest */ public void testWithAddExportsInManifest() throws Exception { Manifest man = new Manifest(); Attributes attrs = man.getMainAttributes(); attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); attrs.put(Attributes.Name.MAIN_CLASS, "TryAccess"); attrs.put(new Attributes.Name("Add-Exports"), "java.base/sun.security.x509"); Path jarfile = Paths.get("x.jar"); Path classes = Paths.get(TEST_CLASSES); JarUtils.createJarFile(jarfile, man, classes, Paths.get("TryAccess.class")); run(jarfile, "reflectPublicMemberNonExportedPackage", successNoWarning()); run(jarfile, "setAccessibleNonPublicMemberExportedPackage", successWithWarning()); // attempt two illegal accesses, one allowed by Add-Exports run(jarfile, "reflectPublicMemberNonExportedPackage," + "setAccessibleNonPublicMemberExportedPackage", successWithWarning()); } /** * Specify Add-Opens in JAR file manifest */ public void testWithAddOpensInManifest() throws Exception { Manifest man = new Manifest(); Attributes attrs = man.getMainAttributes(); attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); attrs.put(Attributes.Name.MAIN_CLASS, "TryAccess"); attrs.put(new Attributes.Name("Add-Opens"), "java.base/java.lang"); Path jarfile = Paths.get("x.jar"); Path classes = Paths.get(TEST_CLASSES); JarUtils.createJarFile(jarfile, man, classes, Paths.get("TryAccess.class")); run(jarfile, "setAccessibleNonPublicMemberExportedPackage", successNoWarning()); run(jarfile, "reflectPublicMemberNonExportedPackage", successWithWarning()); // attempt two illegal accesses, one allowed by Add-Opens run(jarfile, "reflectPublicMemberNonExportedPackage," + "setAccessibleNonPublicMemberExportedPackage", successWithWarning()); } /** * Test that default behavior is to print a warning on the first illegal * access only. */ public void testWarnOnFirstIllegalAccess() throws Exception { String action1 = "reflectPublicMemberNonExportedPackage"; String action2 = "setAccessibleNonPublicMemberExportedPackage"; int warningCount = count(run(action1).asLines(), "WARNING"); // same illegal access List output1 = run(action1 + "," + action1).asLines(); assertTrue(count(output1, "WARNING") == warningCount); // different illegal access List output2 = run(action1 + "," + action2).asLines(); assertTrue(count(output2, "WARNING") == warningCount); } /** * Test that --illegal-access=warn prints a one-line warning per each unique * illegal access. */ public void testWarnPerIllegalAccess() throws Exception { String action1 = "reflectPublicMemberNonExportedPackage"; String action2 = "setAccessibleNonPublicMemberExportedPackage"; // same illegal access String repeatedActions = action1 + "," + action1; List output1 = run(repeatedActions, "--illegal-access=warn").asLines(); assertTrue(count(output1, "WARNING") == 1); // different illegal access String differentActions = action1 + "," + action2; List output2 = run(differentActions, "--illegal-access=warn").asLines(); assertTrue(count(output2, "WARNING") == 2); } /** * Specify --illegal-access more than once, last one wins */ public void testRepeatedOption() throws Exception { run("accessPublicClassNonExportedPackage", successNoWarning(), "--illegal-access=deny", "--illegal-access=permit"); run("accessPublicClassNonExportedPackage", fail("IllegalAccessError"), "--illegal-access=permit", "--illegal-access=deny"); } /** * Specify bad value to --illegal-access */ public void testBadValue() throws Exception { run("accessPublicClassNonExportedPackage", fail("Value specified to --illegal-access not recognized"), "--illegal-access=BAD"); } private int count(Iterable lines, CharSequence cs) { int count = 0; for (String line : lines) { if (line.contains(cs)) count++; } return count; } }