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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /* @test
  27  * @summary test access checking by java.lang.invoke.MethodHandles.Lookup
  28  * @library ../../../..
  29  * @build test.java.lang.invoke.AccessControlTest
  30  * @build test.java.lang.invoke.AccessControlTest_subpkg.Acquaintance_remote
  31  * @run junit/othervm test.java.lang.invoke.AccessControlTest
  32  */
  33 
  34 package test.java.lang.invoke;
  35 
  36 import java.lang.invoke.*;
  37 import java.lang.reflect.*;
  38 import java.util.*;
  39 import org.junit.*;
  40 
  41 import static java.lang.invoke.MethodHandles.*;
  42 import static java.lang.invoke.MethodHandles.Lookup.*;
  43 import static java.lang.invoke.MethodType.*;
  44 import static org.junit.Assert.*;
  45 import 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  */
  52 public 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 }
 477 class 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 /*
 489 package test.java.lang.invoke.AccessControlTest_subpkg;
 490 public class Acquaintance_remote {
 491     public static Lookup[] lookups() { ...
 492     }
 493     ...
 494 }
 495 */