summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/674-hiddenapi/api-blacklist.txt25
-rw-r--r--test/674-hiddenapi/api-dark-greylist.txt25
-rw-r--r--test/674-hiddenapi/api-light-greylist.txt25
-rw-r--r--test/674-hiddenapi/build38
-rw-r--r--test/674-hiddenapi/check20
-rw-r--r--test/674-hiddenapi/expected.txt5
-rw-r--r--test/674-hiddenapi/hiddenapi.cc291
-rw-r--r--test/674-hiddenapi/info.txt15
-rw-r--r--test/674-hiddenapi/src-art/Main.java144
-rw-r--r--test/674-hiddenapi/src-ex/ChildClass.java429
-rw-r--r--test/674-hiddenapi/src-ex/JNI.java29
-rw-r--r--test/674-hiddenapi/src-ex/Linking.java193
-rw-r--r--test/674-hiddenapi/src-ex/Reflection.java205
-rw-r--r--test/674-hiddenapi/src/NullaryConstructorBlacklist.java21
-rw-r--r--test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java21
-rw-r--r--test/674-hiddenapi/src/NullaryConstructorLightGreylist.java21
-rw-r--r--test/674-hiddenapi/src/NullaryConstructorWhitelist.java21
-rw-r--r--test/674-hiddenapi/src/ParentClass.java133
-rw-r--r--test/674-hiddenapi/src/ParentInterface.java41
-rw-r--r--test/Android.bp1
-rw-r--r--test/Android.run-test.mk1
-rwxr-xr-xtest/etc/default-build47
-rwxr-xr-xtest/run-test5
23 files changed, 1755 insertions, 1 deletions
diff --git a/test/674-hiddenapi/api-blacklist.txt b/test/674-hiddenapi/api-blacklist.txt
new file mode 100644
index 0000000000..d43360c62f
--- /dev/null
+++ b/test/674-hiddenapi/api-blacklist.txt
@@ -0,0 +1,25 @@
+LNullaryConstructorBlacklist;-><init>()V
+LParentClass;->fieldPublicBlacklist:I
+LParentClass;->fieldPackageBlacklist:I
+LParentClass;->fieldProtectedBlacklist:I
+LParentClass;->fieldPrivateBlacklist:I
+LParentClass;->fieldPublicStaticBlacklist:I
+LParentClass;->fieldPackageStaticBlacklist:I
+LParentClass;->fieldProtectedStaticBlacklist:I
+LParentClass;->fieldPrivateStaticBlacklist:I
+LParentClass;->methodPublicBlacklist()I
+LParentClass;->methodPackageBlacklist()I
+LParentClass;->methodProtectedBlacklist()I
+LParentClass;->methodPrivateBlacklist()I
+LParentClass;->methodPublicStaticBlacklist()I
+LParentClass;->methodPackageStaticBlacklist()I
+LParentClass;->methodProtectedStaticBlacklist()I
+LParentClass;->methodPrivateStaticBlacklist()I
+LParentClass;-><init>(IC)V
+LParentClass;-><init>(FC)V
+LParentClass;-><init>(JC)V
+LParentClass;-><init>(DC)V
+LParentInterface;->fieldPublicStaticBlacklist:I
+LParentInterface;->methodPublicBlacklist()I
+LParentInterface;->methodPublicStaticBlacklist()I
+LParentInterface;->methodPublicDefaultBlacklist()I \ No newline at end of file
diff --git a/test/674-hiddenapi/api-dark-greylist.txt b/test/674-hiddenapi/api-dark-greylist.txt
new file mode 100644
index 0000000000..d0f35f64bc
--- /dev/null
+++ b/test/674-hiddenapi/api-dark-greylist.txt
@@ -0,0 +1,25 @@
+LNullaryConstructorDarkGreylist;-><init>()V
+LParentClass;->fieldPublicDarkGreylist:I
+LParentClass;->fieldPackageDarkGreylist:I
+LParentClass;->fieldProtectedDarkGreylist:I
+LParentClass;->fieldPrivateDarkGreylist:I
+LParentClass;->fieldPublicStaticDarkGreylist:I
+LParentClass;->fieldPackageStaticDarkGreylist:I
+LParentClass;->fieldProtectedStaticDarkGreylist:I
+LParentClass;->fieldPrivateStaticDarkGreylist:I
+LParentClass;->methodPublicDarkGreylist()I
+LParentClass;->methodPackageDarkGreylist()I
+LParentClass;->methodProtectedDarkGreylist()I
+LParentClass;->methodPrivateDarkGreylist()I
+LParentClass;->methodPublicStaticDarkGreylist()I
+LParentClass;->methodPackageStaticDarkGreylist()I
+LParentClass;->methodProtectedStaticDarkGreylist()I
+LParentClass;->methodPrivateStaticDarkGreylist()I
+LParentClass;-><init>(IB)V
+LParentClass;-><init>(FB)V
+LParentClass;-><init>(JB)V
+LParentClass;-><init>(DB)V
+LParentInterface;->fieldPublicStaticDarkGreylist:I
+LParentInterface;->methodPublicDarkGreylist()I
+LParentInterface;->methodPublicStaticDarkGreylist()I
+LParentInterface;->methodPublicDefaultDarkGreylist()I \ No newline at end of file
diff --git a/test/674-hiddenapi/api-light-greylist.txt b/test/674-hiddenapi/api-light-greylist.txt
new file mode 100644
index 0000000000..2809025cfd
--- /dev/null
+++ b/test/674-hiddenapi/api-light-greylist.txt
@@ -0,0 +1,25 @@
+LNullaryConstructorLightGreylist;-><init>()V
+LParentClass;->fieldPublicLightGreylist:I
+LParentClass;->fieldPackageLightGreylist:I
+LParentClass;->fieldProtectedLightGreylist:I
+LParentClass;->fieldPrivateLightGreylist:I
+LParentClass;->fieldPublicStaticLightGreylist:I
+LParentClass;->fieldPackageStaticLightGreylist:I
+LParentClass;->fieldProtectedStaticLightGreylist:I
+LParentClass;->fieldPrivateStaticLightGreylist:I
+LParentClass;->methodPublicLightGreylist()I
+LParentClass;->methodPackageLightGreylist()I
+LParentClass;->methodProtectedLightGreylist()I
+LParentClass;->methodPrivateLightGreylist()I
+LParentClass;->methodPublicStaticLightGreylist()I
+LParentClass;->methodPackageStaticLightGreylist()I
+LParentClass;->methodProtectedStaticLightGreylist()I
+LParentClass;->methodPrivateStaticLightGreylist()I
+LParentClass;-><init>(IZ)V
+LParentClass;-><init>(FZ)V
+LParentClass;-><init>(JZ)V
+LParentClass;-><init>(DZ)V
+LParentInterface;->fieldPublicStaticLightGreylist:I
+LParentInterface;->methodPublicLightGreylist()I
+LParentInterface;->methodPublicStaticLightGreylist()I
+LParentInterface;->methodPublicDefaultLightGreylist()I \ No newline at end of file
diff --git a/test/674-hiddenapi/build b/test/674-hiddenapi/build
new file mode 100644
index 0000000000..330a6def29
--- /dev/null
+++ b/test/674-hiddenapi/build
@@ -0,0 +1,38 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+set -e
+
+# 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
+# class path. The DexFileVerifier would fail on the former as it does not allow
+# hidden API access flags in dex files. DexFileVerifier is not invoked on boot
+# class path dex files, so the boot jar loads fine in the latter case.
+
+export USE_HIDDENAPI=true
+./default-build "$@"
+
+# Move the jar file into the resource folder to be bundled with the test.
+mkdir res
+mv ${TEST_NAME}.jar res/boot.jar
+
+# Clear all intermediate files otherwise default-build would either skip
+# compilation or fail rebuilding.
+rm -rf classes*
+
+export USE_HIDDENAPI=false
+./default-build "$@"
diff --git a/test/674-hiddenapi/check b/test/674-hiddenapi/check
new file mode 100644
index 0000000000..660643f1a0
--- /dev/null
+++ b/test/674-hiddenapi/check
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+# Remove pid and date from the log messages.
+grep -vE '^dalvikvm(32|64) E [^]]+]' "$2" | grep -v JNI_OnUnload > "$2.tmp"
+
+./default-check "$1" "$2.tmp"
diff --git a/test/674-hiddenapi/expected.txt b/test/674-hiddenapi/expected.txt
new file mode 100644
index 0000000000..727a061248
--- /dev/null
+++ b/test/674-hiddenapi/expected.txt
@@ -0,0 +1,5 @@
+JNI_OnLoad called
+JNI_OnLoad called
+JNI_OnLoad called
+JNI_OnLoad called
+Done
diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc
new file mode 100644
index 0000000000..672079b9d8
--- /dev/null
+++ b/test/674-hiddenapi/hiddenapi.cc
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2017 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 "class_linker.h"
+#include "dex/art_dex_file_loader.h"
+#include "jni.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread.h"
+#include "ti-agent/scoped_utf_chars.h"
+
+namespace art {
+namespace Test674HiddenApi {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_appendToBootClassLoader(
+ JNIEnv* env, jclass, jstring jpath) {
+ ScopedUtfChars utf(env, jpath);
+ const char* path = utf.c_str();
+ if (path == nullptr) {
+ return;
+ }
+
+ ArtDexFileLoader dex_loader;
+ std::string error_msg;
+ std::vector<std::unique_ptr<const DexFile>> dex_files;
+ if (!dex_loader.Open(path,
+ path,
+ /* verify */ false,
+ /* verify_checksum */ true,
+ &error_msg,
+ &dex_files)) {
+ LOG(FATAL) << "Could not open " << path << " for boot classpath extension: " << error_msg;
+ UNREACHABLE();
+ }
+
+ ScopedObjectAccess soa(Thread::Current());
+ for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
+ Runtime::Current()->GetClassLinker()->AppendToBootClassPath(
+ Thread::Current(), *dex_file.release());
+ }
+}
+
+static jobject NewInstance(JNIEnv* env, jclass klass) {
+ jmethodID constructor = env->GetMethodID(klass, "<init>", "()V");
+ if (constructor == NULL) {
+ return NULL;
+ }
+ return env->NewObject(klass, constructor);
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverField(
+ JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) {
+ ScopedUtfChars utf_name(env, name);
+ jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I")
+ : env->GetFieldID(klass, utf_name.c_str(), "I");
+ if (field == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canGetField(
+ JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) {
+ ScopedUtfChars utf_name(env, name);
+ jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I")
+ : env->GetFieldID(klass, utf_name.c_str(), "I");
+ if (field == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+ if (is_static) {
+ env->GetStaticIntField(klass, field);
+ } else {
+ jobject obj = NewInstance(env, klass);
+ if (obj == NULL) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+ env->GetIntField(obj, field);
+ }
+
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canSetField(
+ JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) {
+ ScopedUtfChars utf_name(env, name);
+ jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I")
+ : env->GetFieldID(klass, utf_name.c_str(), "I");
+ if (field == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+ if (is_static) {
+ env->SetStaticIntField(klass, field, 42);
+ } else {
+ jobject obj = NewInstance(env, klass);
+ if (obj == NULL) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+ env->SetIntField(obj, field, 42);
+ }
+
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverMethod(
+ JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) {
+ ScopedUtfChars utf_name(env, name);
+ jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I")
+ : env->GetMethodID(klass, utf_name.c_str(), "()I");
+ if (method == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeMethodA(
+ JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) {
+ ScopedUtfChars utf_name(env, name);
+ jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I")
+ : env->GetMethodID(klass, utf_name.c_str(), "()I");
+ if (method == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ if (is_static) {
+ env->CallStaticIntMethodA(klass, method, nullptr);
+ } else {
+ jobject obj = NewInstance(env, klass);
+ if (obj == NULL) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+ env->CallIntMethodA(obj, method, nullptr);
+ }
+
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeMethodV(
+ JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) {
+ ScopedUtfChars utf_name(env, name);
+ jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I")
+ : env->GetMethodID(klass, utf_name.c_str(), "()I");
+ if (method == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ if (is_static) {
+ env->CallStaticIntMethod(klass, method);
+ } else {
+ jobject obj = NewInstance(env, klass);
+ if (obj == NULL) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+ env->CallIntMethod(obj, method);
+ }
+
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+static constexpr size_t kConstructorSignatureLength = 5; // e.g. (IZ)V
+static constexpr size_t kNumConstructorArgs = kConstructorSignatureLength - 3;
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverConstructor(
+ JNIEnv* env, jclass, jclass klass, jstring args) {
+ ScopedUtfChars utf_args(env, args);
+ jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str());
+ if (constructor == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeConstructorA(
+ JNIEnv* env, jclass, jclass klass, jstring args) {
+ ScopedUtfChars utf_args(env, args);
+ jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str());
+ if (constructor == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ // CheckJNI won't allow out-of-range values, so just zero everything.
+ CHECK_EQ(strlen(utf_args.c_str()), kConstructorSignatureLength);
+ size_t initargs_size = sizeof(jvalue) * kNumConstructorArgs;
+ jvalue *initargs = reinterpret_cast<jvalue*>(alloca(initargs_size));
+ memset(initargs, 0, initargs_size);
+
+ env->NewObjectA(klass, constructor, initargs);
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeConstructorV(
+ JNIEnv* env, jclass, jclass klass, jstring args) {
+ ScopedUtfChars utf_args(env, args);
+ jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str());
+ if (constructor == NULL) {
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ // CheckJNI won't allow out-of-range values, so just zero everything.
+ CHECK_EQ(strlen(utf_args.c_str()), kConstructorSignatureLength);
+ size_t initargs_size = sizeof(jvalue) * kNumConstructorArgs;
+ jvalue *initargs = reinterpret_cast<jvalue*>(alloca(initargs_size));
+ memset(initargs, 0, initargs_size);
+
+ static_assert(kNumConstructorArgs == 2, "Change the varargs below if you change the constant");
+ env->NewObject(klass, constructor, initargs[0], initargs[1]);
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Reflection_getHiddenApiAccessFlags(JNIEnv*, jclass) {
+ return static_cast<jint>(kAccHiddenApiBits);
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_ChildClass_hasPendingWarning(JNIEnv*, jclass) {
+ return false;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_ChildClass_clearWarning(JNIEnv*, jclass) {
+ return;
+}
+
+} // namespace Test674HiddenApi
+} // namespace art
diff --git a/test/674-hiddenapi/info.txt b/test/674-hiddenapi/info.txt
new file mode 100644
index 0000000000..25ac6ae78f
--- /dev/null
+++ b/test/674-hiddenapi/info.txt
@@ -0,0 +1,15 @@
+Test whether hidden API access flags are being enforced. The test is composed of
+two JARs. The first (parent) defines methods and fields and the second (child)
+tries to access them with reflection/JNI or link against them. Note that the
+first is compiled twice - once with and once without hidden access flags.
+
+The test then proceeds to exercise the following combinations of class loading:
+(a) Both parent and child dex loaded with PathClassLoader, parent's class loader
+ is the child's class loader's parent. Access flags should not be enforced as
+ the parent does not belong to boot class path.
+(b) Parent is appended to boot class path, child is loaded with PathClassLoader.
+ In this situation child should not be able to access hidden methods/fields
+ of the parent.
+(c) Both parent and child are appended to boot class path. Restrictions should
+ not apply as hidden APIs are accessible within the boundaries of the boot
+ class path.
diff --git a/test/674-hiddenapi/src-art/Main.java b/test/674-hiddenapi/src-art/Main.java
new file mode 100644
index 0000000000..9773e0a9ca
--- /dev/null
+++ b/test/674-hiddenapi/src-art/Main.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+import dalvik.system.InMemoryDexClassLoader;
+import dalvik.system.PathClassLoader;
+import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+ prepareNativeLibFileName(args[0]);
+
+ // Run test with both parent and child dex files loaded with class loaders.
+ // The expectation is that hidden members in parent should be visible to
+ // the child.
+ doTest(false, false);
+
+ // Now append parent dex file to boot class path and run again. This time
+ // the child dex file should not be able to access private APIs of the parent.
+ appendToBootClassLoader(DEX_PARENT_BOOT);
+ doTest(true, false);
+
+ // And finally append to child to boot class path as well. With both in the
+ // boot class path, access should be granted.
+ appendToBootClassLoader(DEX_CHILD);
+ doTest(true, true);
+
+ System.out.println("Done");
+ }
+
+ private static void doTest(boolean parentInBoot, boolean childInBoot) throws Exception {
+ // Load parent dex if it is not in boot class path.
+ ClassLoader parentLoader = null;
+ if (parentInBoot) {
+ parentLoader = BOOT_CLASS_LOADER;
+ } else {
+ parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader());
+ }
+
+ // Load child dex if it is not in boot class path.
+ ClassLoader childLoader = null;
+ if (childInBoot) {
+ if (parentLoader != BOOT_CLASS_LOADER) {
+ throw new IllegalStateException(
+ "DeclaringClass must be in parent class loader of CallingClass");
+ }
+ childLoader = BOOT_CLASS_LOADER;
+ } else {
+ childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader);
+ }
+
+ // Create a unique copy of the native library. Each shared library can only
+ // be loaded once, but for some reason even classes from a class loader
+ // cannot register their native methods against symbols in a shared library
+ // loaded by their parent class loader.
+ String nativeLibCopy = createNativeLibCopy(parentInBoot, childInBoot);
+
+ // Invoke ChildClass.runTest
+ Class.forName("ChildClass", true, childLoader)
+ .getDeclaredMethod("runTest", String.class, Boolean.TYPE, Boolean.TYPE)
+ .invoke(null, nativeLibCopy, parentInBoot, childInBoot);
+ }
+
+ // Routine which tries to figure out the absolute path of our native library.
+ private static void prepareNativeLibFileName(String arg) throws Exception {
+ String libName = System.mapLibraryName(arg);
+ Method libPathsMethod = Runtime.class.getDeclaredMethod("getLibPaths");
+ libPathsMethod.setAccessible(true);
+ String[] libPaths = (String[]) libPathsMethod.invoke(Runtime.getRuntime());
+ nativeLibFileName = null;
+ for (String p : libPaths) {
+ String candidate = p + libName;
+ if (new File(candidate).exists()) {
+ nativeLibFileName = candidate;
+ break;
+ }
+ }
+ if (nativeLibFileName == null) {
+ throw new IllegalStateException("Didn't find " + libName + " in " +
+ Arrays.toString(libPaths));
+ }
+ }
+
+ // Helper to read dex file into memory.
+ private static ByteBuffer readDexFile(String jarFileName) throws Exception {
+ ZipFile zip = new ZipFile(new File(jarFileName));
+ ZipEntry entry = zip.getEntry("classes.dex");
+ InputStream is = zip.getInputStream(entry);
+ int offset = 0;
+ int size = (int) entry.getSize();
+ ByteBuffer buffer = ByteBuffer.allocate(size);
+ while (is.available() > 0) {
+ is.read(buffer.array(), offset, size - offset);
+ }
+ is.close();
+ zip.close();
+ return buffer;
+ }
+
+ // Copy native library to a new file with a unique name so it does not conflict
+ // with other loaded instance of the same binary file.
+ private static String createNativeLibCopy(boolean parentInBoot, boolean childInBoot)
+ throws Exception {
+ String tempFileName = System.mapLibraryName(
+ "hiddenapitest_" + (parentInBoot ? "1" : "0") + (childInBoot ? "1" : "0"));
+ File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName);
+ Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath());
+ return tempFile.getAbsolutePath();
+ }
+
+ private static String nativeLibFileName;
+
+ private static final String DEX_PARENT =
+ new File(System.getenv("DEX_LOCATION"), "674-hiddenapi.jar").getAbsolutePath();
+ private static final String DEX_PARENT_BOOT =
+ new File(new File(System.getenv("DEX_LOCATION"), "res"), "boot.jar").getAbsolutePath();
+ private static final String DEX_CHILD =
+ new File(System.getenv("DEX_LOCATION"), "674-hiddenapi-ex.jar").getAbsolutePath();
+
+ private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
+
+ private static native void appendToBootClassLoader(String dexPath);
+}
diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java
new file mode 100644
index 0000000000..af615bfd5f
--- /dev/null
+++ b/test/674-hiddenapi/src-ex/ChildClass.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+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;
+
+public class ChildClass {
+ enum PrimitiveType {
+ TInteger('I', Integer.TYPE, Integer.valueOf(0)),
+ TLong('J', Long.TYPE, Long.valueOf(0)),
+ TFloat('F', Float.TYPE, Float.valueOf(0)),
+ TDouble('D', Double.TYPE, Double.valueOf(0)),
+ TBoolean('Z', Boolean.TYPE, Boolean.valueOf(false)),
+ TByte('B', Byte.TYPE, Byte.valueOf((byte) 0)),
+ TShort('S', Short.TYPE, Short.valueOf((short) 0)),
+ TCharacter('C', Character.TYPE, Character.valueOf('0'));
+
+ PrimitiveType(char shorty, Class klass, Object value) {
+ mShorty = shorty;
+ mClass = klass;
+ mDefaultValue = value;
+ }
+
+ public char mShorty;
+ public Class mClass;
+ public Object mDefaultValue;
+ }
+
+ enum Hiddenness {
+ Whitelist(PrimitiveType.TShort),
+ LightGreylist(PrimitiveType.TBoolean),
+ DarkGreylist(PrimitiveType.TByte),
+ Blacklist(PrimitiveType.TCharacter);
+
+ Hiddenness(PrimitiveType type) { mAssociatedType = type; }
+ public PrimitiveType mAssociatedType;
+ }
+
+ enum Visibility {
+ Public(PrimitiveType.TInteger),
+ Package(PrimitiveType.TFloat),
+ Protected(PrimitiveType.TLong),
+ Private(PrimitiveType.TDouble);
+
+ Visibility(PrimitiveType type) { mAssociatedType = type; }
+ public PrimitiveType mAssociatedType;
+ }
+
+ enum Behaviour {
+ Granted,
+ Warning,
+ Denied,
+ }
+
+ private static final boolean booleanValues[] = new boolean[] { false, true };
+
+ public static void runTest(String libFileName, boolean expectedParentInBoot,
+ boolean expectedChildInBoot) throws Exception {
+ System.load(libFileName);
+
+ // Check expectations about loading into boot class path.
+ isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null);
+ if (isParentInBoot != expectedParentInBoot) {
+ throw new RuntimeException("Expected ParentClass " +
+ (expectedParentInBoot ? "" : "not ") + "in boot class path");
+ }
+ isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null);
+ if (isChildInBoot != expectedChildInBoot) {
+ throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") +
+ "in boot class path");
+ }
+
+ // Run meaningful combinations of access flags.
+ for (Hiddenness hiddenness : Hiddenness.values()) {
+ final Behaviour expected = Behaviour.Granted;
+
+ for (boolean isStatic : booleanValues) {
+ String suffix = (isStatic ? "Static" : "") + hiddenness.name();
+
+ for (Visibility visibility : Visibility.values()) {
+ // Test reflection and JNI on methods and fields
+ for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) {
+ String baseName = visibility.name() + suffix;
+ checkField(klass, "field" + baseName, isStatic, visibility, expected);
+ checkMethod(klass, "method" + baseName, isStatic, visibility, expected);
+ }
+
+ // Check whether one can use a class constructor.
+ checkConstructor(ParentClass.class, visibility, hiddenness, expected);
+
+ // Check whether you can use an interface default method.
+ String name = "method" + visibility.name() + "Default" + hiddenness.name();
+ checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected);
+ }
+
+ // Test whether static linking succeeds.
+ checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected);
+ checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected);
+ checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected);
+ }
+
+ // Check whether Class.newInstance succeeds.
+ checkNullaryConstructor(Class.forName("NullaryConstructor" + hiddenness.name()), expected);
+ }
+ }
+
+ private static void checkField(Class<?> klass, String name, boolean isStatic,
+ Visibility visibility, Behaviour behaviour) throws Exception {
+
+ boolean isPublic = (visibility == Visibility.Public);
+ boolean canDiscover = (behaviour != Behaviour.Denied);
+ boolean setsWarning = (behaviour == Behaviour.Warning);
+
+ if (klass.isInterface() && (!isStatic || !isPublic)) {
+ // Interfaces only have public static fields.
+ return;
+ }
+
+ // Test discovery with reflection.
+
+ if (Reflection.canDiscoverWithGetDeclaredField(klass, name) != canDiscover) {
+ throwDiscoveryException(klass, name, true, "getDeclaredField()", canDiscover);
+ }
+
+ if (Reflection.canDiscoverWithGetDeclaredFields(klass, name) != canDiscover) {
+ throwDiscoveryException(klass, name, true, "getDeclaredFields()", canDiscover);
+ }
+
+ if (Reflection.canDiscoverWithGetField(klass, name) != (canDiscover && isPublic)) {
+ throwDiscoveryException(klass, name, true, "getField()", (canDiscover && isPublic));
+ }
+
+ if (Reflection.canDiscoverWithGetFields(klass, name) != (canDiscover && isPublic)) {
+ throwDiscoveryException(klass, name, true, "getFields()", (canDiscover && isPublic));
+ }
+
+ // Test discovery with JNI.
+
+ if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) {
+ throwDiscoveryException(klass, name, true, "JNI", canDiscover);
+ }
+
+ // Finish here if we could not discover the field.
+
+ if (!canDiscover) {
+ return;
+ }
+
+ // 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);
+ }
+ }
+
+ private static void checkMethod(Class<?> klass, String name, boolean isStatic,
+ Visibility visibility, Behaviour behaviour) throws Exception {
+
+ boolean isPublic = (visibility == Visibility.Public);
+ if (klass.isInterface() && !isPublic) {
+ // All interface members are public.
+ return;
+ }
+
+ boolean canDiscover = (behaviour != Behaviour.Denied);
+ boolean setsWarning = (behaviour == Behaviour.Warning);
+
+ // Test discovery with reflection.
+
+ if (Reflection.canDiscoverWithGetDeclaredMethod(klass, name) != canDiscover) {
+ throwDiscoveryException(klass, name, false, "getDeclaredMethod()", canDiscover);
+ }
+
+ if (Reflection.canDiscoverWithGetDeclaredMethods(klass, name) != canDiscover) {
+ throwDiscoveryException(klass, name, false, "getDeclaredMethods()", canDiscover);
+ }
+
+ if (Reflection.canDiscoverWithGetMethod(klass, name) != (canDiscover && isPublic)) {
+ throwDiscoveryException(klass, name, false, "getMethod()", (canDiscover && isPublic));
+ }
+
+ if (Reflection.canDiscoverWithGetMethods(klass, name) != (canDiscover && isPublic)) {
+ throwDiscoveryException(klass, name, false, "getMethods()", (canDiscover && isPublic));
+ }
+
+ // Test discovery with JNI.
+
+ if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) {
+ throwDiscoveryException(klass, name, false, "JNI", canDiscover);
+ }
+
+ // Finish here if we could not discover the field.
+
+ if (!canDiscover) {
+ return;
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness,
+ Behaviour behaviour) throws Exception {
+
+ boolean isPublic = (visibility == Visibility.Public);
+ String signature = "(" + visibility.mAssociatedType.mShorty +
+ hiddenness.mAssociatedType.mShorty + ")V";
+ String fullName = "<init>" + signature;
+ Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass,
+ hiddenness.mAssociatedType.mClass };
+ Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue,
+ hiddenness.mAssociatedType.mDefaultValue };
+
+ boolean canDiscover = (behaviour != Behaviour.Denied);
+ boolean setsWarning = (behaviour == Behaviour.Warning);
+
+ // Test discovery with reflection.
+
+ if (Reflection.canDiscoverWithGetDeclaredConstructor(klass, args) != canDiscover) {
+ throwDiscoveryException(klass, fullName, false, "getDeclaredConstructor()", canDiscover);
+ }
+
+ if (Reflection.canDiscoverWithGetDeclaredConstructors(klass, args) != canDiscover) {
+ throwDiscoveryException(klass, fullName, false, "getDeclaredConstructors()", canDiscover);
+ }
+
+ if (Reflection.canDiscoverWithGetConstructor(klass, args) != (canDiscover && isPublic)) {
+ throwDiscoveryException(
+ klass, fullName, false, "getConstructor()", (canDiscover && isPublic));
+ }
+
+ if (Reflection.canDiscoverWithGetConstructors(klass, args) != (canDiscover && isPublic)) {
+ throwDiscoveryException(
+ klass, fullName, false, "getConstructors()", (canDiscover && isPublic));
+ }
+
+ // Test discovery with JNI.
+
+ if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) {
+ throwDiscoveryException(klass, fullName, false, "JNI", canDiscover);
+ }
+
+ // Finish here if we could not discover the field.
+
+ if (!canDiscover) {
+ return;
+ }
+
+ // Test whether we can invoke the constructor.
+
+ clearWarning();
+ if (!Reflection.canInvokeConstructor(klass, args, initargs)) {
+ throwAccessException(klass, fullName, false, "invoke()");
+ }
+ if (hasPendingWarning() != setsWarning) {
+ throwWarningException(klass, fullName, false, "invoke()", setsWarning);
+ }
+
+ clearWarning();
+ if (!JNI.canInvokeConstructorA(klass, signature)) {
+ throwAccessException(klass, fullName, false, "NewObjectA");
+ }
+ if (hasPendingWarning() != setsWarning) {
+ throwWarningException(klass, fullName, false, "NewObjectA", setsWarning);
+ }
+
+ clearWarning();
+ if (!JNI.canInvokeConstructorV(klass, signature)) {
+ throwAccessException(klass, fullName, false, "NewObjectV");
+ }
+ if (hasPendingWarning() != setsWarning) {
+ throwWarningException(klass, fullName, false, "NewObjectV", setsWarning);
+ }
+ }
+
+ private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour)
+ throws Exception {
+ boolean canAccess = (behaviour != Behaviour.Denied);
+ boolean setsWarning = (behaviour == Behaviour.Warning);
+
+ clearWarning();
+ if (Reflection.canUseNewInstance(klass) != canAccess) {
+ throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
+ "be able to construct " + klass.getName() + ". " +
+ "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
+ }
+ if (canAccess && hasPendingWarning() != setsWarning) {
+ throwWarningException(klass, "nullary constructor", false, "newInstance", setsWarning);
+ }
+ }
+
+ private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour)
+ throws Exception {
+ boolean canAccess = (behaviour != Behaviour.Denied);
+ boolean setsWarning = (behaviour == Behaviour.Warning);
+
+ clearWarning();
+ if (Linking.canAccess(className, takesParameter) != canAccess) {
+ throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
+ "be able to verify " + className + "." +
+ "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
+ }
+ if (canAccess && hasPendingWarning() != setsWarning) {
+ throwWarningException(
+ Class.forName(className), "access", false, "static linking", setsWarning);
+ }
+ }
+
+ private static void throwDiscoveryException(Class<?> klass, String name, boolean isField,
+ String fn, boolean canAccess) {
+ throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
+ "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " +
+ "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
+ }
+
+ private static void throwAccessException(Class<?> klass, String name, boolean isField,
+ String fn) {
+ throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") +
+ klass.getName() + "." + name + " using " + fn + ". " +
+ "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
+ }
+
+ private static void throwWarningException(Class<?> klass, String name, boolean isField,
+ String fn, boolean setsWarning) {
+ throw new RuntimeException("Expected access to " + (isField ? "field " : "method ") +
+ klass.getName() + "." + name + " using " + fn + " to " + (setsWarning ? "" : "not ") +
+ "set the warning flag. " +
+ "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
+ }
+
+ private static void throwModifiersException(Class<?> klass, String name, boolean isField) {
+ throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
+ "." + name + " to not expose hidden modifiers");
+ }
+
+ private static boolean isParentInBoot;
+ private static boolean isChildInBoot;
+
+ private static native boolean hasPendingWarning();
+ private static native void clearWarning();
+}
diff --git a/test/674-hiddenapi/src-ex/JNI.java b/test/674-hiddenapi/src-ex/JNI.java
new file mode 100644
index 0000000000..5dfb2963fa
--- /dev/null
+++ b/test/674-hiddenapi/src-ex/JNI.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+public class JNI {
+ public static native boolean canDiscoverField(Class<?> klass, String name, boolean isStatic);
+ public static native boolean canGetField(Class<?> klass, String name, boolean isStatic);
+ public static native boolean canSetField(Class<?> klass, String name, boolean isStatic);
+
+ public static native boolean canDiscoverMethod(Class<?> klass, String name, boolean isStatic);
+ public static native boolean canInvokeMethodA(Class<?> klass, String name, boolean isStatic);
+ public static native boolean canInvokeMethodV(Class<?> klass, String name, boolean isStatic);
+
+ public static native boolean canDiscoverConstructor(Class<?> klass, String signature);
+ public static native boolean canInvokeConstructorA(Class<?> klass, String signature);
+ public static native boolean canInvokeConstructorV(Class<?> klass, String signature);
+}
diff --git a/test/674-hiddenapi/src-ex/Linking.java b/test/674-hiddenapi/src-ex/Linking.java
new file mode 100644
index 0000000000..c6735d85fe
--- /dev/null
+++ b/test/674-hiddenapi/src-ex/Linking.java
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ */
+
+import java.lang.reflect.InvocationTargetException;
+
+public class Linking {
+ public static boolean canAccess(String className, boolean takesParameter) throws Exception {
+ try {
+ Class<?> c = Class.forName(className);
+ if (takesParameter) {
+ c.getDeclaredMethod("access", Integer.TYPE).invoke(null, 42);
+ } else {
+ c.getDeclaredMethod("access").invoke(null);
+ }
+ return true;
+ } catch (InvocationTargetException ex) {
+ if (ex.getCause() instanceof IllegalAccessError) {
+ return false;
+ } else {
+ throw ex;
+ }
+ }
+ }
+}
+
+// INSTANCE FIELD GET
+
+class LinkFieldGetWhitelist {
+ public static int access() {
+ return new ParentClass().fieldPublicWhitelist;
+ }
+}
+
+class LinkFieldGetLightGreylist {
+ public static int access() {
+ return new ParentClass().fieldPublicLightGreylist;
+ }
+}
+
+class LinkFieldGetDarkGreylist {
+ public static int access() {
+ return new ParentClass().fieldPublicDarkGreylist;
+ }
+}
+
+class LinkFieldGetBlacklist {
+ public static int access() {
+ return new ParentClass().fieldPublicBlacklist;
+ }
+}
+
+// INSTANCE FIELD SET
+
+class LinkFieldSetWhitelist {
+ public static void access(int x) {
+ new ParentClass().fieldPublicWhitelist = x;
+ }
+}
+
+class LinkFieldSetLightGreylist {
+ public static void access(int x) {
+ new ParentClass().fieldPublicLightGreylist = x;
+ }
+}
+
+class LinkFieldSetDarkGreylist {
+ public static void access(int x) {
+ new ParentClass().fieldPublicDarkGreylist = x;
+ }
+}
+
+class LinkFieldSetBlacklist {
+ public static void access(int x) {
+ new ParentClass().fieldPublicBlacklist = x;
+ }
+}
+
+// STATIC FIELD GET
+
+class LinkFieldGetStaticWhitelist {
+ public static int access() {
+ return ParentClass.fieldPublicStaticWhitelist;
+ }
+}
+
+class LinkFieldGetStaticLightGreylist {
+ public static int access() {
+ return ParentClass.fieldPublicStaticLightGreylist;
+ }
+}
+
+class LinkFieldGetStaticDarkGreylist {
+ public static int access() {
+ return ParentClass.fieldPublicStaticDarkGreylist;
+ }
+}
+
+class LinkFieldGetStaticBlacklist {
+ public static int access() {
+ return ParentClass.fieldPublicStaticBlacklist;
+ }
+}
+
+// STATIC FIELD SET
+
+class LinkFieldSetStaticWhitelist {
+ public static void access(int x) {
+ ParentClass.fieldPublicStaticWhitelist = x;
+ }
+}
+
+class LinkFieldSetStaticLightGreylist {
+ public static void access(int x) {
+ ParentClass.fieldPublicStaticLightGreylist = x;
+ }
+}
+
+class LinkFieldSetStaticDarkGreylist {
+ public static void access(int x) {
+ ParentClass.fieldPublicStaticDarkGreylist = x;
+ }
+}
+
+class LinkFieldSetStaticBlacklist {
+ public static void access(int x) {
+ ParentClass.fieldPublicStaticBlacklist = x;
+ }
+}
+
+// INVOKE INSTANCE METHOD
+
+class LinkMethodWhitelist {
+ public static int access() {
+ return new ParentClass().methodPublicWhitelist();
+ }
+}
+
+class LinkMethodLightGreylist {
+ public static int access() {
+ return new ParentClass().methodPublicLightGreylist();
+ }
+}
+
+class LinkMethodDarkGreylist {
+ public static int access() {
+ return new ParentClass().methodPublicDarkGreylist();
+ }
+}
+
+class LinkMethodBlacklist {
+ public static int access() {
+ return new ParentClass().methodPublicBlacklist();
+ }
+}
+
+// INVOKE STATIC METHOD
+
+class LinkMethodStaticWhitelist {
+ public static int access() {
+ return ParentClass.methodPublicStaticWhitelist();
+ }
+}
+
+class LinkMethodStaticLightGreylist {
+ public static int access() {
+ return ParentClass.methodPublicStaticLightGreylist();
+ }
+}
+
+class LinkMethodStaticDarkGreylist {
+ public static int access() {
+ return ParentClass.methodPublicStaticDarkGreylist();
+ }
+}
+
+class LinkMethodStaticBlacklist {
+ public static int access() {
+ return ParentClass.methodPublicStaticBlacklist();
+ }
+}
diff --git a/test/674-hiddenapi/src-ex/Reflection.java b/test/674-hiddenapi/src-ex/Reflection.java
new file mode 100644
index 0000000000..3667e91611
--- /dev/null
+++ b/test/674-hiddenapi/src-ex/Reflection.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+public class Reflection {
+ public static boolean canDiscoverWithGetDeclaredField(Class<?> klass, String name) {
+ try {
+ klass.getDeclaredField(name);
+ return true;
+ } catch (NoSuchFieldException ex) {
+ return false;
+ }
+ }
+
+ public static boolean canDiscoverWithGetDeclaredFields(Class<?> klass, String name) {
+ for (Field f : klass.getDeclaredFields()) {
+ if (f.getName().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean canDiscoverWithGetField(Class<?> klass, String name) {
+ try {
+ klass.getField(name);
+ return true;
+ } catch (NoSuchFieldException ex) {
+ return false;
+ }
+ }
+
+ public static boolean canDiscoverWithGetFields(Class<?> klass, String name) {
+ for (Field f : klass.getFields()) {
+ if (f.getName().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean canGetField(Class<?> klass, String name) {
+ try {
+ Field f = klass.getDeclaredField(name);
+ f.setAccessible(true);
+ f.getInt(Modifier.isStatic(f.getModifiers()) ? null : klass.newInstance());
+ return true;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+
+ public static boolean canSetField(Class<?> klass, String name) {
+ try {
+ Field f = klass.getDeclaredField(name);
+ f.setAccessible(true);
+ f.setInt(Modifier.isStatic(f.getModifiers()) ? null : klass.newInstance(), 42);
+ return true;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+
+ public static boolean canDiscoverWithGetDeclaredMethod(Class<?> klass, String name) {
+ try {
+ klass.getDeclaredMethod(name);
+ return true;
+ } catch (NoSuchMethodException ex) {
+ return false;
+ }
+ }
+
+ public static boolean canDiscoverWithGetDeclaredMethods(Class<?> klass, String name) {
+ for (Method m : klass.getDeclaredMethods()) {
+ if (m.getName().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean canDiscoverWithGetMethod(Class<?> klass, String name) {
+ try {
+ klass.getMethod(name);
+ return true;
+ } catch (NoSuchMethodException ex) {
+ return false;
+ }
+ }
+
+ public static boolean canDiscoverWithGetMethods(Class<?> klass, String name) {
+ for (Method m : klass.getMethods()) {
+ if (m.getName().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean canInvokeMethod(Class<?> klass, String name) {
+ try {
+ Method m = klass.getDeclaredMethod(name);
+ m.setAccessible(true);
+ m.invoke(klass.isInterface() ? null : klass.newInstance());
+ return true;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+
+ public static boolean canDiscoverWithGetDeclaredConstructor(Class<?> klass, Class<?> args[]) {
+ try {
+ klass.getDeclaredConstructor(args);
+ return true;
+ } catch (NoSuchMethodException ex) {
+ return false;
+ }
+ }
+
+ public static boolean canDiscoverWithGetDeclaredConstructors(Class<?> klass, Class<?> args[]) {
+ for (Constructor c : klass.getDeclaredConstructors()) {
+ if (Arrays.equals(c.getParameterTypes(), args)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean canDiscoverWithGetConstructor(Class<?> klass, Class<?> args[]) {
+ try {
+ klass.getConstructor(args);
+ return true;
+ } catch (NoSuchMethodException ex) {
+ return false;
+ }
+ }
+
+ public static boolean canDiscoverWithGetConstructors(Class<?> klass, Class<?> args[]) {
+ for (Constructor c : klass.getConstructors()) {
+ if (Arrays.equals(c.getParameterTypes(), args)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean canInvokeConstructor(Class<?> klass, Class<?> args[], Object[] initargs) {
+ try {
+ Constructor c = klass.getDeclaredConstructor(args);
+ c.setAccessible(true);
+ c.newInstance(initargs);
+ return true;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+
+ public static boolean canUseNewInstance(Class<?> klass) throws IllegalAccessException {
+ try {
+ klass.newInstance();
+ return true;
+ } catch (InstantiationException ex) {
+ return false;
+ }
+ }
+
+ private static native int getHiddenApiAccessFlags();
+
+ public static boolean canObserveFieldHiddenAccessFlags(Class<?> klass, String name)
+ throws Exception {
+ return (klass.getDeclaredField(name).getModifiers() & getHiddenApiAccessFlags()) != 0;
+ }
+
+ public static boolean canObserveMethodHiddenAccessFlags(Class<?> klass, String name)
+ throws Exception {
+ return (klass.getDeclaredMethod(name).getModifiers() & getHiddenApiAccessFlags()) != 0;
+ }
+
+ public static boolean canObserveConstructorHiddenAccessFlags(Class<?> klass, Class<?> args[])
+ throws Exception {
+ return (klass.getConstructor(args).getModifiers() & getHiddenApiAccessFlags()) != 0;
+ }
+}
diff --git a/test/674-hiddenapi/src/NullaryConstructorBlacklist.java b/test/674-hiddenapi/src/NullaryConstructorBlacklist.java
new file mode 100644
index 0000000000..5bf6278a77
--- /dev/null
+++ b/test/674-hiddenapi/src/NullaryConstructorBlacklist.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class NullaryConstructorBlacklist {
+ public NullaryConstructorBlacklist() { x = 22; }
+ public NullaryConstructorBlacklist(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java b/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java
new file mode 100644
index 0000000000..c25a767d1d
--- /dev/null
+++ b/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class NullaryConstructorDarkGreylist {
+ public NullaryConstructorDarkGreylist() { x = 22; }
+ public NullaryConstructorDarkGreylist(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java b/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java
new file mode 100644
index 0000000000..d5dac8b7c0
--- /dev/null
+++ b/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class NullaryConstructorLightGreylist {
+ public NullaryConstructorLightGreylist() { x = 22; }
+ public NullaryConstructorLightGreylist(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/674-hiddenapi/src/NullaryConstructorWhitelist.java b/test/674-hiddenapi/src/NullaryConstructorWhitelist.java
new file mode 100644
index 0000000000..d1019077cf
--- /dev/null
+++ b/test/674-hiddenapi/src/NullaryConstructorWhitelist.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class NullaryConstructorWhitelist {
+ public NullaryConstructorWhitelist() { x = 22; }
+ public NullaryConstructorWhitelist(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/674-hiddenapi/src/ParentClass.java b/test/674-hiddenapi/src/ParentClass.java
new file mode 100644
index 0000000000..edad02dc2c
--- /dev/null
+++ b/test/674-hiddenapi/src/ParentClass.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class ParentClass {
+ public ParentClass() {}
+
+ // INSTANCE FIELD
+
+ public int fieldPublicWhitelist = 211;
+ int fieldPackageWhitelist = 212;
+ protected int fieldProtectedWhitelist = 213;
+ private int fieldPrivateWhitelist = 214;
+
+ public int fieldPublicLightGreylist = 221;
+ int fieldPackageLightGreylist = 222;
+ protected int fieldProtectedLightGreylist = 223;
+ private int fieldPrivateLightGreylist = 224;
+
+ public int fieldPublicDarkGreylist = 231;
+ int fieldPackageDarkGreylist = 232;
+ protected int fieldProtectedDarkGreylist = 233;
+ private int fieldPrivateDarkGreylist = 234;
+
+ public int fieldPublicBlacklist = 241;
+ int fieldPackageBlacklist = 242;
+ protected int fieldProtectedBlacklist = 243;
+ private int fieldPrivateBlacklist = 244;
+
+ // STATIC FIELD
+
+ public static int fieldPublicStaticWhitelist = 111;
+ static int fieldPackageStaticWhitelist = 112;
+ protected static int fieldProtectedStaticWhitelist = 113;
+ private static int fieldPrivateStaticWhitelist = 114;
+
+ public static int fieldPublicStaticLightGreylist = 121;
+ static int fieldPackageStaticLightGreylist = 122;
+ protected static int fieldProtectedStaticLightGreylist = 123;
+ private static int fieldPrivateStaticLightGreylist = 124;
+
+ public static int fieldPublicStaticDarkGreylist = 131;
+ static int fieldPackageStaticDarkGreylist = 132;
+ protected static int fieldProtectedStaticDarkGreylist = 133;
+ private static int fieldPrivateStaticDarkGreylist = 134;
+
+ public static int fieldPublicStaticBlacklist = 141;
+ static int fieldPackageStaticBlacklist = 142;
+ protected static int fieldProtectedStaticBlacklist = 143;
+ private static int fieldPrivateStaticBlacklist = 144;
+
+ // INSTANCE METHOD
+
+ public int methodPublicWhitelist() { return 411; }
+ int methodPackageWhitelist() { return 412; }
+ protected int methodProtectedWhitelist() { return 413; }
+ private int methodPrivateWhitelist() { return 414; }
+
+ public int methodPublicLightGreylist() { return 421; }
+ int methodPackageLightGreylist() { return 422; }
+ protected int methodProtectedLightGreylist() { return 423; }
+ private int methodPrivateLightGreylist() { return 424; }
+
+ public int methodPublicDarkGreylist() { return 431; }
+ int methodPackageDarkGreylist() { return 432; }
+ protected int methodProtectedDarkGreylist() { return 433; }
+ private int methodPrivateDarkGreylist() { return 434; }
+
+ public int methodPublicBlacklist() { return 441; }
+ int methodPackageBlacklist() { return 442; }
+ protected int methodProtectedBlacklist() { return 443; }
+ private int methodPrivateBlacklist() { return 444; }
+
+ // STATIC METHOD
+
+ public static int methodPublicStaticWhitelist() { return 311; }
+ static int methodPackageStaticWhitelist() { return 312; }
+ protected static int methodProtectedStaticWhitelist() { return 313; }
+ private static int methodPrivateStaticWhitelist() { return 314; }
+
+ public static int methodPublicStaticLightGreylist() { return 321; }
+ static int methodPackageStaticLightGreylist() { return 322; }
+ protected static int methodProtectedStaticLightGreylist() { return 323; }
+ private static int methodPrivateStaticLightGreylist() { return 324; }
+
+ public static int methodPublicStaticDarkGreylist() { return 331; }
+ static int methodPackageStaticDarkGreylist() { return 332; }
+ protected static int methodProtectedStaticDarkGreylist() { return 333; }
+ private static int methodPrivateStaticDarkGreylist() { return 334; }
+
+ public static int methodPublicStaticBlacklist() { return 341; }
+ static int methodPackageStaticBlacklist() { return 342; }
+ protected static int methodProtectedStaticBlacklist() { return 343; }
+ private static int methodPrivateStaticBlacklist() { return 344; }
+
+ // CONSTRUCTOR
+
+ // Whitelist
+ public ParentClass(int x, short y) {}
+ ParentClass(float x, short y) {}
+ protected ParentClass(long x, short y) {}
+ private ParentClass(double x, short y) {}
+
+ // Light greylist
+ public ParentClass(int x, boolean y) {}
+ ParentClass(float x, boolean y) {}
+ protected ParentClass(long x, boolean y) {}
+ private ParentClass(double x, boolean y) {}
+
+ // Dark greylist
+ public ParentClass(int x, byte y) {}
+ ParentClass(float x, byte y) {}
+ protected ParentClass(long x, byte y) {}
+ private ParentClass(double x, byte y) {}
+
+ // Blacklist
+ public ParentClass(int x, char y) {}
+ ParentClass(float x, char y) {}
+ protected ParentClass(long x, char y) {}
+ private ParentClass(double x, char y) {}
+}
diff --git a/test/674-hiddenapi/src/ParentInterface.java b/test/674-hiddenapi/src/ParentInterface.java
new file mode 100644
index 0000000000..e36fe0e6b2
--- /dev/null
+++ b/test/674-hiddenapi/src/ParentInterface.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public interface ParentInterface {
+ // STATIC FIELD
+ static int fieldPublicStaticWhitelist = 11;
+ static int fieldPublicStaticLightGreylist = 12;
+ static int fieldPublicStaticDarkGreylist = 13;
+ static int fieldPublicStaticBlacklist = 14;
+
+ // INSTANCE METHOD
+ int methodPublicWhitelist();
+ int methodPublicBlacklist();
+ int methodPublicLightGreylist();
+ int methodPublicDarkGreylist();
+
+ // STATIC METHOD
+ static int methodPublicStaticWhitelist() { return 21; }
+ static int methodPublicStaticLightGreylist() { return 22; }
+ static int methodPublicStaticDarkGreylist() { return 23; }
+ static int methodPublicStaticBlacklist() { return 24; }
+
+ // DEFAULT METHOD
+ default int methodPublicDefaultWhitelist() { return 31; }
+ default int methodPublicDefaultLightGreylist() { return 32; }
+ default int methodPublicDefaultDarkGreylist() { return 33; }
+ default int methodPublicDefaultBlacklist() { return 34; }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 49a34a1246..470a68f386 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -389,6 +389,7 @@ cc_defaults {
"661-oat-writer-layout/oat_writer_layout.cc",
"664-aget-verifier/aget-verifier.cc",
"667-jit-jni-stub/jit_jni_stub_test.cc",
+ "674-hiddenapi/hiddenapi.cc",
"708-jit-cache-churn/jit.cc",
],
shared_libs: [
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 2cada76d90..2df0cc6fae 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -20,6 +20,7 @@ include art/build/Android.common_test.mk
# Dependencies for actually running a run-test.
TEST_ART_RUN_TEST_DEPENDENCIES := \
$(HOST_OUT_EXECUTABLES)/dx \
+ $(HOST_OUT_EXECUTABLES)/hiddenapi \
$(HOST_OUT_EXECUTABLES)/jasmin \
$(HOST_OUT_EXECUTABLES)/smali \
$(HOST_OUT_EXECUTABLES)/dexmerger \
diff --git a/test/etc/default-build b/test/etc/default-build
index 5c8257f210..4dc2393c54 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -81,6 +81,17 @@ else
HAS_SRC_DEX2OAT_UNRESOLVED=false
fi
+if [ -f api-light-greylist.txt -o -f api-dark-greylist.txt -o -f api-blacklist.txt ]; then
+ HAS_HIDDENAPI_SPEC=true
+else
+ HAS_HIDDENAPI_SPEC=false
+fi
+
+# USE_HIDDENAPI=false run-test... will disable hiddenapi.
+if [ -z "${USE_HIDDENAPI}" ]; then
+ USE_HIDDENAPI=true
+fi
+
# DESUGAR=false run-test... will disable desugar.
if [[ "$DESUGAR" == false ]]; then
USE_DESUGAR=false
@@ -321,6 +332,24 @@ function make_dexmerge() {
${DXMERGER} "$dst_file" "${dex_files_to_merge[@]}"
}
+function make_hiddenapi() {
+ local args=()
+ while [[ $# -gt 0 ]]; do
+ args+=("--dex=$1")
+ shift
+ done
+ if [ -f api-light-greylist.txt ]; then
+ args+=("--light-greylist=api-light-greylist.txt")
+ fi
+ if [ -f api-dark-greylist.txt ]; then
+ args+=("--dark-greylist=api-dark-greylist.txt")
+ fi
+ if [ -f api-blacklist.txt ]; then
+ args+=("--blacklist=api-blacklist.txt")
+ fi
+ ${HIDDENAPI} "${args[@]}"
+}
+
# Print the directory name only if it exists.
function maybe_dir() {
local dirname="$1"
@@ -334,6 +363,13 @@ if [ -e classes.dex ]; then
exit 0
fi
+# Helper function for a common test. Evaluate with $(has_mutlidex).
+function has_multidex() {
+ echo [ ${HAS_SRC_MULTIDEX} = "true" \
+ -o ${HAS_JASMIN_MULTIDEX} = "true" \
+ -o ${HAS_SMALI_MULTIDEX} = "true" ]
+}
+
if [ ${HAS_SRC_DEX2OAT_UNRESOLVED} = "true" ]; then
mkdir classes
mkdir classes-ex
@@ -501,9 +537,18 @@ if [ ${HAS_SRC_EX} = "true" ]; then
fi
fi
+# Apply hiddenapi on the dex files if the test has API list file(s).
+if [ ${NEED_DEX} = "true" -a ${USE_HIDDENAPI} = "true" -a ${HAS_HIDDENAPI_SPEC} = "true" ]; then
+ if $(has_multidex); then
+ make_hiddenapi classes.dex classes2.dex
+ else
+ make_hiddenapi classes.dex
+ fi
+fi
+
# Create a single dex jar with two dex files for multidex.
if [ ${NEED_DEX} = "true" ]; then
- if [ ${HAS_SRC_MULTIDEX} = "true" ] || [ ${HAS_JASMIN_MULTIDEX} = "true" ] || [ ${HAS_SMALI_MULTIDEX} = "true" ]; then
+ if $(has_multidex); then
zip $TEST_NAME.jar classes.dex classes2.dex
else
zip $TEST_NAME.jar classes.dex
diff --git a/test/run-test b/test/run-test
index 75fe15c919..bde3aaceb9 100755
--- a/test/run-test
+++ b/test/run-test
@@ -113,6 +113,11 @@ if [ -z "$ZIPALIGN" ]; then
fi
export ZIPALIGN
+# If hiddenapi was not set by the environment variable, assume it is in the path.
+if [ -z "$HIDDENAPI" ]; then
+ export HIDDENAPI="hiddenapi"
+fi
+
info="info.txt"
build="build"
run="run"