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