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