Home | History | Annotate | Download | only in src-ex
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 import dalvik.system.VMRuntime;
     18 import java.lang.invoke.MethodHandles;
     19 import java.lang.invoke.MethodType;
     20 import java.util.function.Consumer;
     21 
     22 public class ChildClass {
     23   enum PrimitiveType {
     24     TInteger('I', Integer.TYPE, Integer.valueOf(0)),
     25     TLong('J', Long.TYPE, Long.valueOf(0)),
     26     TFloat('F', Float.TYPE, Float.valueOf(0)),
     27     TDouble('D', Double.TYPE, Double.valueOf(0)),
     28     TBoolean('Z', Boolean.TYPE, Boolean.valueOf(false)),
     29     TByte('B', Byte.TYPE, Byte.valueOf((byte) 0)),
     30     TShort('S', Short.TYPE, Short.valueOf((short) 0)),
     31     TCharacter('C', Character.TYPE, Character.valueOf('0'));
     32 
     33     PrimitiveType(char shorty, Class klass, Object value) {
     34       mShorty = shorty;
     35       mClass = klass;
     36       mDefaultValue = value;
     37     }
     38 
     39     public char mShorty;
     40     public Class mClass;
     41     public Object mDefaultValue;
     42   }
     43 
     44   enum Hiddenness {
     45     Whitelist(PrimitiveType.TShort),
     46     LightGreylist(PrimitiveType.TBoolean),
     47     DarkGreylist(PrimitiveType.TByte),
     48     Blacklist(PrimitiveType.TCharacter);
     49 
     50     Hiddenness(PrimitiveType type) { mAssociatedType = type; }
     51     public PrimitiveType mAssociatedType;
     52   }
     53 
     54   enum Visibility {
     55     Public(PrimitiveType.TInteger),
     56     Package(PrimitiveType.TFloat),
     57     Protected(PrimitiveType.TLong),
     58     Private(PrimitiveType.TDouble);
     59 
     60     Visibility(PrimitiveType type) { mAssociatedType = type; }
     61     public PrimitiveType mAssociatedType;
     62   }
     63 
     64   enum Behaviour {
     65     Granted,
     66     Warning,
     67     Denied,
     68   }
     69 
     70   private static final boolean booleanValues[] = new boolean[] { false, true };
     71 
     72   public static void runTest(String libFileName, boolean expectedParentInBoot,
     73       boolean expectedChildInBoot, boolean everythingWhitelisted) throws Exception {
     74     System.load(libFileName);
     75 
     76     // Check expectations about loading into boot class path.
     77     isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null);
     78     if (isParentInBoot != expectedParentInBoot) {
     79       throw new RuntimeException("Expected ParentClass " +
     80                                  (expectedParentInBoot ? "" : "not ") + "in boot class path");
     81     }
     82     isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null);
     83     if (isChildInBoot != expectedChildInBoot) {
     84       throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") +
     85                                  "in boot class path");
     86     }
     87     ChildClass.everythingWhitelisted = everythingWhitelisted;
     88 
     89     boolean isSameBoot = (isParentInBoot == isChildInBoot);
     90     boolean isDebuggable = VMRuntime.getRuntime().isJavaDebuggable();
     91 
     92     // Run meaningful combinations of access flags.
     93     for (Hiddenness hiddenness : Hiddenness.values()) {
     94       final Behaviour expected;
     95       // Warnings are now disabled whenever access is granted, even for
     96       // greylisted APIs. This is the behaviour for release builds.
     97       if (isSameBoot || everythingWhitelisted || hiddenness == Hiddenness.Whitelist) {
     98         expected = Behaviour.Granted;
     99       } else if (hiddenness == Hiddenness.Blacklist) {
    100         expected = Behaviour.Denied;
    101       } else if (isDebuggable) {
    102         expected = Behaviour.Warning;
    103       } else {
    104         expected = Behaviour.Granted;
    105       }
    106 
    107       for (boolean isStatic : booleanValues) {
    108         String suffix = (isStatic ? "Static" : "") + hiddenness.name();
    109 
    110         for (Visibility visibility : Visibility.values()) {
    111           // Test reflection and JNI on methods and fields
    112           for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) {
    113             String baseName = visibility.name() + suffix;
    114             checkField(klass, "field" + baseName, isStatic, visibility, expected);
    115             checkMethod(klass, "method" + baseName, isStatic, visibility, expected);
    116           }
    117 
    118           // Check whether one can use a class constructor.
    119           checkConstructor(ParentClass.class, visibility, hiddenness, expected);
    120 
    121           // Check whether one can use an interface default method.
    122           String name = "method" + visibility.name() + "Default" + hiddenness.name();
    123           checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected);
    124         }
    125 
    126         // Test whether static linking succeeds.
    127         checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected);
    128         checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected);
    129         checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected);
    130         checkLinking("LinkMethodInterface" + suffix, /*takesParameter*/ false, expected);
    131       }
    132 
    133       // Check whether Class.newInstance succeeds.
    134       checkNullaryConstructor(Class.forName("NullaryConstructor" + hiddenness.name()), expected);
    135     }
    136   }
    137 
    138   static final class RecordingConsumer implements Consumer<String> {
    139       public String recordedValue = null;
    140 
    141       @Override
    142       public void accept(String value) {
    143           recordedValue = value;
    144       }
    145   }
    146 
    147   private static void checkMemberCallback(Class<?> klass, String name,
    148           boolean isPublic, boolean isField) {
    149       try {
    150           RecordingConsumer consumer = new RecordingConsumer();
    151           VMRuntime.setNonSdkApiUsageConsumer(consumer);
    152           try {
    153               if (isPublic) {
    154                   if (isField) {
    155                       klass.getField(name);
    156                   } else {
    157                       klass.getMethod(name);
    158                   }
    159               } else {
    160                   if (isField) {
    161                       klass.getDeclaredField(name);
    162                   } else {
    163                       klass.getDeclaredMethod(name);
    164                   }
    165               }
    166           } catch (NoSuchFieldException|NoSuchMethodException ignored) {
    167               // We're not concerned whether an exception is thrown or not - we're
    168               // only interested in whether the callback is invoked.
    169           }
    170 
    171           if (consumer.recordedValue == null || !consumer.recordedValue.contains(name)) {
    172               throw new RuntimeException("No callback for member: " + name);
    173           }
    174       } finally {
    175           VMRuntime.setNonSdkApiUsageConsumer(null);
    176       }
    177   }
    178 
    179   private static void checkField(Class<?> klass, String name, boolean isStatic,
    180       Visibility visibility, Behaviour behaviour) throws Exception {
    181 
    182     boolean isPublic = (visibility == Visibility.Public);
    183     boolean canDiscover = (behaviour != Behaviour.Denied);
    184     boolean setsWarning = (behaviour == Behaviour.Warning);
    185 
    186     if (klass.isInterface() && (!isStatic || !isPublic)) {
    187       // Interfaces only have public static fields.
    188       return;
    189     }
    190 
    191     // Test discovery with reflection.
    192 
    193     if (Reflection.canDiscoverWithGetDeclaredField(klass, name) != canDiscover) {
    194       throwDiscoveryException(klass, name, true, "getDeclaredField()", canDiscover);
    195     }
    196 
    197     if (Reflection.canDiscoverWithGetDeclaredFields(klass, name) != canDiscover) {
    198       throwDiscoveryException(klass, name, true, "getDeclaredFields()", canDiscover);
    199     }
    200 
    201     if (Reflection.canDiscoverWithGetField(klass, name) != (canDiscover && isPublic)) {
    202       throwDiscoveryException(klass, name, true, "getField()", (canDiscover && isPublic));
    203     }
    204 
    205     if (Reflection.canDiscoverWithGetFields(klass, name) != (canDiscover && isPublic)) {
    206       throwDiscoveryException(klass, name, true, "getFields()", (canDiscover && isPublic));
    207     }
    208 
    209     // Test discovery with JNI.
    210 
    211     if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) {
    212       throwDiscoveryException(klass, name, true, "JNI", canDiscover);
    213     }
    214 
    215     // Test discovery with MethodHandles.lookup() which is caller
    216     // context sensitive.
    217 
    218     final MethodHandles.Lookup lookup = MethodHandles.lookup();
    219     if (JLI.canDiscoverWithLookupFindGetter(lookup, klass, name, int.class)
    220         != canDiscover) {
    221       throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findGetter()",
    222                               canDiscover);
    223     }
    224     if (JLI.canDiscoverWithLookupFindStaticGetter(lookup, klass, name, int.class)
    225         != canDiscover) {
    226       throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findStaticGetter()",
    227                               canDiscover);
    228     }
    229 
    230     // Test discovery with MethodHandles.publicLookup() which can only
    231     // see public fields. Looking up setters here and fields in
    232     // interfaces are implicitly final.
    233 
    234     final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
    235     if (JLI.canDiscoverWithLookupFindSetter(publicLookup, klass, name, int.class)
    236         != canDiscover) {
    237       throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findSetter()",
    238                               canDiscover);
    239     }
    240     if (JLI.canDiscoverWithLookupFindStaticSetter(publicLookup, klass, name, int.class)
    241         != canDiscover) {
    242       throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findStaticSetter()",
    243                               canDiscover);
    244     }
    245 
    246     // Finish here if we could not discover the field.
    247 
    248     if (canDiscover) {
    249       // Test that modifiers are unaffected.
    250 
    251       if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) {
    252         throwModifiersException(klass, name, true);
    253       }
    254 
    255       // Test getters and setters when meaningful.
    256 
    257       clearWarning();
    258       if (!Reflection.canGetField(klass, name)) {
    259         throwAccessException(klass, name, true, "Field.getInt()");
    260       }
    261       if (hasPendingWarning() != setsWarning) {
    262         throwWarningException(klass, name, true, "Field.getInt()", setsWarning);
    263       }
    264 
    265       clearWarning();
    266       if (!Reflection.canSetField(klass, name)) {
    267         throwAccessException(klass, name, true, "Field.setInt()");
    268       }
    269       if (hasPendingWarning() != setsWarning) {
    270         throwWarningException(klass, name, true, "Field.setInt()", setsWarning);
    271       }
    272 
    273       clearWarning();
    274       if (!JNI.canGetField(klass, name, isStatic)) {
    275         throwAccessException(klass, name, true, "getIntField");
    276       }
    277       if (hasPendingWarning() != setsWarning) {
    278         throwWarningException(klass, name, true, "getIntField", setsWarning);
    279       }
    280 
    281       clearWarning();
    282       if (!JNI.canSetField(klass, name, isStatic)) {
    283         throwAccessException(klass, name, true, "setIntField");
    284       }
    285       if (hasPendingWarning() != setsWarning) {
    286         throwWarningException(klass, name, true, "setIntField", setsWarning);
    287       }
    288     }
    289 
    290     // Test that callbacks are invoked correctly.
    291     clearWarning();
    292     if (setsWarning || !canDiscover) {
    293       checkMemberCallback(klass, name, isPublic, true /* isField */);
    294     }
    295   }
    296 
    297   private static void checkMethod(Class<?> klass, String name, boolean isStatic,
    298       Visibility visibility, Behaviour behaviour) throws Exception {
    299 
    300     boolean isPublic = (visibility == Visibility.Public);
    301     if (klass.isInterface() && !isPublic) {
    302       // All interface members are public.
    303       return;
    304     }
    305 
    306     boolean canDiscover = (behaviour != Behaviour.Denied);
    307     boolean setsWarning = (behaviour == Behaviour.Warning);
    308 
    309     // Test discovery with reflection.
    310 
    311     if (Reflection.canDiscoverWithGetDeclaredMethod(klass, name) != canDiscover) {
    312       throwDiscoveryException(klass, name, false, "getDeclaredMethod()", canDiscover);
    313     }
    314 
    315     if (Reflection.canDiscoverWithGetDeclaredMethods(klass, name) != canDiscover) {
    316       throwDiscoveryException(klass, name, false, "getDeclaredMethods()", canDiscover);
    317     }
    318 
    319     if (Reflection.canDiscoverWithGetMethod(klass, name) != (canDiscover && isPublic)) {
    320       throwDiscoveryException(klass, name, false, "getMethod()", (canDiscover && isPublic));
    321     }
    322 
    323     if (Reflection.canDiscoverWithGetMethods(klass, name) != (canDiscover && isPublic)) {
    324       throwDiscoveryException(klass, name, false, "getMethods()", (canDiscover && isPublic));
    325     }
    326 
    327     // Test discovery with JNI.
    328 
    329     if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) {
    330       throwDiscoveryException(klass, name, false, "JNI", canDiscover);
    331     }
    332 
    333     // Test discovery with MethodHandles.lookup().
    334 
    335     final MethodHandles.Lookup lookup = MethodHandles.lookup();
    336     final MethodType methodType = MethodType.methodType(int.class);
    337     if (JLI.canDiscoverWithLookupFindVirtual(lookup, klass, name, methodType) != canDiscover) {
    338       throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findVirtual()",
    339                               canDiscover);
    340     }
    341 
    342     if (JLI.canDiscoverWithLookupFindStatic(lookup, klass, name, methodType) != canDiscover) {
    343       throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findStatic()",
    344                               canDiscover);
    345     }
    346 
    347     // Finish here if we could not discover the method.
    348 
    349     if (canDiscover) {
    350       // Test that modifiers are unaffected.
    351 
    352       if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) {
    353         throwModifiersException(klass, name, false);
    354       }
    355 
    356       // Test whether we can invoke the method. This skips non-static interface methods.
    357 
    358       if (!klass.isInterface() || isStatic) {
    359         clearWarning();
    360         if (!Reflection.canInvokeMethod(klass, name)) {
    361           throwAccessException(klass, name, false, "invoke()");
    362         }
    363         if (hasPendingWarning() != setsWarning) {
    364           throwWarningException(klass, name, false, "invoke()", setsWarning);
    365         }
    366 
    367         clearWarning();
    368         if (!JNI.canInvokeMethodA(klass, name, isStatic)) {
    369           throwAccessException(klass, name, false, "CallMethodA");
    370         }
    371         if (hasPendingWarning() != setsWarning) {
    372           throwWarningException(klass, name, false, "CallMethodA()", setsWarning);
    373         }
    374 
    375         clearWarning();
    376         if (!JNI.canInvokeMethodV(klass, name, isStatic)) {
    377           throwAccessException(klass, name, false, "CallMethodV");
    378         }
    379         if (hasPendingWarning() != setsWarning) {
    380           throwWarningException(klass, name, false, "CallMethodV()", setsWarning);
    381         }
    382       }
    383     }
    384 
    385     // Test that callbacks are invoked correctly.
    386     clearWarning();
    387     if (setsWarning || !canDiscover) {
    388         checkMemberCallback(klass, name, isPublic, false /* isField */);
    389     }
    390   }
    391 
    392   private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness,
    393       Behaviour behaviour) throws Exception {
    394 
    395     boolean isPublic = (visibility == Visibility.Public);
    396     String signature = "(" + visibility.mAssociatedType.mShorty +
    397                              hiddenness.mAssociatedType.mShorty + ")V";
    398     String fullName = "<init>" + signature;
    399     Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass,
    400                                     hiddenness.mAssociatedType.mClass };
    401     Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue,
    402                                        hiddenness.mAssociatedType.mDefaultValue };
    403     MethodType methodType = MethodType.methodType(void.class, args);
    404 
    405     boolean canDiscover = (behaviour != Behaviour.Denied);
    406     boolean setsWarning = (behaviour == Behaviour.Warning);
    407 
    408     // Test discovery with reflection.
    409 
    410     if (Reflection.canDiscoverWithGetDeclaredConstructor(klass, args) != canDiscover) {
    411       throwDiscoveryException(klass, fullName, false, "getDeclaredConstructor()", canDiscover);
    412     }
    413 
    414     if (Reflection.canDiscoverWithGetDeclaredConstructors(klass, args) != canDiscover) {
    415       throwDiscoveryException(klass, fullName, false, "getDeclaredConstructors()", canDiscover);
    416     }
    417 
    418     if (Reflection.canDiscoverWithGetConstructor(klass, args) != (canDiscover && isPublic)) {
    419       throwDiscoveryException(
    420           klass, fullName, false, "getConstructor()", (canDiscover && isPublic));
    421     }
    422 
    423     if (Reflection.canDiscoverWithGetConstructors(klass, args) != (canDiscover && isPublic)) {
    424       throwDiscoveryException(
    425           klass, fullName, false, "getConstructors()", (canDiscover && isPublic));
    426     }
    427 
    428     // Test discovery with JNI.
    429 
    430     if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) {
    431       throwDiscoveryException(klass, fullName, false, "JNI", canDiscover);
    432     }
    433 
    434     // Test discovery with MethodHandles.lookup()
    435 
    436     final MethodHandles.Lookup lookup = MethodHandles.lookup();
    437     if (JLI.canDiscoverWithLookupFindConstructor(lookup, klass, methodType) != canDiscover) {
    438       throwDiscoveryException(klass, fullName, false, "MethodHandles.lookup().findConstructor",
    439                               canDiscover);
    440     }
    441 
    442     final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
    443     if (JLI.canDiscoverWithLookupFindConstructor(publicLookup, klass, methodType) != canDiscover) {
    444       throwDiscoveryException(klass, fullName, false,
    445                               "MethodHandles.publicLookup().findConstructor",
    446                               canDiscover);
    447     }
    448 
    449     // Finish here if we could not discover the constructor.
    450 
    451     if (!canDiscover) {
    452       return;
    453     }
    454 
    455     // Test whether we can invoke the constructor.
    456 
    457     clearWarning();
    458     if (!Reflection.canInvokeConstructor(klass, args, initargs)) {
    459       throwAccessException(klass, fullName, false, "invoke()");
    460     }
    461     if (hasPendingWarning() != setsWarning) {
    462       throwWarningException(klass, fullName, false, "invoke()", setsWarning);
    463     }
    464 
    465     clearWarning();
    466     if (!JNI.canInvokeConstructorA(klass, signature)) {
    467       throwAccessException(klass, fullName, false, "NewObjectA");
    468     }
    469     if (hasPendingWarning() != setsWarning) {
    470       throwWarningException(klass, fullName, false, "NewObjectA", setsWarning);
    471     }
    472 
    473     clearWarning();
    474     if (!JNI.canInvokeConstructorV(klass, signature)) {
    475       throwAccessException(klass, fullName, false, "NewObjectV");
    476     }
    477     if (hasPendingWarning() != setsWarning) {
    478       throwWarningException(klass, fullName, false, "NewObjectV", setsWarning);
    479     }
    480   }
    481 
    482   private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour)
    483       throws Exception {
    484     boolean canAccess = (behaviour != Behaviour.Denied);
    485     boolean setsWarning = (behaviour == Behaviour.Warning);
    486 
    487     clearWarning();
    488     if (Reflection.canUseNewInstance(klass) != canAccess) {
    489       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
    490           "be able to construct " + klass.getName() + ". " +
    491           "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
    492     }
    493     if (canAccess && hasPendingWarning() != setsWarning) {
    494       throwWarningException(klass, "nullary constructor", false, "newInstance", setsWarning);
    495     }
    496   }
    497 
    498   private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour)
    499       throws Exception {
    500     boolean canAccess = (behaviour != Behaviour.Denied);
    501     boolean setsWarning = (behaviour == Behaviour.Warning);
    502 
    503     clearWarning();
    504     if (Linking.canAccess(className, takesParameter) != canAccess) {
    505       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
    506           "be able to verify " + className + "." +
    507           "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
    508     }
    509     if (canAccess && hasPendingWarning() != setsWarning) {
    510       throwWarningException(
    511           Class.forName(className), "access", false, "static linking", setsWarning);
    512     }
    513   }
    514 
    515   private static void throwDiscoveryException(Class<?> klass, String name, boolean isField,
    516       String fn, boolean canAccess) {
    517     throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
    518         "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " +
    519         "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot + ", " +
    520         "everythingWhitelisted = " + everythingWhitelisted);
    521   }
    522 
    523   private static void throwAccessException(Class<?> klass, String name, boolean isField,
    524       String fn) {
    525     throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") +
    526         klass.getName() + "." + name + " using " + fn + ". " +
    527         "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot + ", " +
    528         "everythingWhitelisted = " + everythingWhitelisted);
    529   }
    530 
    531   private static void throwWarningException(Class<?> klass, String name, boolean isField,
    532       String fn, boolean setsWarning) {
    533     throw new RuntimeException("Expected access to " + (isField ? "field " : "method ") +
    534         klass.getName() + "." + name + " using " + fn + " to " + (setsWarning ? "" : "not ") +
    535         "set the warning flag. " +
    536         "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot + ", " +
    537         "everythingWhitelisted = " + everythingWhitelisted);
    538   }
    539 
    540   private static void throwModifiersException(Class<?> klass, String name, boolean isField) {
    541     throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
    542         "." + name + " to not expose hidden modifiers");
    543   }
    544 
    545   private static boolean isParentInBoot;
    546   private static boolean isChildInBoot;
    547   private static boolean everythingWhitelisted;
    548 
    549   private static native boolean hasPendingWarning();
    550   private static native void clearWarning();
    551 }
    552