8072692: Improve performance of SecurityManager.checkPackageAccess
authordfuchs
Wed, 17 Jun 2015 15:47:12 -0400
changeset 31180 316a8c3e572a
parent 31177 c8ceef082391
child 31181 27ccb72932ee
8072692: Improve performance of SecurityManager.checkPackageAccess Reviewed-by: mullan, weijun
jdk/src/java.base/share/classes/java/lang/SecurityManager.java
jdk/test/java/lang/SecurityManager/CheckPackageAccess.java
jdk/test/java/lang/SecurityManager/CheckPackageMatching.java
jdk/test/java/lang/SecurityManager/RestrictedPackages.java
--- a/jdk/src/java.base/share/classes/java/lang/SecurityManager.java	Tue Jun 16 17:05:08 2015 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/SecurityManager.java	Wed Jun 17 15:47:12 2015 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2015, 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
@@ -1447,7 +1447,7 @@
             throw new NullPointerException("package name can't be null");
         }
 
-        String[] pkgs;
+        String[] restrictedPkgs;
         synchronized (packageAccessLock) {
             /*
              * Do we need to update our property array?
@@ -1457,8 +1457,7 @@
                     AccessController.doPrivileged(
                         new PrivilegedAction<>() {
                             public String run() {
-                                return java.security.Security.getProperty(
-                                    "package.access");
+                                return Security.getProperty("package.access");
                             }
                         }
                     );
@@ -1468,14 +1467,33 @@
 
             // Using a snapshot of packageAccess -- don't care if static field
             // changes afterwards; array contents won't change.
-            pkgs = packageAccess;
+            restrictedPkgs = packageAccess;
         }
 
         /*
          * Traverse the list of packages, check for any matches.
          */
-        for (String restrictedPkg : pkgs) {
-            if (pkg.startsWith(restrictedPkg) || restrictedPkg.equals(pkg + ".")) {
+        final int plen = pkg.length();
+        for (String restrictedPkg : restrictedPkgs) {
+            final int rlast = restrictedPkg.length() - 1;
+
+            // Optimizations:
+            //
+            // If rlast >= plen then restrictedPkg is longer than pkg by at
+            // least one char. This means pkg cannot start with restrictedPkg,
+            // since restrictedPkg will be longer than pkg.
+            //
+            // Similarly if rlast != plen, then pkg + "." cannot be the same
+            // as restrictedPkg, since pkg + "." will have a different length
+            // than restrictedPkg.
+            //
+            if (rlast < plen && pkg.startsWith(restrictedPkg) ||
+                // The following test is equivalent to
+                // restrictedPkg.equals(pkg + ".") but is noticeably more
+                // efficient:
+                rlast == plen && restrictedPkg.startsWith(pkg) &&
+                restrictedPkg.charAt(rlast) == '.')
+            {
                 checkPermission(
                     new RuntimePermission("accessClassInPackage." + pkg));
                 break;  // No need to continue; only need to check this once
--- a/jdk/test/java/lang/SecurityManager/CheckPackageAccess.java	Tue Jun 16 17:05:08 2015 -0700
+++ b/jdk/test/java/lang/SecurityManager/CheckPackageAccess.java	Wed Jun 17 15:47:12 2015 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2015, 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
@@ -29,12 +29,9 @@
  *  @run main/othervm CheckPackageAccess
  */
 
-import java.security.Security;
 import java.util.Collections;
-import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.StringTokenizer;
 
 /*
  * The main benefit of this test is to catch merge errors or other types
@@ -44,60 +41,12 @@
  */
 public class CheckPackageAccess {
 
-    /*
-     * This array should be updated whenever new packages are added to the
-     * package.access property in the java.security file
-     * NOTE: it should be in the same order as the java.security file
-     */
-    private static final String[] packages = {
-        "sun.",
-        "com.sun.xml.internal.",
-        "com.sun.imageio.",
-        "com.sun.istack.internal.",
-        "com.sun.jmx.",
-        "com.sun.media.sound.",
-        "com.sun.naming.internal.",
-        "com.sun.proxy.",
-        "com.sun.corba.se.",
-        "com.sun.org.apache.bcel.internal.",
-        "com.sun.org.apache.regexp.internal.",
-        "com.sun.org.apache.xerces.internal.",
-        "com.sun.org.apache.xpath.internal.",
-        "com.sun.org.apache.xalan.internal.extensions.",
-        "com.sun.org.apache.xalan.internal.lib.",
-        "com.sun.org.apache.xalan.internal.res.",
-        "com.sun.org.apache.xalan.internal.templates.",
-        "com.sun.org.apache.xalan.internal.utils.",
-        "com.sun.org.apache.xalan.internal.xslt.",
-        "com.sun.org.apache.xalan.internal.xsltc.cmdline.",
-        "com.sun.org.apache.xalan.internal.xsltc.compiler.",
-        "com.sun.org.apache.xalan.internal.xsltc.trax.",
-        "com.sun.org.apache.xalan.internal.xsltc.util.",
-        "com.sun.org.apache.xml.internal.res.",
-        "com.sun.org.apache.xml.internal.security.",
-        "com.sun.org.apache.xml.internal.serializer.utils.",
-        "com.sun.org.apache.xml.internal.utils.",
-        "com.sun.org.glassfish.",
-        "com.sun.tools.script.",
-        "com.oracle.xmlns.internal.",
-        "com.oracle.webservices.internal.",
-        "org.jcp.xml.dsig.internal.",
-        "jdk.internal.",
-        "jdk.nashorn.internal.",
-        "jdk.nashorn.tools.",
-        "jdk.tools.jimage.",
-        "com.sun.activation.registries."
-    };
+    public static void main(String[] args) throws Exception {
+        // get expected list of restricted packages
+        List<String> pkgs = RestrictedPackages.expected();
 
-    public static void main(String[] args) throws Exception {
-        List<String> pkgs = new ArrayList<>(Arrays.asList(packages));
-        String osName = System.getProperty("os.name");
-        if (osName.contains("OS X")) {
-            pkgs.add("apple.");  // add apple package for OS X
-        }
-
-        List<String> jspkgs =
-            getPackages(Security.getProperty("package.access"));
+        // get actual list of restricted packages
+        List<String> jspkgs = RestrictedPackages.actual();
 
         if (!isOpenJDKOnly()) {
             String lastPkg = pkgs.get(pkgs.size() - 1);
@@ -127,7 +76,7 @@
         }
         System.setSecurityManager(new SecurityManager());
         SecurityManager sm = System.getSecurityManager();
-        for (String pkg : packages) {
+        for (String pkg : pkgs) {
             String subpkg = pkg + "foo";
             try {
                 sm.checkPackageAccess(pkg);
@@ -153,18 +102,6 @@
         System.out.println("Test passed");
     }
 
-    private static List<String> getPackages(String p) {
-        List<String> packages = new ArrayList<>();
-        if (p != null && !p.equals("")) {
-            StringTokenizer tok = new StringTokenizer(p, ",");
-            while (tok.hasMoreElements()) {
-                String s = tok.nextToken().trim();
-                packages.add(s);
-            }
-        }
-        return packages;
-    }
-
     private static boolean isOpenJDKOnly() {
         String prop = System.getProperty("java.runtime.name");
         return prop != null && prop.startsWith("OpenJDK");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/SecurityManager/CheckPackageMatching.java	Wed Jun 17 15:47:12 2015 -0400
@@ -0,0 +1,545 @@
+/*
+ * Copyright (c) 2015, 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
+ * @bug 8072692
+ * @summary Check the matching implemented by SecurityManager.checkPackageAccess
+ * @run main/othervm CheckPackageMatching
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/*
+ * The purpose of this test is not to verify the content of the package
+ * access list - but to ensure that the matching implemented by the
+ * SecurityManager is correct. This is why we have our own pattern matching
+ * algorithm here.
+ */
+public class CheckPackageMatching {
+
+    /**
+     * The restricted packages listed in the package.access property of the
+     * java.security file.
+     */
+    private static final String[] packages =
+        RestrictedPackages.actual().toArray(new String[0]);
+
+    private static final boolean OPEN_JDK = isOpenJDKOnly();
+
+    /**
+     * PackageMatcher implements a state machine that matches package
+     * names against packages parsed from the package access list.
+     */
+    private static abstract class PackageMatcher {
+        // For each state, chars[state] contains the chars that matches.
+        private final char[][] chars;
+        // For each state, states[state][i] contains the next state to go
+        // to when chars[state][i] matches the current character.
+        private final int[][] states;
+
+        // Some markers. We're making the assumption that 0
+        // cannot be a valid character for a package name.
+        //
+        // We use 0 for marking that we expect an end of string in
+        // char[state][i].
+        private static final char END_OF_STRING = 0;
+        // This special state value indicates that we expect the string to end
+        // there.
+        private static final int END_STATE = -1;
+        // This special state value indicates that we can accept any character
+        // from now on.
+        private static final int WILDCARD_STATE = Integer.MIN_VALUE;
+
+        // Create the data for a new state machine to match package names from
+        // the array of package names passed as argument.
+        // Each package name in the array is expected to end with '.'
+        // For each package in packages we're going to compile state data
+        // that will match the regexp:
+        // ^packages[i].substring(0, packages[i].length()-1).replace(".","\\.")$|^packages[i].replace(".","\\.").*
+        //
+        // Let's say the package array is:
+        //
+        // String[] packages = { "sun.", "com.sun.jmx.", "com.sun.proxy.",
+        //                       "apple." };
+        //
+        // then the state machine will need data that looks like:
+        //
+        // char[][] chars = {
+        //    { 'a', 'c', 's' }, { 'p' }, { 'p' }, { 'l' }, { 'e' }, { 0, '.' },
+        //    { 'o' }, { 'm' }, { '.' }, { 's' }, { 'u' }, { 'n' }, { '.' },
+        //    { 'j', 'p'},
+        //    { 'm' }, { 'x' }, { 0, '.' },
+        //    { 'r' }, { 'o' }, { 'x' }, { 'y' }, { 0, '.' },
+        //    { 'u' }, { 'n' }, { 0, '.' }
+        // }
+        // int[][] states = {
+        //    { 1, 6, 22 }, { 2 }, { 3 }, { 4 }, { 5 },
+        //    { END_STATE, WILDCARD_STATE },
+        //    { 7 }, { 8 }, { 9 }, { 10 }, { 11 }, { 12 }, { 13 }, { 14, 17 },
+        //    { 15 }, { 16 }, { END_STATE, WILDCARD_STATE },
+        //    { 18 }, { 19 }, { 20 }, { 21 }, { END_STATE, WILDCARD_STATE },
+        //    { 23 }, { 24 }, { END_STATE, WILDCARD_STATE }
+        // }
+        //
+        // The machine will start by loading the chars and states for state 0
+        // chars[0] => { 'a', 'c', 's' } states[0] => { 1, 6, 22 }
+        // then it examines the char at index 0 in the candidate name.
+        // if the char matches one of the characters in chars[0], then it goes
+        // to the corresponding state in states[0]. For instance - if the first
+        // char in the candidate name is 's', which corresponds to chars[0][2] -
+        // then it will proceed with the next char in the candidate name and go
+        // to state 22 (as indicated by states[0][2]) - where it will load the
+        // chars and states for states 22: chars[22] = { 'u' },
+        // states[22] = { 23 } etc... until the candidate char at the current
+        // index matches no char in chars[states] => the candidate name doesn't
+        // match - or until it finds a success termination condition: the
+        // candidate chars are exhausted and states[state][0] is END_STATE, or
+        // the candidate chars are not exhausted - and
+        // states[state][chars[state]] is WILDCARD_STATE indicating a '.*' like
+        // regexp.
+        //
+        // [Note that the chars in chars[i] are sorted]
+        //
+        // The compile(...) method is reponsible for building the state machine
+        // data and is called only once in the constructor.
+        //
+        // The matches(String candidate) method will tell whether the candidate
+        // matches by implementing the algorithm described above.
+        //
+        PackageMatcher(String[] packages) {
+            final boolean[] selected = new boolean[packages.length];
+            Arrays.fill(selected, true);
+            final ArrayList<char[]> charList = new ArrayList<>();
+            final ArrayList<int[]> stateList = new ArrayList<>();
+            compile(0, 0, packages, selected, charList, stateList);
+            chars = charList.toArray(new char[0][0]);
+            states = stateList.toArray(new int[0][0]);
+        }
+
+        /**
+         * Compiles the state machine data (recursive).
+         *
+         * @param step  The index of the character which we're looking at in
+         *              this step.
+         * @param state The current state (starts at 0).
+         * @param pkgs  The list of packages from which the automaton is built.
+         * @param selected  Indicates which packages we're looking at in this
+                            step.
+         * @param charList  The list from which we will build
+                            {@code char[][] chars;}
+         * @param stateList The list from which we will build
+                            {@code int[][]  states;}
+         * @return the next available state.
+         */
+        private int compile(int step, int state, String[] pkgs,
+                            boolean[] selected, ArrayList<char[]> charList,
+                            ArrayList<int[]> stateList) {
+            final char[] next = new char[pkgs.length];
+            final int[] nexti = new int[pkgs.length];
+            int j = 0;
+            char min = Character.MAX_VALUE; char max = 0;
+            for (int i = 0; i < pkgs.length; i++) {
+                if (!selected[i]) continue;
+                final String p = pkgs[i];
+                final int len = p.length();
+                if (step > len) {
+                    selected[i] = false;
+                    continue;
+                }
+                if (len - 1 == step) {
+                    boolean unknown = true;
+                    for (int k = 0; k < j ; k++) {
+                        if (next[k] == END_OF_STRING) {
+                            unknown = false;
+                            break;
+                        }
+                    }
+                    if (unknown) {
+                        next[j] = END_OF_STRING;
+                        j++;
+                    }
+                    nexti[i] = END_STATE;
+                }
+                final char c = p.charAt(step);
+                nexti[i] = len - 1 == step ? END_STATE : c;
+                boolean unknown = j == 0 || c < min || c > max;
+                if (!unknown) {
+                    if (c != min || c != max) {
+                        unknown = true;
+                        for (int k = 0; k < j ; k++) {
+                            if (next[k] == c) {
+                                unknown = false;
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (unknown) {
+                    min = min > c ? c : min;
+                    max = max < c ? c : max;
+                    next[j] = c;
+                    j++;
+                }
+            }
+            final char[] nc = new char[j];
+            final int[]  nst = new int[j];
+            System.arraycopy(next, 0, nc, 0, nc.length);
+            Arrays.sort(nc);
+            final boolean ns[] = new boolean[pkgs.length];
+
+            charList.ensureCapacity(state + 1);
+            stateList.ensureCapacity(state + 1);
+            charList.add(state, nc);
+            stateList.add(state, nst);
+            state = state + 1;
+            for (int k = 0; k < nc.length; k++) {
+                int selectedCount = 0;
+                boolean endStateFound = false;
+                boolean wildcardFound = false;
+                for (int l = 0; l < nexti.length; l++) {
+                    if (!(ns[l] = selected[l])) {
+                        continue;
+                    }
+                    ns[l] = nexti[l] == nc[k] || nexti[l] == END_STATE
+                            && nc[k] == '.';
+                    endStateFound = endStateFound || nc[k] == END_OF_STRING
+                                    && nexti[l] == END_STATE;
+                    wildcardFound = wildcardFound || nc[k] == '.'
+                                    && nexti[l] == END_STATE;
+                    if (ns[l]) {
+                        selectedCount++;
+                    }
+                }
+                nst[k] = (endStateFound ? END_STATE
+                         : wildcardFound ? WILDCARD_STATE : state);
+                if (selectedCount == 0 || wildcardFound) {
+                    continue;
+                }
+                state = compile(step + 1, state, pkgs, ns, charList, stateList);
+            }
+            return state;
+        }
+
+        /**
+         * Matches 'pkg' against the list of package names compiled in the
+         * state machine data.
+         *
+         * @param pkg The package name to match. Must not end with '.'.
+         * @return true if the package name matches, false otherwise.
+         */
+        public boolean matches(String pkg) {
+            int state = 0;
+            int i;
+            final int len = pkg.length();
+            next: for (i = 0; i <= len; i++) {
+                if (state == WILDCARD_STATE) {
+                    return true; // all characters will match.
+                }
+                if (state == END_STATE) {
+                    return i == len;
+                }
+                final char[] ch = chars[state];
+                final int[] st = states[state];
+                if (i == len) {
+                    // matches only if we have exhausted the string.
+                    return st[0] == END_STATE;
+                }
+                if (st[0] == END_STATE && st.length == 1) {
+                    // matches only if we have exhausted the string.
+                    return i == len;
+                }
+                final char c = pkg.charAt(i); // look at next char...
+                for (int j = st[0] == END_STATE ? 1 : 0; j < ch.length; j++) {
+                    final char n = ch[j];
+                    if (c == n) {      // found a match
+                        state = st[j]; // get the next state.
+                        continue next; // go to next state
+                    } else if (c < n) {
+                        break; // chars are sorted. we won't find it. no match.
+                    }
+                }
+                break; // no match
+            }
+            return false;
+        }
+    }
+
+    private static final class TestPackageMatcher extends PackageMatcher {
+        private final List<String> list;
+
+        TestPackageMatcher(String[] packages) {
+            super(packages);
+            this.list = Collections.unmodifiableList(Arrays.asList(packages));
+        }
+
+        @Override
+        public boolean matches(String pkg) {
+            final boolean match1 = super.matches(pkg);
+            boolean match2 = false;
+            String p2 = pkg + ".";
+            for (String p : list) {
+                if (pkg.startsWith(p) || p2.equals(p)) {
+                    match2 = true;
+                    break;
+                }
+            }
+            if (match1 != match2) {
+                System.err.println("Test Bug: PackageMatcher.matches(\"" +
+                                   pkg + "\") returned " + match1);
+                System.err.println("Package Access List is: " + list);
+                throw new Error("Test Bug: PackageMatcher.matches(\"" +
+                                pkg + "\") returned " + match1);
+            }
+            return match1;
+        }
+    }
+
+    private static void smokeTest() {
+        // these checks should pass.
+        System.getSecurityManager().checkPackageAccess("com.sun.blah");
+        System.getSecurityManager().checkPackageAccess("com.sun.jm");
+        System.getSecurityManager().checkPackageAccess("com.sun.jmxa");
+        System.getSecurityManager().checkPackageAccess("jmx");
+        List<String> actual = Arrays.asList(packages);
+        for (String p : actual) {
+            if (!actual.contains(p)) {
+                System.err.println("Warning: '" + p + " not in package.access");
+            }
+        }
+        if (!actual.contains("sun.")) {
+            throw new Error("package.access does not contain 'sun.'");
+        }
+    }
+
+    // This is a sanity test for our own test code.
+    private static void testTheTest(String[] pkgs, char[][] chars,
+                                    int[][] states) {
+
+        PackageMatcher m = new TestPackageMatcher(pkgs);
+        String unexpected = "";
+        if (!Arrays.deepEquals(chars, m.chars)) {
+            System.err.println("Char arrays differ");
+            if (chars.length != m.chars.length) {
+                System.err.println("Char array lengths differ: expected="
+                        + chars.length + " actual=" + m.chars.length);
+            }
+            System.err.println(Arrays.deepToString(m.chars).replace((char)0,
+                                                   '0'));
+            unexpected = "chars[]";
+        }
+        if (!Arrays.deepEquals(states, m.states)) {
+            System.err.println("State arrays differ");
+            if (states.length != m.states.length) {
+                System.err.println("Char array lengths differ: expected="
+                        + states.length + " actual=" + m.states.length);
+            }
+            System.err.println(Arrays.deepToString(m.states));
+            if (unexpected.length() > 0) {
+                unexpected = unexpected + " and ";
+            }
+            unexpected = unexpected + "states[]";
+        }
+
+        if (unexpected.length() > 0) {
+            throw new Error("Unexpected "+unexpected+" in PackageMatcher");
+        }
+
+        testMatches(m, pkgs);
+    }
+
+    // This is a sanity test for our own test code.
+    private static void testTheTest() {
+        final String[] packages2 = { "sun.", "com.sun.jmx.",
+                                     "com.sun.proxy.", "apple." };
+
+        final int END_STATE = PackageMatcher.END_STATE;
+        final int WILDCARD_STATE = PackageMatcher.WILDCARD_STATE;
+
+        final char[][] chars2 = {
+            { 'a', 'c', 's' }, { 'p' }, { 'p' }, { 'l' }, { 'e' }, { 0, '.' },
+            { 'o' }, { 'm' }, { '.' }, { 's' }, { 'u' }, { 'n' }, { '.' },
+            { 'j', 'p'},
+            { 'm' }, { 'x' }, { 0, '.' },
+            { 'r' }, { 'o' }, { 'x' }, { 'y' }, { 0, '.' },
+            { 'u' }, { 'n' }, { 0, '.' }
+         };
+
+         final int[][] states2 = {
+            { 1, 6, 22 }, { 2 }, { 3 }, { 4 }, { 5 },
+            { END_STATE, WILDCARD_STATE },
+            { 7 }, { 8 }, { 9 }, { 10 }, { 11 }, { 12 }, { 13 }, { 14, 17 },
+            { 15 }, { 16 }, { END_STATE, WILDCARD_STATE },
+            { 18 }, { 19 }, { 20 }, { 21 }, { END_STATE, WILDCARD_STATE },
+            { 23 }, { 24 }, { END_STATE, WILDCARD_STATE }
+         };
+
+         testTheTest(packages2, chars2, states2);
+
+         final String[] packages3 = { "sun.", "com.sun.pro.",
+                                      "com.sun.proxy.", "apple." };
+
+         final char[][] chars3 = {
+            { 'a', 'c', 's' }, { 'p' }, { 'p' }, { 'l' }, { 'e' }, { 0, '.' },
+            { 'o' }, { 'm' }, { '.' }, { 's' }, { 'u' }, { 'n' }, { '.' },
+            { 'p' }, { 'r' }, { 'o' }, { 0, '.', 'x' },
+            { 'y' }, { 0, '.' },
+            { 'u' }, { 'n' }, { 0, '.' }
+         };
+
+         final int[][] states3 = {
+            { 1, 6, 19 }, { 2 }, { 3 }, { 4 }, { 5 },
+            { END_STATE, WILDCARD_STATE },
+            { 7 }, { 8 }, { 9 }, { 10 }, { 11 }, { 12 }, { 13 }, { 14 },
+            { 15 }, { 16 }, { END_STATE, WILDCARD_STATE, 17 },
+            { 18 }, { END_STATE, WILDCARD_STATE },
+            { 20 }, { 21 }, { END_STATE, WILDCARD_STATE }
+         };
+
+         testTheTest(packages3, chars3, states3);
+    }
+
+    private static volatile boolean sanityTesting = false;
+
+    public static void main(String[] args) {
+        System.setSecurityManager(new SecurityManager());
+
+        // Some smoke tests.
+        smokeTest();
+        System.out.println("Smoke tests passed.");
+
+        // Test our own pattern matching algorithm. Here we actually test
+        // the PackageMatcher class from our own test code.
+        sanityTesting = true;
+        try {
+            testTheTest();
+            System.out.println("Sanity tests passed.");
+        } finally {
+            sanityTesting = false;
+        }
+
+        // Now test the package matching in the security manager.
+        PackageMatcher matcher = new TestPackageMatcher(packages);
+
+        // These should not match.
+        for (String pkg : new String[] {"gloups.machin", "su",
+                                        "org.jcp.xml.dsig.interna",
+                                        "com.sun.jm", "com.sun.jmxa"}) {
+            testMatch(matcher, pkg, false, true);
+        }
+
+        // These should match.
+        for (String pkg : Arrays.asList(
+                new String[] {"sun.gloups.machin", "sun", "sun.com",
+                              "com.sun.jmx", "com.sun.jmx.a",
+                              "org.jcp.xml.dsig.internal",
+                              "org.jcp.xml.dsig.internal.foo"})) {
+            testMatch(matcher, pkg, true, true);
+        }
+
+        // Derive a list of packages that should match or not match from
+        // the list in 'packages' - and check that the security manager
+        // throws the appropriate exception.
+        testMatches(matcher, packages);
+    }
+
+    private static void testMatches(PackageMatcher matcher, String[] pkgs) {
+        Collection<String> pkglist = Arrays.asList(pkgs);
+        PackageMatcher ref = new TestPackageMatcher(packages);
+
+        for (String pkg : pkgs) {
+            String candidate = pkg + "toto";
+            boolean expected = true;
+            testMatch(matcher, candidate, expected,
+                      ref.matches(candidate) == expected);
+        }
+
+        for (String pkg : pkgs) {
+            String candidate = pkg.substring(0, pkg.length() - 1);
+            boolean expected = pkglist.contains(candidate + ".");
+            testMatch(matcher, candidate, expected,
+                      ref.matches(candidate) == expected);
+        }
+
+        for (String pkg : pkgs) {
+            if (!OPEN_JDK && pkg.equals("com.sun.media.sound.")) {
+                // don't test com.sun.media.sound since there is an entry
+                // for com.sun.media in non OpenJDK builds. Otherwise,
+                // the test for this package will fail unexpectedly.
+                continue;
+            }
+            String candidate = pkg.substring(0, pkg.length() - 2);
+            boolean expected = pkglist.contains(candidate + ".");
+            testMatch(matcher, candidate, expected,
+                      ref.matches(candidate) == expected);
+        }
+    }
+
+    private static void testMatch(PackageMatcher matcher, String candidate,
+                                  boolean expected, boolean testSecurityManager)
+    {
+        final boolean m = matcher.matches(candidate);
+        if (m != expected) {
+            final String msg = "\"" + candidate + "\": " +
+                (m ? "matches" : "does not match");
+            throw new Error("PackageMatcher does not give expected results: "
+                            + msg);
+        }
+
+        if (sanityTesting) {
+            testSecurityManager = false;
+        }
+
+        if (testSecurityManager) {
+            System.out.println("Access to " + candidate + " should be " +
+                               (expected ? "rejected" : "granted"));
+            final String errormsg = "\"" + candidate + "\" : " +
+                (expected ? "granted" : "not granted");
+            try {
+                System.getSecurityManager().checkPackageAccess(candidate);
+                if (expected) {
+                    System.err.println(errormsg);
+                    throw new Error("Expected exception not thrown: " +
+                                    errormsg);
+                }
+            } catch (SecurityException x) {
+                if (!expected) {
+                    System.err.println(errormsg);
+                    throw new Error(errormsg + " - unexpected exception: " +
+                                    x, x);
+                } else {
+                    System.out.println("Got expected exception: " + x);
+                }
+            }
+        }
+    }
+
+    private static boolean isOpenJDKOnly() {
+        String prop = System.getProperty("java.runtime.name");
+        return prop != null && prop.startsWith("OpenJDK");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/SecurityManager/RestrictedPackages.java	Wed Jun 17 15:47:12 2015 -0400
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * A collection of utility methods and constants for testing the package
+ * access and package definition security checks.
+ */
+final class RestrictedPackages {
+
+    /*
+     * The expected list of restricted packages.
+     *
+     * This array should be updated whenever new packages are added to the
+     * package.access property in the java.security file
+     * NOTE: it should be in the same order as the java.security file
+     */
+    static final String[] EXPECTED = {
+        "sun.",
+        "com.sun.xml.internal.",
+        "com.sun.imageio.",
+        "com.sun.istack.internal.",
+        "com.sun.jmx.",
+        "com.sun.media.sound.",
+        "com.sun.naming.internal.",
+        "com.sun.proxy.",
+        "com.sun.corba.se.",
+        "com.sun.org.apache.bcel.internal.",
+        "com.sun.org.apache.regexp.internal.",
+        "com.sun.org.apache.xerces.internal.",
+        "com.sun.org.apache.xpath.internal.",
+        "com.sun.org.apache.xalan.internal.extensions.",
+        "com.sun.org.apache.xalan.internal.lib.",
+        "com.sun.org.apache.xalan.internal.res.",
+        "com.sun.org.apache.xalan.internal.templates.",
+        "com.sun.org.apache.xalan.internal.utils.",
+        "com.sun.org.apache.xalan.internal.xslt.",
+        "com.sun.org.apache.xalan.internal.xsltc.cmdline.",
+        "com.sun.org.apache.xalan.internal.xsltc.compiler.",
+        "com.sun.org.apache.xalan.internal.xsltc.trax.",
+        "com.sun.org.apache.xalan.internal.xsltc.util.",
+        "com.sun.org.apache.xml.internal.res.",
+        "com.sun.org.apache.xml.internal.security.",
+        "com.sun.org.apache.xml.internal.serializer.utils.",
+        "com.sun.org.apache.xml.internal.utils.",
+        "com.sun.org.glassfish.",
+        "com.sun.tools.script.",
+        "com.oracle.xmlns.internal.",
+        "com.oracle.webservices.internal.",
+        "org.jcp.xml.dsig.internal.",
+        "jdk.internal.",
+        "jdk.nashorn.internal.",
+        "jdk.nashorn.tools.",
+        "jdk.tools.jimage.",
+        "com.sun.activation.registries."
+    };
+
+    /*
+     * A non-exhaustive list of restricted packages.
+     *
+     * Contrary to what is in the EXPECTED list, this list does not need
+     * to be exhaustive.
+     */
+    static final String[] EXPECTED_NONEXHAUSTIVE = {
+        "sun.",
+        "com.sun.xml.internal.",
+        "com.sun.imageio.",
+        "com.sun.istack.internal.",
+        "com.sun.jmx.",
+        "com.sun.proxy.",
+        "com.sun.org.apache.bcel.internal.",
+        "com.sun.org.apache.regexp.internal.",
+        "com.sun.org.apache.xerces.internal.",
+        "com.sun.org.apache.xpath.internal.",
+        "com.sun.org.apache.xalan.internal.extensions.",
+        "com.sun.org.apache.xalan.internal.lib.",
+        "com.sun.org.apache.xalan.internal.res.",
+        "com.sun.org.apache.xalan.internal.templates.",
+        "com.sun.org.apache.xalan.internal.utils.",
+        "com.sun.org.apache.xalan.internal.xslt.",
+        "com.sun.org.apache.xalan.internal.xsltc.cmdline.",
+        "com.sun.org.apache.xalan.internal.xsltc.compiler.",
+        "com.sun.org.apache.xalan.internal.xsltc.trax.",
+        "com.sun.org.apache.xalan.internal.xsltc.util.",
+        "com.sun.org.apache.xml.internal.res.",
+        "com.sun.org.apache.xml.internal.serializer.utils.",
+        "com.sun.org.apache.xml.internal.utils.",
+        "com.sun.org.apache.xml.internal.security.",
+        "com.sun.org.glassfish.",
+        "org.jcp.xml.dsig.internal."
+    };
+
+    private static final String OS_NAME = System.getProperty("os.name");
+
+    /**
+     * Returns a list of expected restricted packages, including any
+     * OS specific packages. The returned list is mutable.
+     */
+    static List<String> expected() {
+        List<String> pkgs = new ArrayList<>(Arrays.asList(EXPECTED));
+        if (OS_NAME.contains("OS X")) {
+            pkgs.add("apple.");  // add apple package for OS X
+        }
+        return pkgs;
+    }
+
+    /**
+     * Returns a list of actual restricted packages. The returned list
+     * is mutable.
+     */
+    static List<String> actual() {
+        String prop = Security.getProperty("package.access");
+        List<String> packages = new ArrayList<>();
+        if (prop != null && !prop.equals("")) {
+            StringTokenizer tok = new StringTokenizer(prop, ",");
+            while (tok.hasMoreElements()) {
+                String s = tok.nextToken().trim();
+                packages.add(s);
+            }
+        }
+        return packages;
+    }
+
+    private RestrictedPackages() { }
+}