ART: Walk past j.l.i in stackwalk for Hidden API
Bug: 77631986
Test: art/test.py --host 674-hidden-api
(cherry picked from change 198a27e92a529115a2db2a17ec11d8b52bd6a315)
Change-Id: I2364036a6b7bd8bb06d2e456fb22f4db9c6cce21
Merged-In: I2e3e0399765da7f554259fe14247b45e42110ef4
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 82300fe..510c5de 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -33,6 +33,7 @@
#include "mirror/class_loader.h"
#include "mirror/field-inl.h"
#include "mirror/method.h"
+#include "mirror/method_handles_lookup.h"
#include "mirror/object-inl.h"
#include "mirror/object_array-inl.h"
#include "mirror/string-inl.h"
@@ -49,12 +50,13 @@
namespace art {
-// Returns true if the first non-ClassClass caller up the stack is in a platform dex file.
+// Returns true if the first caller outside of the Class class or java.lang.invoke package
+// is in a platform DEX file.
static bool IsCallerInPlatformDex(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
- // Walk the stack and find the first frame not from java.lang.Class.
+ // Walk the stack and find the first frame not from java.lang.Class and not from java.lang.invoke.
// This is very expensive. Save this till the last.
- struct FirstNonClassClassCallerVisitor : public StackVisitor {
- explicit FirstNonClassClassCallerVisitor(Thread* thread)
+ struct FirstExternalCallerVisitor : public StackVisitor {
+ explicit FirstExternalCallerVisitor(Thread* thread)
: StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
caller(nullptr) {
}
@@ -68,18 +70,30 @@
} else if (m->IsRuntimeMethod()) {
// Internal runtime method, continue walking the stack.
return true;
- } else if (m->GetDeclaringClass()->IsClassClass()) {
- return true;
- } else {
- caller = m;
- return false;
}
+
+ ObjPtr<mirror::Class> declaring_class = m->GetDeclaringClass();
+ if (declaring_class->IsBootStrapClassLoaded()) {
+ if (declaring_class->IsClassClass()) {
+ return true;
+ }
+ ObjPtr<mirror::Class> lookup_class = mirror::MethodHandlesLookup::StaticClass();
+ if (declaring_class == lookup_class || declaring_class->IsInSamePackage(lookup_class)) {
+ // Check classes in the java.lang.invoke package. At the time of writing, the
+ // classes of interest are MethodHandles and MethodHandles.Lookup, but this
+ // is subject to change so conservatively cover the entire package.
+ return true;
+ }
+ }
+
+ caller = m;
+ return false;
}
ArtMethod* caller;
};
- FirstNonClassClassCallerVisitor visitor(self);
+ FirstExternalCallerVisitor visitor(self);
visitor.WalkStack();
return visitor.caller != nullptr &&
hiddenapi::IsCallerInPlatformDex(visitor.caller->GetDeclaringClass());
diff --git a/test/674-hiddenapi/info.txt b/test/674-hiddenapi/info.txt
index 25ac6ae..94aac1e 100644
--- a/test/674-hiddenapi/info.txt
+++ b/test/674-hiddenapi/info.txt
@@ -1,7 +1,8 @@
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.
+tries to access them with reflection/JNI/MethodHandles 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
diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java
index 822224c..ea66f16 100644
--- a/test/674-hiddenapi/src-ex/ChildClass.java
+++ b/test/674-hiddenapi/src-ex/ChildClass.java
@@ -15,13 +15,8 @@
*/
import dalvik.system.VMRuntime;
-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 java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
import java.util.function.Consumer;
public class ChildClass {
@@ -210,6 +205,37 @@
throwDiscoveryException(klass, name, true, "JNI", canDiscover);
}
+ // Test discovery with MethodHandles.lookup() which is caller
+ // context sensitive.
+
+ final MethodHandles.Lookup lookup = MethodHandles.lookup();
+ if (JLI.canDiscoverWithLookupFindGetter(lookup, klass, name, int.class)
+ != canDiscover) {
+ throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findGetter()",
+ canDiscover);
+ }
+ if (JLI.canDiscoverWithLookupFindStaticGetter(lookup, klass, name, int.class)
+ != canDiscover) {
+ throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findStaticGetter()",
+ canDiscover);
+ }
+
+ // Test discovery with MethodHandles.publicLookup() which can only
+ // see public fields. Looking up setters here and fields in
+ // interfaces are implicitly final.
+
+ final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
+ if (JLI.canDiscoverWithLookupFindSetter(publicLookup, klass, name, int.class)
+ != canDiscover) {
+ throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findSetter()",
+ canDiscover);
+ }
+ if (JLI.canDiscoverWithLookupFindStaticSetter(publicLookup, klass, name, int.class)
+ != canDiscover) {
+ throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findStaticSetter()",
+ canDiscover);
+ }
+
// Finish here if we could not discover the field.
if (canDiscover) {
@@ -297,7 +323,21 @@
throwDiscoveryException(klass, name, false, "JNI", canDiscover);
}
- // Finish here if we could not discover the field.
+ // Test discovery with MethodHandles.lookup().
+
+ final MethodHandles.Lookup lookup = MethodHandles.lookup();
+ final MethodType methodType = MethodType.methodType(int.class);
+ if (JLI.canDiscoverWithLookupFindVirtual(lookup, klass, name, methodType) != canDiscover) {
+ throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findVirtual()",
+ canDiscover);
+ }
+
+ if (JLI.canDiscoverWithLookupFindStatic(lookup, klass, name, methodType) != canDiscover) {
+ throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findStatic()",
+ canDiscover);
+ }
+
+ // Finish here if we could not discover the method.
if (canDiscover) {
// Test that modifiers are unaffected.
@@ -353,6 +393,7 @@
hiddenness.mAssociatedType.mClass };
Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue,
hiddenness.mAssociatedType.mDefaultValue };
+ MethodType methodType = MethodType.methodType(void.class, args);
boolean canDiscover = (behaviour != Behaviour.Denied);
boolean setsWarning = (behaviour == Behaviour.Warning);
@@ -383,7 +424,22 @@
throwDiscoveryException(klass, fullName, false, "JNI", canDiscover);
}
- // Finish here if we could not discover the field.
+ // Test discovery with MethodHandles.lookup()
+
+ final MethodHandles.Lookup lookup = MethodHandles.lookup();
+ if (JLI.canDiscoverWithLookupFindConstructor(lookup, klass, methodType) != canDiscover) {
+ throwDiscoveryException(klass, fullName, false, "MethodHandles.lookup().findConstructor",
+ canDiscover);
+ }
+
+ final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
+ if (JLI.canDiscoverWithLookupFindConstructor(publicLookup, klass, methodType) != canDiscover) {
+ throwDiscoveryException(klass, fullName, false,
+ "MethodHandles.publicLookup().findConstructor",
+ canDiscover);
+ }
+
+ // Finish here if we could not discover the constructor.
if (!canDiscover) {
return;
diff --git a/test/674-hiddenapi/src-ex/JLI.java b/test/674-hiddenapi/src-ex/JLI.java
new file mode 100644
index 0000000..e4ca3bc
--- /dev/null
+++ b/test/674-hiddenapi/src-ex/JLI.java
@@ -0,0 +1,98 @@
+/*
+ * 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.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+/** Class with helper methods for field and method lookups using java.lang.invoke. */
+public class JLI {
+ public static boolean canDiscoverWithLookupFindGetter(
+ MethodHandles.Lookup lookup, Class<?> klass, String fieldName, Class<?> fieldType) {
+ try {
+ return lookup.findGetter(klass, fieldName, fieldType) != null;
+ } catch (NoSuchFieldException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ return true;
+ }
+ }
+
+ public static boolean canDiscoverWithLookupFindSetter(
+ MethodHandles.Lookup lookup, Class<?> klass, String fieldName, Class<?> fieldType) {
+ try {
+ return lookup.findSetter(klass, fieldName, fieldType) != null;
+ } catch (NoSuchFieldException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ return true;
+ }
+ }
+
+ public static boolean canDiscoverWithLookupFindStaticGetter(
+ MethodHandles.Lookup lookup, Class<?> klass, String fieldName, Class<?> fieldType) {
+ try {
+ return lookup.findStaticGetter(klass, fieldName, fieldType) != null;
+ } catch (NoSuchFieldException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ return true;
+ }
+ }
+
+ public static boolean canDiscoverWithLookupFindStaticSetter(
+ MethodHandles.Lookup lookup, Class<?> klass, String fieldName, Class<?> fieldType) {
+ try {
+ return lookup.findStaticSetter(klass, fieldName, fieldType) != null;
+ } catch (NoSuchFieldException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ return true;
+ }
+ }
+
+ public static boolean canDiscoverWithLookupFindConstructor(
+ MethodHandles.Lookup lookup, Class<?> klass, MethodType methodType) {
+ try {
+ return lookup.findConstructor(klass, methodType) != null;
+ } catch (NoSuchMethodException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ return true;
+ }
+ }
+
+ public static boolean canDiscoverWithLookupFindVirtual(
+ MethodHandles.Lookup lookup, Class<?> klass, String methodName, MethodType methodType) {
+ try {
+ return lookup.findVirtual(klass, methodName, methodType) != null;
+ } catch (NoSuchMethodException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ return true;
+ }
+ }
+
+ public static boolean canDiscoverWithLookupFindStatic(
+ MethodHandles.Lookup lookup, Class<?> klass, String methodName, MethodType methodType) {
+ try {
+ return lookup.findStatic(klass, methodName, methodType) != null;
+ } catch (NoSuchMethodException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ return true;
+ }
+ }
+}