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;
+    }
+  }
+}