hidden_api: Call back into libcore on hidden api detection

This change also removes some unnecessary RI specific logic for
building src-ex since it isn't required.

Bug: 73896556
Test: run-test --host 674-hiddenapi
Test: StrictModeTest

Co-Authored-By: Andreas Gampe <agampe@google.com>

(cherry picked from commit 757a9d0a2e97d43bafeb8a95cc3c51102be99586)

Merged-In: Ib2b4dfad55c5d829630bfe2adb4a468124bea61c
Change-Id: Ida0943990aa1b3bad0c674bc31ff46766ae493a6
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index f0b36a0..aad3917 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -16,7 +16,11 @@
 
 #include "hidden_api.h"
 
+#include <nativehelper/scoped_local_ref.h>
+
 #include "base/dumpable.h"
+#include "thread-current-inl.h"
+#include "well_known_classes.h"
 
 namespace art {
 namespace hiddenapi {
@@ -136,6 +140,35 @@
   member_signature.WarnAboutAccess(access_method,
       HiddenApiAccessFlags::DecodeFromRuntime(member->GetAccessFlags()));
 
+  // We're now not in the boot classpath and have decided to warn, show
+  // a toast or deny access. Let strict mode know if a callback is set.
+  //
+  // For consistency of reasoning, we assume that a callback is never set
+  // when running unstarted with dex2oat.
+  if (access_method == kReflection && !runtime->IsAotCompiler()) {
+    ScopedObjectAccessUnchecked soa(Thread::Current());
+
+    ScopedLocalRef<jobject> consumer_object(soa.Env(),
+        soa.Env()->GetStaticObjectField(
+            WellKnownClasses::dalvik_system_VMRuntime,
+            WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer));
+    // If the consumer is non-null, we call back to it to let it know that we
+    // have encountered an API that's in one of our lists.
+    if (consumer_object != nullptr) {
+      std::ostringstream member_signature_str;
+      member_signature.Dump(member_signature_str);
+
+      ScopedLocalRef<jobject> signature_str(
+          soa.Env(),
+          soa.Env()->NewStringUTF(member_signature_str.str().c_str()));
+
+      // Call through to Consumer.accept(String memberSignature);
+      soa.Env()->CallVoidMethod(consumer_object.get(),
+                                WellKnownClasses::java_util_function_Consumer_accept,
+                                signature_str.get());
+    }
+  }
+
   if (action == kDeny) {
     // Block access
     return true;
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index ad05856..1c17806 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -535,18 +535,19 @@
 static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
                                                jstring name, jobjectArray args) {
   ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<1> hs(soa.Self());
   DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
   DCHECK(!Runtime::Current()->IsActiveTransaction());
-  ObjPtr<mirror::Method> result =
+  Handle<mirror::Method> result = hs.NewHandle(
       mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
           soa.Self(),
           DecodeClass(soa, javaThis),
           soa.Decode<mirror::String>(name),
-          soa.Decode<mirror::ObjectArray<mirror::Class>>(args));
+          soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
   if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
     return nullptr;
   }
-  return soa.AddLocalReference<jobject>(result);
+  return soa.AddLocalReference<jobject>(result.Get());
 }
 
 static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaThis,
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index bf36ccf..742e713 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -77,6 +77,7 @@
 jclass WellKnownClasses::java_nio_DirectByteBuffer;
 jclass WellKnownClasses::java_util_ArrayList;
 jclass WellKnownClasses::java_util_Collections;
+jclass WellKnownClasses::java_util_function_Consumer;
 jclass WellKnownClasses::libcore_reflect_AnnotationFactory;
 jclass WellKnownClasses::libcore_reflect_AnnotationMember;
 jclass WellKnownClasses::libcore_util_EmptyArray;
@@ -115,6 +116,7 @@
 jmethodID WellKnownClasses::java_lang_ThreadGroup_add;
 jmethodID WellKnownClasses::java_lang_ThreadGroup_removeThread;
 jmethodID WellKnownClasses::java_nio_DirectByteBuffer_init;
+jmethodID WellKnownClasses::java_util_function_Consumer_accept;
 jmethodID WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation;
 jmethodID WellKnownClasses::libcore_reflect_AnnotationMember_init;
 jmethodID WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
@@ -125,6 +127,7 @@
 jfieldID WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList;
 jfieldID WellKnownClasses::dalvik_system_DexPathList_dexElements;
 jfieldID WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
+jfieldID WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
 jfieldID WellKnownClasses::java_lang_Thread_daemon;
 jfieldID WellKnownClasses::java_lang_Thread_group;
 jfieldID WellKnownClasses::java_lang_Thread_lock;
@@ -349,6 +352,7 @@
   java_nio_DirectByteBuffer = CacheClass(env, "java/nio/DirectByteBuffer");
   java_util_ArrayList = CacheClass(env, "java/util/ArrayList");
   java_util_Collections = CacheClass(env, "java/util/Collections");
+  java_util_function_Consumer = CacheClass(env, "java/util/function/Consumer");
   libcore_reflect_AnnotationFactory = CacheClass(env, "libcore/reflect/AnnotationFactory");
   libcore_reflect_AnnotationMember = CacheClass(env, "libcore/reflect/AnnotationMember");
   libcore_util_EmptyArray = CacheClass(env, "libcore/util/EmptyArray");
@@ -379,6 +383,7 @@
   java_lang_ThreadGroup_add = CacheMethod(env, java_lang_ThreadGroup, false, "add", "(Ljava/lang/Thread;)V");
   java_lang_ThreadGroup_removeThread = CacheMethod(env, java_lang_ThreadGroup, false, "threadTerminated", "(Ljava/lang/Thread;)V");
   java_nio_DirectByteBuffer_init = CacheMethod(env, java_nio_DirectByteBuffer, false, "<init>", "(JI)V");
+  java_util_function_Consumer_accept = CacheMethod(env, java_util_function_Consumer, false, "accept", "(Ljava/lang/Object;)V");
   libcore_reflect_AnnotationFactory_createAnnotation = CacheMethod(env, libcore_reflect_AnnotationFactory, true, "createAnnotation", "(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation;");
   libcore_reflect_AnnotationMember_init = CacheMethod(env, libcore_reflect_AnnotationMember, false, "<init>", "(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/reflect/Method;)V");
   org_apache_harmony_dalvik_ddmc_DdmServer_broadcast = CacheMethod(env, org_apache_harmony_dalvik_ddmc_DdmServer, true, "broadcast", "(I)V");
@@ -389,6 +394,7 @@
   dalvik_system_DexFile_fileName = CacheField(env, dalvik_system_DexFile, false, "mFileName", "Ljava/lang/String;");
   dalvik_system_DexPathList_dexElements = CacheField(env, dalvik_system_DexPathList, false, "dexElements", "[Ldalvik/system/DexPathList$Element;");
   dalvik_system_DexPathList__Element_dexFile = CacheField(env, dalvik_system_DexPathList__Element, false, "dexFile", "Ldalvik/system/DexFile;");
+  dalvik_system_VMRuntime_nonSdkApiUsageConsumer = CacheField(env, dalvik_system_VMRuntime, true, "nonSdkApiUsageConsumer", "Ljava/util/function/Consumer;");
   java_lang_Thread_daemon = CacheField(env, java_lang_Thread, false, "daemon", "Z");
   java_lang_Thread_group = CacheField(env, java_lang_Thread, false, "group", "Ljava/lang/ThreadGroup;");
   java_lang_Thread_lock = CacheField(env, java_lang_Thread, false, "lock", "Ljava/lang/Object;");
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index d5d7033..7b1a294 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -85,6 +85,7 @@
   static jclass java_lang_Throwable;
   static jclass java_util_ArrayList;
   static jclass java_util_Collections;
+  static jclass java_util_function_Consumer;
   static jclass java_nio_ByteBuffer;
   static jclass java_nio_DirectByteBuffer;
   static jclass libcore_reflect_AnnotationFactory;
@@ -125,6 +126,7 @@
   static jmethodID java_lang_ThreadGroup_add;
   static jmethodID java_lang_ThreadGroup_removeThread;
   static jmethodID java_nio_DirectByteBuffer_init;
+  static jmethodID java_util_function_Consumer_accept;
   static jmethodID libcore_reflect_AnnotationFactory_createAnnotation;
   static jmethodID libcore_reflect_AnnotationMember_init;
   static jmethodID org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
@@ -135,6 +137,7 @@
   static jfieldID dalvik_system_DexFile_fileName;
   static jfieldID dalvik_system_DexPathList_dexElements;
   static jfieldID dalvik_system_DexPathList__Element_dexFile;
+  static jfieldID dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
   static jfieldID java_lang_reflect_Executable_artMethod;
   static jfieldID java_lang_reflect_Proxy_h;
   static jfieldID java_lang_Thread_daemon;
diff --git a/test/674-hiddenapi/build b/test/674-hiddenapi/build
index 9012e8f..330a6de 100644
--- a/test/674-hiddenapi/build
+++ b/test/674-hiddenapi/build
@@ -16,15 +16,6 @@
 
 set -e
 
-# Special build logic to handle src-ex .java files which have code that only builds on RI.
-custom_build_logic() {
-  [[ -d ignore.src-ex ]] && mv ignore.src-ex src-ex
-  # src-ex uses code that can only build on RI.
-  ${JAVAC} -source 1.8 -target 1.8 -sourcepath src-ex -sourcepath src -d classes-ex $(find src-ex -name '*.java')
-  # remove src-ex so that default-build doesn't try to build it.
-  [[ -d src-ex ]] && mv src-ex ignore.src-ex
-}
-
 # Build the jars twice. First with applying hiddenapi, creating a boot jar, then
 # a second time without to create a normal jar. We need to do this because we
 # want to load the jar once as an app module and once as a member of the boot
@@ -33,7 +24,6 @@
 # class path dex files, so the boot jar loads fine in the latter case.
 
 export USE_HIDDENAPI=true
-custom_build_logic
 ./default-build "$@"
 
 # Move the jar file into the resource folder to be bundled with the test.
@@ -45,5 +35,4 @@
 rm -rf classes*
 
 export USE_HIDDENAPI=false
-custom_build_logic
 ./default-build "$@"
diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java
index 582e907..822224c 100644
--- a/test/674-hiddenapi/src-ex/ChildClass.java
+++ b/test/674-hiddenapi/src-ex/ChildClass.java
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+import dalvik.system.VMRuntime;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -21,11 +22,7 @@
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
-
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.type.PrimitiveType;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeVisitor;
+import java.util.function.Consumer;
 
 public class ChildClass {
   enum PrimitiveType {
@@ -136,6 +133,47 @@
     }
   }
 
+  static final class RecordingConsumer implements Consumer<String> {
+      public String recordedValue = null;
+
+      @Override
+      public void accept(String value) {
+          recordedValue = value;
+      }
+  }
+
+  private static void checkMemberCallback(Class<?> klass, String name,
+          boolean isPublic, boolean isField) {
+      try {
+          RecordingConsumer consumer = new RecordingConsumer();
+          VMRuntime.setNonSdkApiUsageConsumer(consumer);
+          try {
+              if (isPublic) {
+                  if (isField) {
+                      klass.getField(name);
+                  } else {
+                      klass.getMethod(name);
+                  }
+              } else {
+                  if (isField) {
+                      klass.getDeclaredField(name);
+                  } else {
+                      klass.getDeclaredMethod(name);
+                  }
+              }
+          } catch (NoSuchFieldException|NoSuchMethodException ignored) {
+              // We're not concerned whether an exception is thrown or not - we're
+              // only interested in whether the callback is invoked.
+          }
+
+          if (consumer.recordedValue == null || !consumer.recordedValue.contains(name)) {
+              throw new RuntimeException("No callback for member: " + name);
+          }
+      } finally {
+          VMRuntime.setNonSdkApiUsageConsumer(null);
+      }
+  }
+
   private static void checkField(Class<?> klass, String name, boolean isStatic,
       Visibility visibility, Behaviour behaviour) throws Exception {
 
@@ -174,48 +212,52 @@
 
     // Finish here if we could not discover the field.
 
-    if (!canDiscover) {
-      return;
+    if (canDiscover) {
+      // Test that modifiers are unaffected.
+
+      if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) {
+        throwModifiersException(klass, name, true);
+      }
+
+      // Test getters and setters when meaningful.
+
+      clearWarning();
+      if (!Reflection.canGetField(klass, name)) {
+        throwAccessException(klass, name, true, "Field.getInt()");
+      }
+      if (hasPendingWarning() != setsWarning) {
+        throwWarningException(klass, name, true, "Field.getInt()", setsWarning);
+      }
+
+      clearWarning();
+      if (!Reflection.canSetField(klass, name)) {
+        throwAccessException(klass, name, true, "Field.setInt()");
+      }
+      if (hasPendingWarning() != setsWarning) {
+        throwWarningException(klass, name, true, "Field.setInt()", setsWarning);
+      }
+
+      clearWarning();
+      if (!JNI.canGetField(klass, name, isStatic)) {
+        throwAccessException(klass, name, true, "getIntField");
+      }
+      if (hasPendingWarning() != setsWarning) {
+        throwWarningException(klass, name, true, "getIntField", setsWarning);
+      }
+
+      clearWarning();
+      if (!JNI.canSetField(klass, name, isStatic)) {
+        throwAccessException(klass, name, true, "setIntField");
+      }
+      if (hasPendingWarning() != setsWarning) {
+        throwWarningException(klass, name, true, "setIntField", setsWarning);
+      }
     }
 
-    // Test that modifiers are unaffected.
-
-    if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) {
-      throwModifiersException(klass, name, true);
-    }
-
-    // Test getters and setters when meaningful.
-
+    // Test that callbacks are invoked correctly.
     clearWarning();
-    if (!Reflection.canGetField(klass, name)) {
-      throwAccessException(klass, name, true, "Field.getInt()");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, name, true, "Field.getInt()", setsWarning);
-    }
-
-    clearWarning();
-    if (!Reflection.canSetField(klass, name)) {
-      throwAccessException(klass, name, true, "Field.setInt()");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, name, true, "Field.setInt()", setsWarning);
-    }
-
-    clearWarning();
-    if (!JNI.canGetField(klass, name, isStatic)) {
-      throwAccessException(klass, name, true, "getIntField");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, name, true, "getIntField", setsWarning);
-    }
-
-    clearWarning();
-    if (!JNI.canSetField(klass, name, isStatic)) {
-      throwAccessException(klass, name, true, "setIntField");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, name, true, "setIntField", setsWarning);
+    if (setsWarning || !canDiscover) {
+      checkMemberCallback(klass, name, isPublic, true /* isField */);
     }
   }
 
