Use JVMTI extension API to get the list of defined classes
Use JVMTI extension API to get the list of defined classes from the boot
class loader. This change is required since dex files are stripped in
userdebug and user builds. Furthermore, even when there is dex files
(e.g. for eng builds), reading dex files using DexFile class is no
longer supported because we intentionally have invalid access flags for
the boot class path jars to hide private APIs from apps.
Bug: 73504235
Test: atest . under cts/tests/signature/api-check/system-annotation
Change-Id: I0cb2bed9d389b5a5f762a3369b674e4a9f8c0bc0
diff --git a/tests/signature/api-check/Android.mk b/tests/signature/api-check/Android.mk
index 888ace3..9ee92c9 100644
--- a/tests/signature/api-check/Android.mk
+++ b/tests/signature/api-check/Android.mk
@@ -22,7 +22,7 @@
# don't include this package in any target
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src/java)
LOCAL_MODULE := cts-api-signature-test
@@ -37,4 +37,13 @@
include $(BUILD_STATIC_JAVA_LIBRARY)
+include $(CLEAR_VARS)
+LOCAL_MODULE := libclassdescriptors
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := src/jni/classdescriptors.cpp
+LOCAL_HEADER_LIBRARIES := jni_headers libopenjdkjvmti_headers
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+include $(BUILD_SHARED_LIBRARY)
+
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml b/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
index 684872f..877e911 100644
--- a/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="25" android:targetSdkVersion="27"/>
- <application/>
+ <application android:debuggable="true"/>
<instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
android:targetPackage="android.signature.cts.api.android_test_base_27"
diff --git a/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml b/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
index 5c88521..f0fc8e1 100644
--- a/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
- <application>
+ <application android:debuggable="true">
<uses-library android:name="android.test.mock"/>
</application>
diff --git a/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml b/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
index 61de501..f81665e 100644
--- a/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
- <application>
+ <application android:debuggable="true">
<uses-library android:name="android.test.runner"/>
</application>
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml b/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
index eaf118b..f0fb7d8 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
- <application/>
+ <application android:debuggable="true"/>
<instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
android:targetPackage="android.signature.cts.api.apache_http_legacy_current"
diff --git a/tests/signature/api-check/build_signature_apk.mk b/tests/signature/api-check/build_signature_apk.mk
index 3b0afd2..1bae842 100644
--- a/tests/signature/api-check/build_signature_apk.mk
+++ b/tests/signature/api-check/build_signature_apk.mk
@@ -21,7 +21,7 @@
# the list of api files needed
# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_TAGS := tests
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
@@ -30,9 +30,15 @@
LOCAL_STATIC_JAVA_LIBRARIES := cts-api-signature-test
+LOCAL_JNI_SHARED_LIBRARIES := libclassdescriptors
+LOCAL_MULTILIB := both
+
LOCAL_ADDITIONAL_DEPENDENCIES += \
$(addprefix $(COMPATIBILITY_TESTCASES_OUT_cts)/,$(LOCAL_SIGNATURE_API_FILES))
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
include $(BUILD_CTS_PACKAGE)
LOCAL_SIGNATURE_API_FILES :=
diff --git a/tests/signature/api-check/current-api/AndroidManifest.xml b/tests/signature/api-check/current-api/AndroidManifest.xml
index 7dc5730..d6f474b 100644
--- a/tests/signature/api-check/current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/current-api/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
- <application/>
+ <application android:debuggable="true"/>
<instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
android:targetPackage="android.signature.cts.api.current"
diff --git a/tests/signature/api-check/src/android/signature/cts/api/BootClassPathClassesProvider.java b/tests/signature/api-check/src/android/signature/cts/api/BootClassPathClassesProvider.java
deleted file mode 100644
index 02bd72e..0000000
--- a/tests/signature/api-check/src/android/signature/cts/api/BootClassPathClassesProvider.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.signature.cts.api;
-
-import android.signature.cts.ClassProvider;
-import dalvik.system.DexFile;
-import java.io.IOException;
-import java.util.Enumeration;
-import java.util.stream.Stream;
-
-@SuppressWarnings("deprecation")
-public class BootClassPathClassesProvider extends ClassProvider {
- @Override
- public Stream<Class<?>> getAllClasses() {
- Stream.Builder<Class<?>> builder = Stream.builder();
- for (String file : getBootJarPaths()) {
- try {
- DexFile dexFile = new DexFile(file);
- Enumeration<String> entries = dexFile.entries();
- while (entries.hasMoreElements()) {
- String className = entries.nextElement();
- Class<?> clazz = getClass(className);
- if (clazz != null) {
- builder.add(clazz);
- }
- }
- } catch (IOException e) {
- throw new RuntimeException("Failed to parse dex in " + file, e);
- } catch (ClassNotFoundException e) {
- throw new RuntimeException("Error while loading class in " + file, e);
- }
- }
- return builder.build();
- }
-
- private String[] getBootJarPaths() {
- return System.getProperty("java.boot.class.path").split(":");
- }
-}
\ No newline at end of file
diff --git a/tests/signature/api-check/src/android/signature/cts/api/AbstractApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
similarity index 100%
rename from tests/signature/api-check/src/android/signature/cts/api/AbstractApiTest.java
rename to tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
diff --git a/tests/signature/api-check/src/android/signature/cts/api/AnnotationTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AnnotationTest.java
similarity index 100%
rename from tests/signature/api-check/src/android/signature/cts/api/AnnotationTest.java
rename to tests/signature/api-check/src/java/android/signature/cts/api/AnnotationTest.java
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/BootClassPathClassesProvider.java b/tests/signature/api-check/src/java/android/signature/cts/api/BootClassPathClassesProvider.java
new file mode 100644
index 0000000..95f46df
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/BootClassPathClassesProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.api;
+
+import android.os.Debug;
+import android.signature.cts.ClassProvider;
+import dalvik.system.BaseDexClassLoader;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+@SuppressWarnings("deprecation")
+public class BootClassPathClassesProvider extends ClassProvider {
+ private static boolean sJvmtiAttached = false;
+
+ @Override
+ public Stream<Class<?>> getAllClasses() {
+ if (!sJvmtiAttached) {
+ try {
+ Debug.attachJvmtiAgent(copyAgentToFile("classdescriptors").getAbsolutePath(), null,
+ BootClassPathClassesProvider.class.getClassLoader());
+ sJvmtiAttached = true;
+ initialize();
+ } catch (Exception e) {
+ throw new RuntimeException("Error while attaching JVMTI agent", e);
+ }
+ }
+ return Arrays.stream(getClassloaderDescriptors(Object.class.getClassLoader()))
+ .map(descriptor -> {
+ System.err.println("Class name = " + descriptor);
+ String classname = descriptor.replace('/', '.');
+ // omit L and ; at the front and at the end
+ return classname.substring(1, classname.length() - 1);
+ })
+ .map(classname -> {
+ try {
+ return getClass(classname);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Cannot load " + classname, e);
+ }
+ });
+ }
+
+ private static File copyAgentToFile(String lib) throws Exception {
+ ClassLoader cl = BootClassPathClassesProvider.class.getClassLoader();
+
+ File copiedAgent = File.createTempFile("agent", ".so");
+ try (InputStream is = new FileInputStream(
+ ((BaseDexClassLoader) cl).findLibrary(lib))) {
+ try (OutputStream os = new FileOutputStream(copiedAgent)) {
+ byte[] buffer = new byte[64 * 1024];
+
+ while (true) {
+ int numRead = is.read(buffer);
+ if (numRead == -1) {
+ break;
+ }
+ os.write(buffer, 0, numRead);
+ }
+ }
+ }
+ return copiedAgent;
+ }
+
+ private static native void initialize();
+
+ private static native String[] getClassloaderDescriptors(ClassLoader loader);
+}
diff --git a/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
similarity index 100%
rename from tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
rename to tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
diff --git a/tests/signature/api-check/src/android/signature/cts/api/TestResultObserver.java b/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
similarity index 100%
rename from tests/signature/api-check/src/android/signature/cts/api/TestResultObserver.java
rename to tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
diff --git a/tests/signature/api-check/src/jni/classdescriptors.cpp b/tests/signature/api-check/src/jni/classdescriptors.cpp
new file mode 100644
index 0000000..3da66f1
--- /dev/null
+++ b/tests/signature/api-check/src/jni/classdescriptors.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <jvmti.h>
+
+#include <string.h>
+
+namespace android {
+namespace signature {
+namespace cts {
+namespace api {
+
+static jvmtiEnv* jvmti_env;
+static jvmtiError (*get_descriptor_list)(jvmtiEnv* env, jobject loader, jint* cnt, char*** descs);
+
+template <typename T>
+static void Dealloc(T* t) {
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename ...Rest>
+static void Dealloc(T* t, Rest... rs) {
+ Dealloc(t);
+ Dealloc(rs...);
+}
+
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+ for (jint i = 0; i < n_params; i++) {
+ Dealloc(params[i].name);
+ }
+}
+
+static void Cleanup(char** data, jint cnt) {
+ for (jint i = 0; i < cnt; i++) {
+ Dealloc(data[i]);
+ }
+ Dealloc(data);
+}
+
+extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm,
+ __attribute__((unused)) char* options,
+ __attribute__((unused)) void* reserved) {
+ jint jvmError = vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_2);
+ if (jvmError != JNI_OK) {
+ return jvmError;
+ }
+ return JVMTI_ERROR_NONE;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_android_signature_cts_api_BootClassPathClassesProvider_getClassloaderDescriptors(
+ JNIEnv* env, jclass, jobject loader) {
+ if (get_descriptor_list == nullptr) {
+ jclass rt_exception = env->FindClass("java/lang/RuntimeException");
+ env->ThrowNew(rt_exception, "get_class_loader_class_descriptor extension is not ready.");
+ return nullptr;
+ }
+ char** classes = nullptr;
+ jint cnt = -1;
+ jvmtiError error = get_descriptor_list(jvmti_env, loader, &cnt, &classes);
+ if (error != JVMTI_ERROR_NONE) {
+ jclass rt_exception = env->FindClass("java/lang/RuntimeException");
+ env->ThrowNew(rt_exception, "Error while executing get_class_loader_class_descriptor.");
+ return nullptr;
+ }
+
+ jobjectArray arr = env->NewObjectArray(cnt, env->FindClass("java/lang/String"), nullptr);
+ if (env->ExceptionCheck()) {
+ Cleanup(classes, cnt);
+ return nullptr;
+ }
+
+ for (jint i = 0; i < cnt; i++) {
+ env->SetObjectArrayElement(arr, i, env->NewStringUTF(classes[i]));
+ if (env->ExceptionCheck()) {
+ Cleanup(classes, cnt);
+ return nullptr;
+ }
+ }
+ Cleanup(classes, cnt);
+ return arr;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_android_signature_cts_api_BootClassPathClassesProvider_initialize(JNIEnv* env, jclass) {
+ jint functionInfosCount = 0;
+ jvmtiExtensionFunctionInfo* functionInfos = nullptr;
+
+ jvmtiError err = jvmti_env->GetExtensionFunctions(&functionInfosCount, &functionInfos);
+ if (err != JVMTI_ERROR_NONE) {
+ jclass rt_exception = env->FindClass("java/lang/RuntimeException");
+ env->ThrowNew(rt_exception, "Failed to get JVMTI extension APIs");
+ return;
+ }
+
+ for (jint i = 0; i < functionInfosCount; i++) {
+ jvmtiExtensionFunctionInfo* curInfo = &functionInfos[i];
+ if (strcmp("com.android.art.class.get_class_loader_class_descriptors", curInfo->id) == 0) {
+ get_descriptor_list = reinterpret_cast<jvmtiError (*)(jvmtiEnv*, jobject, jint*, char***)>(curInfo->func);
+ }
+ DeallocParams(curInfo->params, curInfo->param_count);
+ Dealloc(curInfo->id, curInfo->short_description, curInfo->params, curInfo->errors);
+ }
+ Dealloc(functionInfos);
+
+ if (get_descriptor_list == nullptr) {
+ jclass rt_exception = env->FindClass("java/lang/RuntimeException");
+ env->ThrowNew(rt_exception, "Failed to find get_class_loader_class_descriptors extension");
+ return;
+ }
+}
+
+} // namespace api
+} // namespace cts
+} // namespace signature
+} // namespace android
diff --git a/tests/signature/api-check/system-annotation/AndroidManifest.xml b/tests/signature/api-check/system-annotation/AndroidManifest.xml
index 55318ed..7753fb9 100644
--- a/tests/signature/api-check/system-annotation/AndroidManifest.xml
+++ b/tests/signature/api-check/system-annotation/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
- <application/>
+ <application android:debuggable="true"/>
<instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
android:targetPackage="android.signature.cts.api.system_annotation"