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 */);
}
}