@@ -257,42 +299,46 @@
 
     // Finish here if we could not discover the field.
 
-    if (!canDiscover) {
-      return;
+    if (canDiscover) {
+      // Test that modifiers are unaffected.
+
+      if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) {
+        throwModifiersException(klass, name, false);
+      }
+
+      // Test whether we can invoke the method. This skips non-static interface methods.
+
+      if (!klass.isInterface() || isStatic) {
+        clearWarning();
+        if (!Reflection.canInvokeMethod(klass, name)) {
+          throwAccessException(klass, name, false, "invoke()");
+        }
+        if (hasPendingWarning() != setsWarning) {
+          throwWarningException(klass, name, false, "invoke()", setsWarning);
+        }
+
+        clearWarning();
+        if (!JNI.canInvokeMethodA(klass, name, isStatic)) {
+          throwAccessException(klass, name, false, "CallMethodA");
+        }
+        if (hasPendingWarning() != setsWarning) {
+          throwWarningException(klass, name, false, "CallMethodA()", setsWarning);
+        }
+
+        clearWarning();
+        if (!JNI.canInvokeMethodV(klass, name, isStatic)) {
+          throwAccessException(klass, name, false, "CallMethodV");
+        }
+        if (hasPendingWarning() != setsWarning) {
+          throwWarningException(klass, name, false, "CallMethodV()", setsWarning);
+        }
+      }
     }
 
