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     BlacklistAndCorePlatformApi(PrimitiveType.TInteger);
     50 
     51     Hiddenness(PrimitiveType type) { mAssociatedType = type; }
     52     public PrimitiveType mAssociatedType;
     53   }
     54 
     55   enum Visibility {
     56     Public(PrimitiveType.TInteger),
     57     Package(PrimitiveType.TFloat),
     58     Protected(PrimitiveType.TLong),
     59     Private(PrimitiveType.TDouble);
     60 
     61     Visibility(PrimitiveType type) { mAssociatedType = type; }
     62     public PrimitiveType mAssociatedType;
     63   }
     64 
     65   enum Behaviour {
     66     Granted,
     67     Warning,
     68     Denied,
     69   }
     70 
     71   // This needs to be kept in sync with DexDomain in Main.
     72   enum DexDomain {
     73     CorePlatform,
     74     Platform,
     75     Application
     76   }
     77 
     78   private static final boolean booleanValues[] = new boolean[] { false, true };
     79 
     80   public static void runTest(String libFileName, int parentDomainOrdinal,
     81       int childDomainOrdinal, boolean everythingWhitelisted) throws Exception {
     82     System.load(libFileName);
     83 
     84     parentDomain = DexDomain.values()[parentDomainOrdinal];
     85     childDomain = DexDomain.values()[childDomainOrdinal];
     86 
     87     configMessage = "parentDomain=" + parentDomain.name() + ", childDomain=" + childDomain.name()
     88         + ", everythingWhitelisted=" + everythingWhitelisted;
     89 
     90     // Check expectations about loading into boot class path.
     91     boolean isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null);
     92     boolean expectedParentInBoot = (parentDomain != DexDomain.Application);
     93     if (isParentInBoot != expectedParentInBoot) {
     94       throw new RuntimeException("Expected ParentClass " +
     95                                  (expectedParentInBoot ? "" : "not ") + "in boot class path");
     96     }
     97     boolean isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null);
     98     boolean expectedChildInBoot = (childDomain != DexDomain.Application);
     99     if (isChildInBoot != expectedChildInBoot) {
    100       throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") +
    101                                  "in boot class path");
    102     }
    103     ChildClass.everythingWhitelisted = everythingWhitelisted;
    104 
    105     boolean isSameBoot = (isParentInBoot == isChildInBoot);
    106     boolean isDebuggable = VMRuntime.getRuntime().isJavaDebuggable();
    107 
    108     // Run meaningful combinations of access flags.
    109     for (Hiddenness hiddenness : Hiddenness.values()) {
    110       final Behaviour expected;
    111       final boolean invokesMemberCallback;
    112       // Warnings are now disabled whenever access is granted, even for
    113       // greylisted APIs. This is the behaviour for release builds.
    114       if (everythingWhitelisted || hiddenness == Hiddenness.Whitelist) {
    115         expected = Behaviour.Granted;
    116         invokesMemberCallback = false;
    117       } else if (parentDomain == DexDomain.CorePlatform && childDomain == DexDomain.Platform) {
    118         expected = (hiddenness == Hiddenness.BlacklistAndCorePlatformApi)
    119             ? Behaviour.Granted : Behaviour.Denied;
    120         invokesMemberCallback = false;
    121       } else if (isSameBoot) {
    122         expected = Behaviour.Granted;
    123         invokesMemberCallback = false;
    124       } else if (hiddenness == Hiddenness.Blacklist ||
    125                  hiddenness == Hiddenness.BlacklistAndCorePlatformApi) {
    126         expected = Behaviour.Denied;
    127         invokesMemberCallback = true;
    128       } else {
    129         expected = Behaviour.Warning;
    130         invokesMemberCallback = true;
    131       }
    132 
    133       for (boolean isStatic : booleanValues) {
    134         String suffix = (isStatic ? "Static" : "") + hiddenness.name();
    135 
    136         for (Visibility visibility : Visibility.values()) {
    137           // Test reflection and JNI on methods and fields
    138           for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) {
    139             String baseName = visibility.name() + suffix;
    140             checkField(klass, "field" + baseName, isStatic, visibility, expected,
    141                 invokesMemberCallback);
    142             checkMethod(klass, "method" + baseName, isStatic, visibility, expected,
    143                 invokesMemberCallback);
    144           }
    145 
    146           // Check whether one can use a class constructor.
    147           checkConstructor(ParentClass.class, visibility, hiddenness, expected);
    148 
    149           // Check whether one can use an interface default method.
    150           String name = "method" + visibility.name() + "Default" + hiddenness.name();
    151           checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected,
    152               invokesMemberCallback);
    153         }
    154 
    155         // Test whether static linking succeeds.
    156         checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected);
    157         checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected);
    158         checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected);
    159         checkLinking("LinkMethodInterface" + suffix, /*takesParameter*/ false, expected);
    160       }
    161 
    162       // Check whether Class.newInstance succeeds.
    163       checkNullaryConstructor(Class.forName("NullaryConstructor" + hiddenness.name()), expected);
    164     }
    165   }
    166 
    167   static final class RecordingConsumer implements Consumer<String> {
    168       public String recordedValue = null;
    169 
    170       @Override
    171       public void accept(String value) {
    172           recordedValue = value;
    173       }
    174   }
    175 
    176   private static void checkMemberCallback(Class<?> klass, String name,
    177           boolean isPublic, boolean isField, boolean expectedCallback) {
    178       try {
    179           RecordingConsumer consumer = new RecordingConsumer();
    180           VMRuntime.setNonSdkApiUsageConsumer(consumer);
    181           try {
    182               if (isPublic) {
    183                   if (isField) {
    184                       klass.getField(name);
    185                   } else {
    186                       klass.getMethod(name);
    187                   }
    188               } else {
    189                   if (isField) {
    190                       klass.getDeclaredField(name);
    191                   } else {
    192                       klass.getDeclaredMethod(name);
    193                   }
    194               }
    195           } catch (NoSuchFieldException|NoSuchMethodException ignored) {
    196               // We're not concerned whether an exception is thrown or not - we're
    197               // only interested in whether the callback is invoked.
    198           }
    199 
    200           boolean actualCallback = consumer.recordedValue != null &&
    201                           consumer.recordedValue.contains(name);
    202           if (expectedCallback != actualCallback) {
    203               if (expectedCallback) {
    204                 throw new RuntimeException("Expected callback for member: " + name);
    205               } else {
    206                 throw new RuntimeException("Did not expect callback for member: " + name);
    207               }
    208           }
    209       } finally {
    210           VMRuntime.setNonSdkApiUsageConsumer(null);
    211       }
    212   }
    213 
    214   private static void checkField(Class<?> klass, String name, boolean isStatic,
    215       Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback) throws Exception {
    216 
    217     boolean isPublic = (visibility == Visibility.Public);
    218     boolean canDiscover = (behaviour != Behaviour.Denied);
    219 
    220     if (klass.isInterface() && (!isStatic || !isPublic)) {
    221       // Interfaces only have public static fields.
    222       return;
    223     }
    224 
    225     // Test discovery with reflection.
    226 
    227     if (Reflection.canDiscoverWithGetDeclaredField(klass, name) != canDiscover) {
    228       throwDiscoveryException(klass, name, true, "getDeclaredField()", canDiscover);
    229     }
    230 
    231     if (Reflection.canDiscoverWithGetDeclaredFields(klass, name) != canDiscover) {
    232       throwDiscoveryException(klass, name, true, "getDeclaredFields()", canDiscover);
    233     }
    234 
    235     if (Reflection.canDiscoverWithGetField(klass, name) != (canDiscover && isPublic)) {
    236       throwDiscoveryException(klass, name, true, "getField()", (canDiscover && isPublic));
    237     }
    238 
    239     if (Reflection.canDiscoverWithGetFields(klass, name) != (canDiscover && isPublic)) {
    240       throwDiscoveryException(klass, name, true, "getFields()", (canDiscover && isPublic));
    241     }
    242 
    243     // Test discovery with JNI.
    244 
    245     if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) {
    246       throwDiscoveryException(klass, name, true, "JNI", canDiscover);
    247     }
    248 
    249     // Test discovery with MethodHandles.lookup() which is caller
    250     // context sensitive.
    251 
    252     final MethodHandles.Lookup lookup = MethodHandles.lookup();
    253     if (JLI.canDiscoverWithLookupFindGetter(lookup, klass, name, int.class)
    254         != canDiscover) {
    255       throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findGetter()",
    256                               canDiscover);
    257     }
    258     if (JLI.canDiscoverWithLookupFindStaticGetter(lookup, klass, name, int.class)
    259         != canDiscover) {
    260       throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findStaticGetter()",
    261                               canDiscover);
    262     }
    263 
    264     // Test discovery with MethodHandles.publicLookup() which can only
    265     // see public fields. Looking up setters here and fields in
    266     // interfaces are implicitly final.
    267 
    268     final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
    269     if (JLI.canDiscoverWithLookupFindSetter(publicLookup, klass, name, int.class)
    270         != canDiscover) {
    271       throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findSetter()",
    272                               canDiscover);
    273     }
    274     if (JLI.canDiscoverWithLookupFindStaticSetter(publicLookup, klass, name, int.class)
    275         != canDiscover) {
    276       throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findStaticSetter()",
    277                               canDiscover);
    278     }
    279 
    280     if (canDiscover) {
    281       // Test that modifiers are unaffected.
    282 
    283       if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) {
    284         throwModifiersException(klass, name, true);
    285       }
    286 
    287       // Test getters and setters when meaningful.
    288 
    289       if (!Reflection.canGetField(klass, name)) {
    290         throwAccessException(klass, name, true, "Field.getInt()");
    291       }
    292       if (!Reflection.canSetField(klass, name)) {
    293         throwAccessException(klass, name, true, "Field.setInt()");
    294       }
    295       if (!JNI.canGetField(klass, name, isStatic)) {
    296         throwAccessException(klass, name, true, "getIntField");
    297       }
    298       if (!JNI.canSetField(klass, name, isStatic)) {
    299         throwAccessException(klass, name, true, "setIntField");
    300       }
    301     }
    302 
    303     // Test that callbacks are invoked correctly.
    304     checkMemberCallback(klass, name, isPublic, true /* isField */, invokesMemberCallback);
    305   }
    306 
    307   private static void checkMethod(Class<?> klass, String name, boolean isStatic,
    308       Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback) throws Exception {
    309 
    310     boolean isPublic = (visibility == Visibility.Public);
    311     if (klass.isInterface() && !isPublic) {
    312       // All interface members are public.
    313       return;
    314     }
    315 
    316     boolean canDiscover = (behaviour != Behaviour.Denied);
    317 
    318     // Test discovery with reflection.
    319 
    320     if (Reflection.canDiscoverWithGetDeclaredMethod(klass, name) != canDiscover) {
    321       throwDiscoveryException(klass, name, false, "getDeclaredMethod()", canDiscover);
    322     }
    323 
    324     if (Reflection.canDiscoverWithGetDeclaredMethods(klass, name) != canDiscover) {
    325       throwDiscoveryException(klass, name, false, "getDeclaredMethods()", canDiscover);
    326     }
    327 
    328     if (Reflection.canDiscoverWithGetMethod(klass, name) != (canDiscover && isPublic)) {
    329       throwDiscoveryException(klass, name, false, "getMethod()", (canDiscover && isPublic));
    330     }
    331 
    332     if (Reflection.canDiscoverWithGetMethods(klass, name) != (canDiscover && isPublic)) {
    333       throwDiscoveryException(klass, name, false, "getMethods()", (canDiscover && isPublic));
    334     }
    335 
    336     // Test discovery with JNI.
    337 
    338     if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) {
    339       throwDiscoveryException(klass, name, false, "JNI", canDiscover);
    340     }
    341 
    342     // Test discovery with MethodHandles.lookup().
    343 
    344     final MethodHandles.Lookup lookup = MethodHandles.lookup();
    345     final MethodType methodType = MethodType.methodType(int.class);
    346     if (JLI.canDiscoverWithLookupFindVirtual(lookup, klass, name, methodType) != canDiscover) {
    347       throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findVirtual()",
    348                               canDiscover);
    349     }
    350 
    351     if (JLI.canDiscoverWithLookupFindStatic(lookup, klass, name, methodType) != canDiscover) {
    352       throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findStatic()",
    353                               canDiscover);
    354     }
    355 
    356     // Finish here if we could not discover the method.
    357 
    358     if (canDiscover) {
    359       // Test that modifiers are unaffected.
    360 
    361       if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) {
    362         throwModifiersException(klass, name, false);
    363       }
    364 
    365       // Test whether we can invoke the method. This skips non-static interface methods.
    366       if (!klass.isInterface() || isStatic) {
    367         if (!Reflection.canInvokeMethod(klass, name)) {
    368           throwAccessException(klass, name, false, "invoke()");
    369         }
    370         if (!JNI.canInvokeMethodA(klass, name, isStatic)) {
    371           throwAccessException(klass, name, false, "CallMethodA");
    372         }
    373         if (!JNI.canInvokeMethodV(klass, name, isStatic)) {
    374           throwAccessException(klass, name, false, "CallMethodV");
    375         }
    376       }
    377     }
    378 
    379     // Test that callbacks are invoked correctly.
    380     checkMemberCallback(klass, name, isPublic, false /* isField */, invokesMemberCallback);
    381   }
    382 
    383   private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness,
    384       Behaviour behaviour) throws Exception {
    385 
    386     boolean isPublic = (visibility == Visibility.Public);
    387     String signature = "(" + visibility.mAssociatedType.mShorty +
    388                              hiddenness.mAssociatedType.mShorty + ")V";
    389     String fullName = "<init>" + signature;
    390     Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass,
    391                                     hiddenness.mAssociatedType.mClass };
    392     Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue,
    393                                        hiddenness.mAssociatedType.mDefaultValue };
    394     MethodType methodType = MethodType.methodType(void.class, args);
    395 
    396     boolean canDiscover = (behaviour != Behaviour.Denied);
    397 
    398     // Test discovery with reflection.
    399 
    400     if (Reflection.canDiscoverWithGetDeclaredConstructor(klass, args) != canDiscover) {
    401       throwDiscoveryException(klass, fullName, false, "getDeclaredConstructor()", canDiscover);
    402     }
    403 
    404     if (Reflection.canDiscoverWithGetDeclaredConstructors(klass, args) != canDiscover) {
    405       throwDiscoveryException(klass, fullName, false, "getDeclaredConstructors()", canDiscover);
    406     }
    407 
    408     if (Reflection.canDiscoverWithGetConstructor(klass, args) != (canDiscover && isPublic)) {
    409       throwDiscoveryException(
    410           klass, fullName, false, "getConstructor()", (canDiscover && isPublic));
    411     }
    412 
    413     if (Reflection.canDiscoverWithGetConstructors(klass, args) != (canDiscover && isPublic)) {
    414       throwDiscoveryException(
    415           klass, fullName, false, "getConstructors()", (canDiscover && isPublic));
    416     }
    417 
    418     // Test discovery with JNI.
    419 
    420     if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) {
    421       throwDiscoveryException(klass, fullName, false, "JNI", canDiscover);
    422     }
    423 
    424     // Test discovery with MethodHandles.lookup()
    425 
    426     final MethodHandles.Lookup lookup = MethodHandles.lookup();
    427     if (JLI.canDiscoverWithLookupFindConstructor(lookup, klass, methodType) != canDiscover) {
    428       throwDiscoveryException(klass, fullName, false, "MethodHandles.lookup().findConstructor",
    429                               canDiscover);
    430     }
    431 
    432     final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
    433     if (JLI.canDiscoverWithLookupFindConstructor(publicLookup, klass, methodType) != canDiscover) {
    434       throwDiscoveryException(klass, fullName, false,
    435                               "MethodHandles.publicLookup().findConstructor",
    436                               canDiscover);
    437     }
    438 
    439     if (canDiscover) {
    440       // Test whether we can invoke the constructor.
    441 
    442       if (!Reflection.canInvokeConstructor(klass, args, initargs)) {
    443         throwAccessException(klass, fullName, false, "invoke()");
    444       }
    445       if (!JNI.canInvokeConstructorA(klass, signature)) {
    446         throwAccessException(klass, fullName, false, "NewObjectA");
    447       }
    448       if (!JNI.canInvokeConstructorV(klass, signature)) {
    449         throwAccessException(klass, fullName, false, "NewObjectV");
    450       }
    451     }
    452   }
    453 
    454   private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour)
    455       throws Exception {
    456     boolean canAccess = (behaviour != Behaviour.Denied);
    457 
    458     if (Reflection.canUseNewInstance(klass) != canAccess) {
    459       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
    460           "be able to construct " + klass.getName() + ". " + configMessage);
    461     }
    462   }
    463 
    464   private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour)
    465       throws Exception {
    466     boolean canAccess = (behaviour != Behaviour.Denied);
    467 
    468     if (Linking.canAccess(className, takesParameter) != canAccess) {
    469       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
    470           "be able to verify " + className + "." + configMessage);
    471     }
    472   }
    473 
    474   private static void throwDiscoveryException(Class<?> klass, String name, boolean isField,
    475       String fn, boolean canAccess) {
    476     throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
    477         "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " +
    478         configMessage);
    479   }
    480 
    481   private static void throwAccessException(Class<?> klass, String name, boolean isField,
    482       String fn) {
    483     throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") +
    484         klass.getName() + "." + name + " using " + fn + ". " + configMessage);
    485   }
    486 
    487   private static void throwModifiersException(Class<?> klass, String name, boolean isField) {
    488     throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
    489         "." + name + " to not expose hidden modifiers");
    490   }
    491 
    492   private static DexDomain parentDomain;
    493   private static DexDomain childDomain;
    494   private static boolean everythingWhitelisted;
    495 
    496   private static String configMessage;
    497 }
    498