AccessControlTest.java revision 8729:0242fce0f717
1/*
2 * Copyright (c) 2012, 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/* @test
25 * @summary test access checking by java.lang.invoke.MethodHandles.Lookup
26 * @library ../../../..
27 * @build test.java.lang.invoke.AccessControlTest
28 * @build test.java.lang.invoke.AccessControlTest_subpkg.Acquaintance_remote
29 * @run testng/othervm test.java.lang.invoke.AccessControlTest
30 */
31
32package test.java.lang.invoke;
33
34import java.lang.invoke.*;
35import java.lang.reflect.*;
36import java.util.*;
37import org.testng.*;
38import org.testng.annotations.*;
39
40import static java.lang.invoke.MethodHandles.*;
41import static java.lang.invoke.MethodHandles.Lookup.*;
42import static java.lang.invoke.MethodType.*;
43import static org.testng.Assert.*;
44
45import test.java.lang.invoke.AccessControlTest_subpkg.Acquaintance_remote;
46
47
48/**
49 * Test many combinations of Lookup access and cross-class lookupStatic.
50 * @author jrose
51 */
52public class AccessControlTest {
53    static final Class<?> THIS_CLASS = AccessControlTest.class;
54    // How much output?
55    static int verbosity = 0;
56    static {
57        String vstr = System.getProperty(THIS_CLASS.getSimpleName()+".verbosity");
58        if (vstr == null)
59            vstr = System.getProperty(THIS_CLASS.getName()+".verbosity");
60        if (vstr != null)  verbosity = Integer.parseInt(vstr);
61    }
62
63    private class LookupCase implements Comparable<LookupCase> {
64        final Lookup   lookup;
65        final Class<?> lookupClass;
66        final int      lookupModes;
67        public LookupCase(Lookup lookup) {
68            this.lookup = lookup;
69            this.lookupClass = lookup.lookupClass();
70            this.lookupModes = lookup.lookupModes();
71            assert(lookupString().equals(lookup.toString()));
72            numberOf(lookupClass().getClassLoader()); // assign CL#
73        }
74        public LookupCase(Class<?> lookupClass, int lookupModes) {
75            this.lookup = null;
76            this.lookupClass = lookupClass;
77            this.lookupModes = lookupModes;
78            numberOf(lookupClass().getClassLoader()); // assign CL#
79        }
80
81        public final Class<?> lookupClass() { return lookupClass; }
82        public final int      lookupModes() { return lookupModes; }
83
84        public Lookup lookup() { lookup.getClass(); return lookup; }
85
86        @Override
87        public int compareTo(LookupCase that) {
88            Class<?> c1 = this.lookupClass();
89            Class<?> c2 = that.lookupClass();
90            if (c1 != c2) {
91                int cmp = c1.getName().compareTo(c2.getName());
92                if (cmp != 0)  return cmp;
93                cmp = numberOf(c1.getClassLoader()) - numberOf(c2.getClassLoader());
94                assert(cmp != 0);
95                return cmp;
96            }
97            return -(this.lookupModes() - that.lookupModes());
98        }
99
100        @Override
101        public boolean equals(Object that) {
102            return (that instanceof LookupCase && equals((LookupCase)that));
103        }
104        public boolean equals(LookupCase that) {
105            return (this.lookupClass() == that.lookupClass() &&
106                    this.lookupModes() == that.lookupModes());
107        }
108
109        @Override
110        public int hashCode() {
111            return lookupClass().hashCode() + (lookupModes() * 31);
112        }
113
114        /** Simulate all assertions in the spec. for Lookup.toString. */
115        private String lookupString() {
116            String name = lookupClass.getName();
117            String suffix = "";
118            if (lookupModes == 0)
119                suffix = "/noaccess";
120            else if (lookupModes == PUBLIC)
121                suffix = "/public";
122            else if (lookupModes == (PUBLIC|PACKAGE))
123                suffix = "/package";
124            else if (lookupModes == (PUBLIC|PACKAGE|PRIVATE))
125                suffix = "/private";
126            else if (lookupModes == (PUBLIC|PACKAGE|PRIVATE|PROTECTED))
127                suffix = "";
128            else
129                suffix = "/#"+Integer.toHexString(lookupModes);
130            return name+suffix;
131        }
132
133        /** Simulate all assertions from the spec. for Lookup.in:
134         * <hr>
135         * Creates a lookup on the specified new lookup class.
136         * [A1] The resulting object will report the specified
137         * class as its own {@link #lookupClass lookupClass}.
138         * <p>
139         * [A2] However, the resulting {@code Lookup} object is guaranteed
140         * to have no more access capabilities than the original.
141         * In particular, access capabilities can be lost as follows:<ul>
142         * <li>[A3] If the new lookup class differs from the old one,
143         * protected members will not be accessible by virtue of inheritance.
144         * (Protected members may continue to be accessible because of package sharing.)
145         * <li>[A4] If the new lookup class is in a different package
146         * than the old one, protected and default (package) members will not be accessible.
147         * <li>[A5] If the new lookup class is not within the same package member
148         * as the old one, private members will not be accessible.
149         * <li>[A6] If the new lookup class is not accessible to the old lookup class,
150         * using the original access modes,
151         * then no members, not even public members, will be accessible.
152         * [A7] (In all other cases, public members will continue to be accessible.)
153         * </ul>
154         * Other than the above cases, the new lookup will have the same
155         * access capabilities as the original. [A8]
156         * <hr>
157         */
158        public LookupCase in(Class<?> c2) {
159            Class<?> c1 = lookupClass();
160            int m1 = lookupModes();
161            int changed = 0;
162            boolean samePackage = (c1.getClassLoader() == c2.getClassLoader() &&
163                                   packagePrefix(c1).equals(packagePrefix(c2)));
164            boolean sameTopLevel = (topLevelClass(c1) == topLevelClass(c2));
165            boolean sameClass = (c1 == c2);
166            assert(samePackage  || !sameTopLevel);
167            assert(sameTopLevel || !sameClass);
168            boolean accessible = sameClass;  // [A6]
169            if ((m1 & PACKAGE) != 0)  accessible |= samePackage;
170            if ((m1 & PUBLIC ) != 0)  accessible |= (c2.getModifiers() & PUBLIC) != 0;
171            if (!accessible) {
172                // Different package and no access to c2; lose all access.
173                changed |= (PUBLIC|PACKAGE|PRIVATE|PROTECTED);  // [A6]
174            }
175            if (!samePackage) {
176                // Different package; lose PACKAGE and lower access.
177                changed |= (PACKAGE|PRIVATE|PROTECTED);  // [A4]
178            }
179            if (!sameTopLevel) {
180                // Different top-level class.  Lose PRIVATE and lower access.
181                changed |= (PRIVATE|PROTECTED);  // [A5]
182            }
183            if (!sameClass) {
184                changed |= (PROTECTED);     // [A3]
185            } else {
186                assert(changed == 0);       // [A8] (no deprivation if same class)
187            }
188            if (accessible)  assert((changed & PUBLIC) == 0);  // [A7]
189            int m2 = m1 & ~changed;
190            LookupCase l2 = new LookupCase(c2, m2);
191            assert(l2.lookupClass() == c2); // [A1]
192            assert((m1 | m2) == m1);        // [A2] (no elevation of access)
193            return l2;
194        }
195
196        @Override
197        public String toString() {
198            String s = lookupClass().getSimpleName();
199            String lstr = lookupString();
200            int sl = lstr.indexOf('/');
201            if (sl >= 0)  s += lstr.substring(sl);
202            ClassLoader cld = lookupClass().getClassLoader();
203            if (cld != THIS_LOADER)  s += "/loader#"+numberOf(cld);
204            return s;
205        }
206
207        /** Predict the success or failure of accessing this method. */
208        public boolean willAccess(Method m) {
209            Class<?> c1 = lookupClass();
210            Class<?> c2 = m.getDeclaringClass();
211            LookupCase lc = this.in(c2);
212            int m1 = lc.lookupModes();
213            int m2 = fixMods(m.getModifiers());
214            // privacy is strictly enforced on lookups
215            if (c1 != c2)  m1 &= ~PRIVATE;
216            // protected access is sometimes allowed
217            if ((m2 & PROTECTED) != 0) {
218                int prev = m2;
219                m2 |= PACKAGE;  // it acts like a package method also
220                if ((lookupModes() & PROTECTED) != 0 &&
221                    c2.isAssignableFrom(c1))
222                    m2 |= PUBLIC;  // from a subclass, it acts like a public method also
223            }
224            if (verbosity >= 2)
225                System.out.println(this+" willAccess "+lc+" m1="+m1+" m2="+m2+" => "+((m2 & m1) != 0));
226            return (m2 & m1) != 0;
227        }
228    }
229
230    private static Class<?> topLevelClass(Class<?> cls) {
231        Class<?> c = cls;
232        for (Class<?> ec; (ec = c.getEnclosingClass()) != null; )
233            c = ec;
234        assert(c.getEnclosingClass() == null);
235        assert(c == cls || cls.getEnclosingClass() != null);
236        return c;
237    }
238
239    private static String packagePrefix(Class<?> c) {
240        while (c.isArray())  c = c.getComponentType();
241        String s = c.getName();
242        assert(s.indexOf('/') < 0);
243        return s.substring(0, s.lastIndexOf('.')+1);
244    }
245
246
247    private final TreeSet<LookupCase> CASES = new TreeSet<>();
248    private final TreeMap<LookupCase,TreeSet<LookupCase>> CASE_EDGES = new TreeMap<>();
249    private final ArrayList<ClassLoader> LOADERS = new ArrayList<>();
250    private final ClassLoader THIS_LOADER = this.getClass().getClassLoader();
251    { if (THIS_LOADER != null)  LOADERS.add(THIS_LOADER); }  // #1
252
253    private LookupCase lookupCase(String name) {
254        for (LookupCase lc : CASES) {
255            if (lc.toString().equals(name))
256                return lc;
257        }
258        throw new AssertionError(name);
259    }
260
261    private int numberOf(ClassLoader cl) {
262        if (cl == null)  return 0;
263        int i = LOADERS.indexOf(cl);
264        if (i < 0) {
265            i = LOADERS.size();
266            LOADERS.add(cl);
267        }
268        return i+1;
269    }
270
271    private void addLookupEdge(LookupCase l1, Class<?> c2, LookupCase l2) {
272        TreeSet<LookupCase> edges = CASE_EDGES.get(l2);
273        if (edges == null)  CASE_EDGES.put(l2, edges = new TreeSet<>());
274        if (edges.add(l1)) {
275            Class<?> c1 = l1.lookupClass();
276            assert(l2.lookupClass() == c2); // [A1]
277            int m1 = l1.lookupModes();
278            int m2 = l2.lookupModes();
279            assert((m1 | m2) == m1);        // [A2] (no elevation of access)
280            LookupCase expect = l1.in(c2);
281            if (!expect.equals(l2))
282                System.out.println("*** expect "+l1+" => "+expect+" but got "+l2);
283            assertEquals(expect, l2);
284        }
285    }
286
287    private void makeCases(Lookup[] originalLookups) {
288        // make initial set of lookup test cases
289        CASES.clear(); LOADERS.clear(); CASE_EDGES.clear();
290        ArrayList<Class<?>> classes = new ArrayList<>();
291        for (Lookup l : originalLookups) {
292            CASES.add(new LookupCase(l));
293            classes.remove(l.lookupClass());  // no dups please
294            classes.add(l.lookupClass());
295        }
296        System.out.println("loaders = "+LOADERS);
297        int rounds = 0;
298        for (int lastCount = -1; lastCount != CASES.size(); ) {
299            lastCount = CASES.size();  // if CASES grow in the loop we go round again
300            for (LookupCase lc1 : CASES.toArray(new LookupCase[0])) {
301                for (Class<?> c2 : classes) {
302                    LookupCase lc2 = new LookupCase(lc1.lookup().in(c2));
303                    addLookupEdge(lc1, c2, lc2);
304                    CASES.add(lc2);
305                }
306            }
307            rounds++;
308        }
309        System.out.println("filled in "+CASES.size()+" cases from "+originalLookups.length+" original cases in "+rounds+" rounds");
310        if (false) {
311            System.out.println("CASES: {");
312            for (LookupCase lc : CASES) {
313                System.out.println(lc);
314                Set<LookupCase> edges = CASE_EDGES.get(lc);
315                if (edges != null)
316                    for (LookupCase prev : edges) {
317                        System.out.println("\t"+prev);
318                    }
319            }
320            System.out.println("}");
321        }
322    }
323
324    @Test public void test() {
325        makeCases(lookups());
326        if (verbosity > 0) {
327            verbosity += 9;
328            Method pro_in_self = targetMethod(THIS_CLASS, PROTECTED, methodType(void.class));
329            testOneAccess(lookupCase("AccessControlTest/public"),  pro_in_self, "find");
330            testOneAccess(lookupCase("Remote_subclass/public"),    pro_in_self, "find");
331            testOneAccess(lookupCase("Remote_subclass"),           pro_in_self, "find");
332            verbosity -= 9;
333        }
334        Set<Class<?>> targetClassesDone = new HashSet<>();
335        for (LookupCase targetCase : CASES) {
336            Class<?> targetClass = targetCase.lookupClass();
337            if (!targetClassesDone.add(targetClass))  continue;  // already saw this one
338            String targetPlace = placeName(targetClass);
339            if (targetPlace == null)  continue;  // Object, String, not a target
340            for (int targetAccess : ACCESS_CASES) {
341                MethodType methodType = methodType(void.class);
342                Method method = targetMethod(targetClass, targetAccess, methodType);
343                // Try to access target method from various contexts.
344                for (LookupCase sourceCase : CASES) {
345                    testOneAccess(sourceCase, method, "find");
346                    testOneAccess(sourceCase, method, "unreflect");
347                }
348            }
349        }
350        System.out.println("tested "+testCount+" access scenarios; "+testCountFails+" accesses were denied");
351    }
352
353    private int testCount, testCountFails;
354
355    private void testOneAccess(LookupCase sourceCase, Method method, String kind) {
356        Class<?> targetClass = method.getDeclaringClass();
357        String methodName = method.getName();
358        MethodType methodType = methodType(method.getReturnType(), method.getParameterTypes());
359        boolean willAccess = sourceCase.willAccess(method);
360        boolean didAccess = false;
361        ReflectiveOperationException accessError = null;
362        try {
363            switch (kind) {
364            case "find":
365                if ((method.getModifiers() & Modifier.STATIC) != 0)
366                    sourceCase.lookup().findStatic(targetClass, methodName, methodType);
367                else
368                    sourceCase.lookup().findVirtual(targetClass, methodName, methodType);
369                break;
370            case "unreflect":
371                sourceCase.lookup().unreflect(method);
372                break;
373            default:
374                throw new AssertionError(kind);
375            }
376            didAccess = true;
377        } catch (ReflectiveOperationException ex) {
378            accessError = ex;
379        }
380        if (willAccess != didAccess) {
381            System.out.println(sourceCase+" => "+targetClass.getSimpleName()+"."+methodName+methodType);
382            System.out.println("fail on "+method+" ex="+accessError);
383            assertEquals(willAccess, didAccess);
384        }
385        testCount++;
386        if (!didAccess)  testCountFails++;
387    }
388
389    static Method targetMethod(Class<?> targetClass, int targetAccess, MethodType methodType) {
390        String methodName = accessName(targetAccess)+placeName(targetClass);
391        if (verbosity >= 2)
392            System.out.println(targetClass.getSimpleName()+"."+methodName+methodType);
393        try {
394            Method method = targetClass.getDeclaredMethod(methodName, methodType.parameterArray());
395            assertEquals(method.getReturnType(), methodType.returnType());
396            int haveMods = method.getModifiers();
397            assert(Modifier.isStatic(haveMods));
398            assert(targetAccess == fixMods(haveMods));
399            return method;
400        } catch (NoSuchMethodException ex) {
401            throw new AssertionError(methodName, ex);
402        }
403    }
404
405    static String placeName(Class<?> cls) {
406        // return "self", "sibling", "nestmate", etc.
407        if (cls == AccessControlTest.class)  return "self";
408        String cln = cls.getSimpleName();
409        int under = cln.lastIndexOf('_');
410        if (under < 0)  return null;
411        return cln.substring(under+1);
412    }
413    static String accessName(int acc) {
414        switch (acc) {
415        case PUBLIC:     return "pub_in_";
416        case PROTECTED:  return "pro_in_";
417        case PACKAGE:    return "pkg_in_";
418        case PRIVATE:    return "pri_in_";
419        }
420        assert(false);
421        return "?";
422    }
423    private static final int[] ACCESS_CASES = {
424        PUBLIC, PACKAGE, PRIVATE, PROTECTED
425    };
426    /** Return one of the ACCESS_CASES. */
427    static int fixMods(int mods) {
428        mods &= (PUBLIC|PRIVATE|PROTECTED);
429        switch (mods) {
430        case PUBLIC: case PRIVATE: case PROTECTED: return mods;
431        case 0:  return PACKAGE;
432        }
433        throw new AssertionError(mods);
434    }
435
436    static Lookup[] lookups() {
437        ArrayList<Lookup> tem = new ArrayList<>();
438        Collections.addAll(tem,
439                           AccessControlTest.lookup_in_self(),
440                           Inner_nestmate.lookup_in_nestmate(),
441                           AccessControlTest_sibling.lookup_in_sibling());
442        if (true) {
443            Collections.addAll(tem,Acquaintance_remote.lookups());
444        } else {
445            try {
446                Class<?> remc = Class.forName("test.java.lang.invoke.AccessControlTest_subpkg.Acquaintance_remote");
447                Lookup[] remls = (Lookup[]) remc.getMethod("lookups").invoke(null);
448                Collections.addAll(tem, remls);
449            } catch (ReflectiveOperationException ex) {
450                throw new LinkageError("reflection failed", ex);
451            }
452        }
453        tem.add(publicLookup());
454        tem.add(publicLookup().in(String.class));
455        tem.add(publicLookup().in(List.class));
456        return tem.toArray(new Lookup[0]);
457    }
458
459    static Lookup lookup_in_self() {
460        return MethodHandles.lookup();
461    }
462    static public      void pub_in_self() { }
463    static protected   void pro_in_self() { }
464    static /*package*/ void pkg_in_self() { }
465    static private     void pri_in_self() { }
466
467    static class Inner_nestmate {
468        static Lookup lookup_in_nestmate() {
469            return MethodHandles.lookup();
470        }
471        static public      void pub_in_nestmate() { }
472        static protected   void pro_in_nestmate() { }
473        static /*package*/ void pkg_in_nestmate() { }
474        static private     void pri_in_nestmate() { }
475    }
476}
477class AccessControlTest_sibling {
478    static Lookup lookup_in_sibling() {
479        return MethodHandles.lookup();
480    }
481    static public      void pub_in_sibling() { }
482    static protected   void pro_in_sibling() { }
483    static /*package*/ void pkg_in_sibling() { }
484    static private     void pri_in_sibling() { }
485}
486
487// This guy tests access from outside the package:
488/*
489package test.java.lang.invoke.AccessControlTest_subpkg;
490public class Acquaintance_remote {
491    public static Lookup[] lookups() { ...
492    }
493    ...
494}
495*/
496