-    // Test that modifiers are unaffected.
-
-    if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) {
-      throwModifiersException(klass, name, false);
-    }
-
-    // Test whether we can invoke the method. This skips non-static interface methods.
-
-    if (!klass.isInterface() || isStatic) {
-      clearWarning();
-      if (!Reflection.canInvokeMethod(klass, name)) {
-        throwAccessException(klass, name, false, "invoke()");
-      }
-      if (hasPendingWarning() != setsWarning) {
-        throwWarningException(klass, name, false, "invoke()", setsWarning);
-      }
-
-      clearWarning();
-      if (!JNI.canInvokeMethodA(klass, name, isStatic)) {
-        throwAccessException(klass, name, false, "CallMethodA");
-      }
-      if (hasPendingWarning() != setsWarning) {
-        throwWarningException(klass, name, false, "CallMethodA()", setsWarning);
-      }
-
-      clearWarning();
-      if (!JNI.canInvokeMethodV(klass, name, isStatic)) {
-        throwAccessException(klass, name, false, "CallMethodV");
-      }
-      if (hasPendingWarning() != setsWarning) {
-        throwWarningException(klass, name, false, "CallMethodV()", setsWarning);
-      }
+    // Test that callbacks are invoked correctly.
+    clearWarning();
+    if (setsWarning || !canDiscover) {
+        checkMemberCallback(klass, name, isPublic, false /* isField */);
     }
   }