Take hidden API into account during getDeclaredMethod()
Generics make it possible for two methods to have the same name and list
of parameters but differ in their return type. Class.getDeclaredMethod()
does not allow callers to specify the type, so either of the matching
methods can be returned (ART will prefer the non-synthetic one).
However, Class::GetDeclaredMethodInternal() did not use to take hidden API
into account and could return a hidden method, despite a non-hidden one
being available. The reflective call would then reject the method and
throw NoSuchMethodException.
This patch modifies Class:GetDeclaredMethodInternal() to consider:
(a) hidden/non-hidden
(b) virtual/direct
(c) synthetic/non-synthetic
in that decreasing order of importance and pick the best matching
method. The hiddenness checks are performed with AccessMethod::kNone
so as to not trigger warnings. A hidden method may still be returned and
the caller should do the access check again with the appropriate
AccessMethod.
Bug: 122291025
Test: art/test.py -r -t 690-hiddenapi-same-name-methods
Change-Id: Iaee780c1e87f5587f51e24b517b2b37101c729e3
diff --git a/test/690-hiddenapi-same-name-methods/build b/test/690-hiddenapi-same-name-methods/build
new file mode 100644
index 0000000..c364b3b
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+USE_HIDDENAPI=true ./default-build "$@"
diff --git a/test/690-hiddenapi-same-name-methods/expected.txt b/test/690-hiddenapi-same-name-methods/expected.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/expected.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/690-hiddenapi-same-name-methods/hiddenapi-flags.csv b/test/690-hiddenapi-same-name-methods/hiddenapi-flags.csv
new file mode 100644
index 0000000..001ab80
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/hiddenapi-flags.csv
@@ -0,0 +1,9 @@
+LSpecificClass;->foo()Ljava/lang/Double;,blacklist
+LDirectMethods;->foo()Ljava/lang/Integer;,blacklist
+LDirectMethods;->foo()Ljava/lang/Boolean;,blacklist
+LVirtualMethods;->foo()Ljava/lang/Integer;,blacklist
+LVirtualMethods;->foo()Ljava/lang/Boolean;,blacklist
+LSyntheticMethods;->foo()Ljava/lang/Integer;,blacklist
+LSyntheticMethods;->foo()Ljava/lang/Boolean;,blacklist
+LNonSyntheticMethods;->foo()Ljava/lang/Integer;,blacklist
+LNonSyntheticMethods;->foo()Ljava/lang/Boolean;,blacklist
\ No newline at end of file
diff --git a/test/690-hiddenapi-same-name-methods/info.txt b/test/690-hiddenapi-same-name-methods/info.txt
new file mode 100644
index 0000000..be5b195
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/info.txt
@@ -0,0 +1 @@
+Test that Class::GetDeclaredMethodInternal() takes hidden API into account.
\ No newline at end of file
diff --git a/test/690-hiddenapi-same-name-methods/smali-ex/DirectMethods.smali b/test/690-hiddenapi-same-name-methods/smali-ex/DirectMethods.smali
new file mode 100644
index 0000000..8564976
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/smali-ex/DirectMethods.smali
@@ -0,0 +1,46 @@
+#
+# Copyright (C) 2019 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.
+#
+
+.class LDirectMethods;
+.super Ljava/lang/Object;
+
+# Expect to choose the non-hidden, non-synthetic method.
+
+# Non-hidden methods
+.method private foo()Ljava/lang/Number;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+.method private synthetic foo()Ljava/lang/Double;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+# Hidden methods
+.method private foo()Ljava/lang/Integer;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+.method private synthetic foo()Ljava/lang/Boolean;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
diff --git a/test/690-hiddenapi-same-name-methods/smali-ex/NonSyntheticMethods.smali b/test/690-hiddenapi-same-name-methods/smali-ex/NonSyntheticMethods.smali
new file mode 100644
index 0000000..f47219f
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/smali-ex/NonSyntheticMethods.smali
@@ -0,0 +1,46 @@
+#
+# Copyright (C) 2019 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.
+#
+
+.class LNonSyntheticMethods;
+.super Ljava/lang/Object;
+
+# Expect to choose the non-hidden, virtual method.
+
+# Non-hidden methods
+.method public foo()Ljava/lang/Number;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+.method private foo()Ljava/lang/Double;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+# Hidden methods
+.method public foo()Ljava/lang/Integer;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+.method private foo()Ljava/lang/Boolean;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
diff --git a/test/690-hiddenapi-same-name-methods/smali-ex/SyntheticMethods.smali b/test/690-hiddenapi-same-name-methods/smali-ex/SyntheticMethods.smali
new file mode 100644
index 0000000..afb4d33
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/smali-ex/SyntheticMethods.smali
@@ -0,0 +1,46 @@
+#
+# Copyright (C) 2019 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.
+#
+
+.class LSyntheticMethods;
+.super Ljava/lang/Object;
+
+# Expect to choose the non-hidden, virtual method.
+
+# Non-hidden methods
+.method public synthetic foo()Ljava/lang/Number;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+.method private synthetic foo()Ljava/lang/Double;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+# Hidden methods
+.method public synthetic foo()Ljava/lang/Integer;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+.method private synthetic foo()Ljava/lang/Boolean;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
diff --git a/test/690-hiddenapi-same-name-methods/smali-ex/VirtualMethods.smali b/test/690-hiddenapi-same-name-methods/smali-ex/VirtualMethods.smali
new file mode 100644
index 0000000..fb26c74
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/smali-ex/VirtualMethods.smali
@@ -0,0 +1,46 @@
+#
+# Copyright (C) 2019 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.
+#
+
+.class LVirtualMethods;
+.super Ljava/lang/Object;
+
+# Expect to choose the non-hidden, non-synthetic method.
+
+# Non-hidden methods
+.method public foo()Ljava/lang/Number;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+.method public synthetic foo()Ljava/lang/Double;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+# Hidden methods
+.method public foo()Ljava/lang/Integer;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
+
+.method public synthetic foo()Ljava/lang/Boolean;
+ .registers 1
+ const/4 v0, 0x0
+ return-object v0
+.end method
diff --git a/test/690-hiddenapi-same-name-methods/src-ex/GenericInterface.java b/test/690-hiddenapi-same-name-methods/src-ex/GenericInterface.java
new file mode 100644
index 0000000..c404402
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/src-ex/GenericInterface.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 GenericInterface<T extends Number> {
+ public T foo();
+}
diff --git a/test/690-hiddenapi-same-name-methods/src-ex/SpecificClass.java b/test/690-hiddenapi-same-name-methods/src-ex/SpecificClass.java
new file mode 100644
index 0000000..dd3a835
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/src-ex/SpecificClass.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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 SpecificClass implements GenericInterface<Double> {
+ public Double foo() {
+ return 42.0;
+ }
+}
diff --git a/test/690-hiddenapi-same-name-methods/src/Main.java b/test/690-hiddenapi-same-name-methods/src/Main.java
new file mode 100644
index 0000000..12cfdd7
--- /dev/null
+++ b/test/690-hiddenapi-same-name-methods/src/Main.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 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.io.File;
+import java.lang.reflect.Method;
+import java.util.Base64;
+
+public class Main {
+ public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
+ System.loadLibrary(args[0]);
+
+ // Run the initialization routine. This will enable hidden API checks in
+ // the runtime, in case they are not enabled by default.
+ init();
+
+ // Load the '-ex' APK and attach it to the boot class path.
+ appendToBootClassLoader(DEX_EXTRA, /* isCorePlatform */ false);
+
+ // All test classes contain just methods named "foo" with different return types
+ // and access flags. Check that:
+ // (a) only the non-hidden ones are returned from getDeclaredMethods
+ // (they have return types Number and Double), and
+ // (b) getDeclaredMethod picks virtual/non-synthetic methods over direct/synthetic
+ // (the right one always has return type Number).
+ Class<?> covariantClass = Class.forName(JAVA_CLASS_NAME, true, BOOT_CLASS_LOADER);
+ checkMethodList(covariantClass, /* expectedLength= */ 1);
+ checkMethod(covariantClass);
+
+ String[] classes = new String[] {
+ "VirtualMethods",
+ "DirectMethods",
+ "SyntheticMethods",
+ "NonSyntheticMethods"
+ };
+ for (String className : classes) {
+ Class<?> klass = Class.forName(className, true, BOOT_CLASS_LOADER);
+ checkMethodList(klass, /* expectedLength= */ 2);
+ checkMethod(klass);
+ }
+ }
+
+ private static void checkMethodList(Class<?> klass, int expectedLength) {
+ String className = klass.getName();
+ Method[] methods = klass.getDeclaredMethods();
+ if (methods.length != expectedLength) {
+ throw new RuntimeException(className + ": expected " + expectedLength +
+ " declared method(s), got " + methods.length);
+ }
+ boolean hasNumberReturnType = false;
+ boolean hasDoubleReturnType = false;
+ for (Method method : methods) {
+ if (!METHOD_NAME.equals(method.getName())) {
+ throw new RuntimeException(className + ": expected declared method name: \"" + METHOD_NAME +
+ "\", got: \"" + method.getName() + "\"");
+ }
+ if (Number.class == method.getReturnType()) {
+ hasNumberReturnType = true;
+ } else if (Double.class == method.getReturnType()) {
+ hasDoubleReturnType = true;
+ }
+ }
+ if (methods.length >= 1 && !hasNumberReturnType) {
+ throw new RuntimeException(className + ": expected a method with return type \"Number\"");
+ }
+ if (methods.length >= 2 && !hasDoubleReturnType) {
+ throw new RuntimeException(className + ": expected a method with return type \"Double\"");
+ }
+ }
+
+ private static void checkMethod(Class<?> klass) throws NoSuchMethodException {
+ String className = klass.getName();
+ Method method = klass.getDeclaredMethod(METHOD_NAME);
+ if (!METHOD_NAME.equals(method.getName())) {
+ throw new RuntimeException(className + ": expected declared method name: \"" + METHOD_NAME +
+ "\", got: \"" + method.getName() + "\"");
+ } else if (Number.class != method.getReturnType()) {
+ throw new RuntimeException(className + ": expected method return type: \"Number\", got \"" +
+ method.getReturnType().toString() + "\"");
+ }
+ }
+
+ private static final String DEX_EXTRA = new File(System.getenv("DEX_LOCATION"),
+ "690-hiddenapi-same-name-methods-ex.jar").getAbsolutePath();
+
+ private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
+
+ private static final String JAVA_CLASS_NAME = "SpecificClass";
+ private static final String METHOD_NAME = "foo";
+
+ // Native functions. Note that these are implemented in 674-hiddenapi/hiddenapi.cc.
+ private static native void appendToBootClassLoader(String dexPath, boolean isCorePlatform);
+ private static native void init();
+}