diff options
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" |