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 
PrimitiveType(char shorty, Class klass, Object value)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 
Hiddenness(PrimitiveType type)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 
Visibility(PrimitiveType type)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 
runTest(String libFileName, int parentDomainOrdinal, int childDomainOrdinal, boolean everythingWhitelisted)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 
107     // For compat reasons, meta-reflection should still be usable by apps if hidden api check
108     // hardening is disabled (i.e. target SDK is Q or earlier). The only configuration where this
109     // workaround used to work is for ChildClass in the Application domain and ParentClass in the
110     // Platform domain, so only test that configuration with hidden api check hardening disabled.
111     boolean testHiddenApiCheckHardeningDisabled =
112         (childDomain == DexDomain.Application) && (parentDomain == DexDomain.Platform);
113 
114     // Run meaningful combinations of access flags.
115     for (Hiddenness hiddenness : Hiddenness.values()) {
116       final Behaviour expected;
117       final boolean invokesMemberCallback;
118       // Warnings are now disabled whenever access is granted, even for
119       // greylisted APIs. This is the behaviour for release builds.
120       if (everythingWhitelisted || hiddenness == Hiddenness.Whitelist) {
121         expected = Behaviour.Granted;
122         invokesMemberCallback = false;
123       } else if (parentDomain == DexDomain.CorePlatform && childDomain == DexDomain.Platform) {
124         expected = (hiddenness == Hiddenness.BlacklistAndCorePlatformApi)
125             ? Behaviour.Granted : Behaviour.Denied;
126         invokesMemberCallback = false;
127       } else if (isSameBoot) {
128         expected = Behaviour.Granted;
129         invokesMemberCallback = false;
130       } else if (hiddenness == Hiddenness.Blacklist ||
131                  hiddenness == Hiddenness.BlacklistAndCorePlatformApi) {
132         expected = Behaviour.Denied;
133         invokesMemberCallback = true;
134       } else {
135         expected = Behaviour.Warning;
136         invokesMemberCallback = true;
137       }
138 
139       for (boolean isStatic : booleanValues) {
140         String suffix = (isStatic ? "Static" : "") + hiddenness.name();
141 
142         for (Visibility visibility : Visibility.values()) {
143           // Test reflection and JNI on methods and fields
144           for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) {
145             String baseName = visibility.name() + suffix;
146             checkField(klass, "field" + baseName, isStatic, visibility, expected,
147                 invokesMemberCallback, testHiddenApiCheckHardeningDisabled);
148             checkMethod(klass, "method" + baseName, isStatic, visibility, expected,
149                 invokesMemberCallback, testHiddenApiCheckHardeningDisabled);
150           }
151 
152           // Check whether one can use a class constructor.
153           checkConstructor(ParentClass.class, visibility, hiddenness, expected,
154                 testHiddenApiCheckHardeningDisabled);
155 
156           // Check whether one can use an interface default method.
157           String name = "method" + visibility.name() + "Default" + hiddenness.name();
158           checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected,
159               invokesMemberCallback, testHiddenApiCheckHardeningDisabled);
160         }
161 
162         // Test whether static linking succeeds.
163         checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected);
164         checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected);
165         checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected);
166         checkLinking("LinkMethodInterface" + suffix, /*takesParameter*/ false, expected);
167       }
168 
169       // Check whether Class.newInstance succeeds.
170       checkNullaryConstructor(Class.forName("NullaryConstructor" + hiddenness.name()), expected);
171     }
172   }
173 
174   static final class RecordingConsumer implements Consumer<String> {
175       public String recordedValue = null;
176 
177       @Override
accept(String value)178       public void accept(String value) {
179           recordedValue = value;
180       }
181   }
182 
checkMemberCallback(Class<?> klass, String name, boolean isPublic, boolean isField, boolean expectedCallback)183   private static void checkMemberCallback(Class<?> klass, String name,
184           boolean isPublic, boolean isField, boolean expectedCallback) {
185       try {
186           RecordingConsumer consumer = new RecordingConsumer();
187           VMRuntime.setNonSdkApiUsageConsumer(consumer);
188           try {
189               if (isPublic) {
190                   if (isField) {
191                       klass.getField(name);
192                   } else {
193                       klass.getMethod(name);
194                   }
195               } else {
196                   if (isField) {
197                       klass.getDeclaredField(name);
198                   } else {
199                       klass.getDeclaredMethod(name);
200                   }
201               }
202           } catch (NoSuchFieldException|NoSuchMethodException ignored) {
203               // We're not concerned whether an exception is thrown or not - we're
204               // only interested in whether the callback is invoked.
205           }
206 
207           boolean actualCallback = consumer.recordedValue != null &&
208                           consumer.recordedValue.contains(name);
209           if (expectedCallback != actualCallback) {
210               if (expectedCallback) {
211                 throw new RuntimeException("Expected callback for member: " + name);
212               } else {
213                 throw new RuntimeException("Did not expect callback for member: " + name);
214               }
215           }
216       } finally {
217           VMRuntime.setNonSdkApiUsageConsumer(null);
218       }
219   }
220 
checkField(Class<?> klass, String name, boolean isStatic, Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback, boolean testHiddenApiCheckHardeningDisabled)221   private static void checkField(Class<?> klass, String name, boolean isStatic,
222       Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback,
223       boolean testHiddenApiCheckHardeningDisabled) throws Exception {
224 
225     boolean isPublic = (visibility == Visibility.Public);
226     boolean canDiscover = (behaviour != Behaviour.Denied);
227 
228     if (klass.isInterface() && (!isStatic || !isPublic)) {
229       // Interfaces only have public static fields.
230       return;
231     }
232 
233     // Test discovery with reflection.
234 
235     if (Reflection.canDiscoverWithGetDeclaredField(klass, name) != canDiscover) {
236       throwDiscoveryException(klass, name, true, "getDeclaredField()", canDiscover);
237     }
238 
239     if (Reflection.canDiscoverWithGetDeclaredFields(klass, name) != canDiscover) {
240       throwDiscoveryException(klass, name, true, "getDeclaredFields()", canDiscover);
241     }
242 
243     if (Reflection.canDiscoverWithGetField(klass, name) != (canDiscover && isPublic)) {
244       throwDiscoveryException(klass, name, true, "getField()", (canDiscover && isPublic));
245     }
246 
247     if (Reflection.canDiscoverWithGetFields(klass, name) != (canDiscover && isPublic)) {
248       throwDiscoveryException(klass, name, true, "getFields()", (canDiscover && isPublic));
249     }
250 
251     // Test discovery with JNI.
252 
253     if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) {
254       throwDiscoveryException(klass, name, true, "JNI", canDiscover);
255     }
256 
257     // Test discovery with MethodHandles.lookup() which is caller
258     // context sensitive.
259 
260     final MethodHandles.Lookup lookup = MethodHandles.lookup();
261     if (JLI.canDiscoverWithLookupFindGetter(lookup, klass, name, int.class)
262         != canDiscover) {
263       throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findGetter()",
264                               canDiscover);
265     }
266     if (JLI.canDiscoverWithLookupFindStaticGetter(lookup, klass, name, int.class)
267         != canDiscover) {
268       throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findStaticGetter()",
269                               canDiscover);
270     }
271 
272     // Test discovery with MethodHandles.publicLookup() which can only
273     // see public fields. Looking up setters here and fields in
274     // interfaces are implicitly final.
275 
276     final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
277     if (JLI.canDiscoverWithLookupFindSetter(publicLookup, klass, name, int.class)
278         != canDiscover) {
279       throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findSetter()",
280                               canDiscover);
281     }
282     if (JLI.canDiscoverWithLookupFindStaticSetter(publicLookup, klass, name, int.class)
283         != canDiscover) {
284       throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findStaticSetter()",
285                               canDiscover);
286     }
287 
288     // Check for meta reflection.
289 
290     // With hidden api check hardening enabled, only white and light greylisted fields should be
291     // discoverable.
292     if (Reflection.canDiscoverFieldWithMetaReflection(klass, name, true) != canDiscover) {
293       throwDiscoveryException(klass, name, false,
294           "Meta reflection with hidden api hardening enabled", canDiscover);
295     }
296 
297     if (testHiddenApiCheckHardeningDisabled) {
298       // With hidden api check hardening disabled, all fields should be discoverable.
299       if (Reflection.canDiscoverFieldWithMetaReflection(klass, name, false) != true) {
300         throwDiscoveryException(klass, name, false,
301             "Meta reflection with hidden api hardening enabled", canDiscover);
302       }
303     }
304 
305     if (canDiscover) {
306       // Test that modifiers are unaffected.
307 
308       if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) {
309         throwModifiersException(klass, name, true);
310       }
311 
312       // Test getters and setters when meaningful.
313 
314       if (!Reflection.canGetField(klass, name)) {
315         throwAccessException(klass, name, true, "Field.getInt()");
316       }
317       if (!Reflection.canSetField(klass, name)) {
318         throwAccessException(klass, name, true, "Field.setInt()");
319       }
320       if (!JNI.canGetField(klass, name, isStatic)) {
321         throwAccessException(klass, name, true, "getIntField");
322       }
323       if (!JNI.canSetField(klass, name, isStatic)) {
324         throwAccessException(klass, name, true, "setIntField");
325       }
326     }
327 
328     // Test that callbacks are invoked correctly.
329     checkMemberCallback(klass, name, isPublic, true /* isField */, invokesMemberCallback);
330   }
331 
checkMethod(Class<?> klass, String name, boolean isStatic, Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback, boolean testHiddenApiCheckHardeningDisabled)332   private static void checkMethod(Class<?> klass, String name, boolean isStatic,
333       Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback,
334       boolean testHiddenApiCheckHardeningDisabled) throws Exception {
335 
336     boolean isPublic = (visibility == Visibility.Public);
337     if (klass.isInterface() && !isPublic) {
338       // All interface members are public.
339       return;
340     }
341 
342     boolean canDiscover = (behaviour != Behaviour.Denied);
343 
344     // Test discovery with reflection.
345 
346     if (Reflection.canDiscoverWithGetDeclaredMethod(klass, name) != canDiscover) {
347       throwDiscoveryException(klass, name, false, "getDeclaredMethod()", canDiscover);
348     }
349 
350     if (Reflection.canDiscoverWithGetDeclaredMethods(klass, name) != canDiscover) {
351       throwDiscoveryException(klass, name, false, "getDeclaredMethods()", canDiscover);
352     }
353 
354     if (Reflection.canDiscoverWithGetMethod(klass, name) != (canDiscover && isPublic)) {
355       throwDiscoveryException(klass, name, false, "getMethod()", (canDiscover && isPublic));
356     }
357 
358     if (Reflection.canDiscoverWithGetMethods(klass, name) != (canDiscover && isPublic)) {
359       throwDiscoveryException(klass, name, false, "getMethods()", (canDiscover && isPublic));
360     }
361 
362     // Test discovery with JNI.
363 
364     if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) {
365       throwDiscoveryException(klass, name, false, "JNI", canDiscover);
366     }
367 
368     // Test discovery with MethodHandles.lookup().
369 
370     final MethodHandles.Lookup lookup = MethodHandles.lookup();
371     final MethodType methodType = MethodType.methodType(int.class);
372     if (JLI.canDiscoverWithLookupFindVirtual(lookup, klass, name, methodType) != canDiscover) {
373       throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findVirtual()",
374                               canDiscover);
375     }
376 
377     if (JLI.canDiscoverWithLookupFindStatic(lookup, klass, name, methodType) != canDiscover) {
378       throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findStatic()",
379                               canDiscover);
380     }
381 
382     // Check for meta reflection.
383 
384     // With hidden api check hardening enabled, only white and light greylisted methods should be
385     // discoverable.
386     if (Reflection.canDiscoverMethodWithMetaReflection(klass, name, true) != canDiscover) {
387       throwDiscoveryException(klass, name, false,
388           "Meta reflection with hidden api hardening enabled", canDiscover);
389     }
390 
391     if (testHiddenApiCheckHardeningDisabled) {
392       // With hidden api check hardening disabled, all methods should be discoverable.
393       if (Reflection.canDiscoverMethodWithMetaReflection(klass, name, false) != true) {
394         throwDiscoveryException(klass, name, false,
395             "Meta reflection with hidden api hardening enabled", canDiscover);
396       }
397     }
398 
399     // Finish here if we could not discover the method.
400 
401     if (canDiscover) {
402       // Test that modifiers are unaffected.
403 
404       if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) {
405         throwModifiersException(klass, name, false);
406       }
407 
408       // Test whether we can invoke the method. This skips non-static interface methods.
409       if (!klass.isInterface() || isStatic) {
410         if (!Reflection.canInvokeMethod(klass, name)) {
411           throwAccessException(klass, name, false, "invoke()");
412         }
413         if (!JNI.canInvokeMethodA(klass, name, isStatic)) {
414           throwAccessException(klass, name, false, "CallMethodA");
415         }
416         if (!JNI.canInvokeMethodV(klass, name, isStatic)) {
417           throwAccessException(klass, name, false, "CallMethodV");
418         }
419       }
420     }
421 
422     // Test that callbacks are invoked correctly.
423     checkMemberCallback(klass, name, isPublic, false /* isField */, invokesMemberCallback);
424   }
425 
checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness, Behaviour behaviour, boolean testHiddenApiCheckHardeningDisabled)426   private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness,
427       Behaviour behaviour, boolean testHiddenApiCheckHardeningDisabled) throws Exception {
428 
429     boolean isPublic = (visibility == Visibility.Public);
430     String signature = "(" + visibility.mAssociatedType.mShorty +
431                              hiddenness.mAssociatedType.mShorty + ")V";
432     String fullName = "<init>" + signature;
433     Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass,
434                                     hiddenness.mAssociatedType.mClass };
435     Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue,
436                                        hiddenness.mAssociatedType.mDefaultValue };
437     MethodType methodType = MethodType.methodType(void.class, args);
438 
439     boolean canDiscover = (behaviour != Behaviour.Denied);
440 
441     // Test discovery with reflection.
442 
443     if (Reflection.canDiscoverWithGetDeclaredConstructor(klass, args) != canDiscover) {
444       throwDiscoveryException(klass, fullName, false, "getDeclaredConstructor()", canDiscover);
445     }
446 
447     if (Reflection.canDiscoverWithGetDeclaredConstructors(klass, args) != canDiscover) {
448       throwDiscoveryException(klass, fullName, false, "getDeclaredConstructors()", canDiscover);
449     }
450 
451     if (Reflection.canDiscoverWithGetConstructor(klass, args) != (canDiscover && isPublic)) {
452       throwDiscoveryException(
453           klass, fullName, false, "getConstructor()", (canDiscover && isPublic));
454     }
455 
456     if (Reflection.canDiscoverWithGetConstructors(klass, args) != (canDiscover && isPublic)) {
457       throwDiscoveryException(
458           klass, fullName, false, "getConstructors()", (canDiscover && isPublic));
459     }
460 
461     // Test discovery with JNI.
462 
463     if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) {
464       throwDiscoveryException(klass, fullName, false, "JNI", canDiscover);
465     }
466 
467     // Test discovery with MethodHandles.lookup()
468 
469     final MethodHandles.Lookup lookup = MethodHandles.lookup();
470     if (JLI.canDiscoverWithLookupFindConstructor(lookup, klass, methodType) != canDiscover) {
471       throwDiscoveryException(klass, fullName, false, "MethodHandles.lookup().findConstructor",
472                               canDiscover);
473     }
474 
475     final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
476     if (JLI.canDiscoverWithLookupFindConstructor(publicLookup, klass, methodType) != canDiscover) {
477       throwDiscoveryException(klass, fullName, false,
478                               "MethodHandles.publicLookup().findConstructor",
479                               canDiscover);
480     }
481 
482     // Check for meta reflection.
483 
484     // With hidden api check hardening enabled, only white and light greylisted constructors should
485     // be discoverable.
486     if (Reflection.canDiscoverConstructorWithMetaReflection(klass, args, true) != canDiscover) {
487       throwDiscoveryException(klass, fullName, false,
488           "Meta reflection with hidden api hardening enabled", canDiscover);
489     }
490 
491     if (testHiddenApiCheckHardeningDisabled) {
492       // With hidden api check hardening disabled, all constructors should be discoverable.
493       if (Reflection.canDiscoverConstructorWithMetaReflection(klass, args, false) != true) {
494         throwDiscoveryException(klass, fullName, false,
495             "Meta reflection with hidden api hardening enabled", canDiscover);
496       }
497     }
498 
499     if (canDiscover) {
500       // Test whether we can invoke the constructor.
501 
502       if (!Reflection.canInvokeConstructor(klass, args, initargs)) {
503         throwAccessException(klass, fullName, false, "invoke()");
504       }
505       if (!JNI.canInvokeConstructorA(klass, signature)) {
506         throwAccessException(klass, fullName, false, "NewObjectA");
507       }
508       if (!JNI.canInvokeConstructorV(klass, signature)) {
509         throwAccessException(klass, fullName, false, "NewObjectV");
510       }
511     }
512   }
513 
checkNullaryConstructor(Class<?> klass, Behaviour behaviour)514   private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour)
515       throws Exception {
516     boolean canAccess = (behaviour != Behaviour.Denied);
517 
518     if (Reflection.canUseNewInstance(klass) != canAccess) {
519       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
520           "be able to construct " + klass.getName() + ". " + configMessage);
521     }
522   }
523 
checkLinking(String className, boolean takesParameter, Behaviour behaviour)524   private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour)
525       throws Exception {
526     boolean canAccess = (behaviour != Behaviour.Denied);
527 
528     if (Linking.canAccess(className, takesParameter) != canAccess) {
529       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
530           "be able to verify " + className + "." + configMessage);
531     }
532   }
533 
throwDiscoveryException(Class<?> klass, String name, boolean isField, String fn, boolean canAccess)534   private static void throwDiscoveryException(Class<?> klass, String name, boolean isField,
535       String fn, boolean canAccess) {
536     throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
537         "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " +
538         configMessage);
539   }
540 
throwAccessException(Class<?> klass, String name, boolean isField, String fn)541   private static void throwAccessException(Class<?> klass, String name, boolean isField,
542       String fn) {
543     throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") +
544         klass.getName() + "." + name + " using " + fn + ". " + configMessage);
545   }
546 
throwModifiersException(Class<?> klass, String name, boolean isField)547   private static void throwModifiersException(Class<?> klass, String name, boolean isField) {
548     throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
549         "." + name + " to not expose hidden modifiers");
550   }
551 
552   private static DexDomain parentDomain;
553   private static DexDomain childDomain;
554   private static boolean everythingWhitelisted;
555 
556   private static String configMessage;
557 }
558