summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author David Brazdil <dbrazdil@google.com> 2019-02-02 20:08:44 +0000
committer David Brazdil <dbrazdil@google.com> 2019-02-04 15:00:20 +0000
commit4bcd65753c1e49ceba2c41983f26af81ae319670 (patch)
tree3fc7bd53d37448621726dc508f173490807af9c7
parent2da3cbb4af20a64108e474c0bbbe0cc5d3af2aa2 (diff)
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
-rw-r--r--runtime/hidden_api.h2
-rw-r--r--runtime/interpreter/unstarted_runtime.cc25
-rw-r--r--runtime/mirror/class.cc98
-rw-r--r--runtime/mirror/class.h14
-rw-r--r--runtime/native/java_lang_Class.cc14
-rw-r--r--test/690-hiddenapi-same-name-methods/build17
-rw-r--r--test/690-hiddenapi-same-name-methods/expected.txt1
-rw-r--r--test/690-hiddenapi-same-name-methods/hiddenapi-flags.csv9
-rw-r--r--test/690-hiddenapi-same-name-methods/info.txt1
-rw-r--r--test/690-hiddenapi-same-name-methods/smali-ex/DirectMethods.smali46
-rw-r--r--test/690-hiddenapi-same-name-methods/smali-ex/NonSyntheticMethods.smali46
-rw-r--r--test/690-hiddenapi-same-name-methods/smali-ex/SyntheticMethods.smali46
-rw-r--r--test/690-hiddenapi-same-name-methods/smali-ex/VirtualMethods.smali46
-rw-r--r--test/690-hiddenapi-same-name-methods/src-ex/GenericInterface.java19
-rw-r--r--test/690-hiddenapi-same-name-methods/src-ex/SpecificClass.java21
-rw-r--r--test/690-hiddenapi-same-name-methods/src/Main.java106
-rwxr-xr-xtest/etc/default-build20
-rw-r--r--test/knownfailures.json5
18 files changed, 495 insertions, 41 deletions
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
index 8bd59ea90a..fcab4c4405 100644
--- a/runtime/hidden_api.h
+++ b/runtime/hidden_api.h
@@ -352,7 +352,7 @@ ALWAYS_INLINE inline uint32_t GetRuntimeFlags(ArtMethod* method)
// This function might print warnings into the log if the member is hidden.
template<typename T>
inline bool ShouldDenyAccessToMember(T* member,
- std::function<AccessContext()> fn_get_access_context,
+ const std::function<AccessContext()>& fn_get_access_context,
AccessMethod access_method)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(member != nullptr);
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 72a87276ac..7a1b7eba22 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -181,17 +181,21 @@ static mirror::String* GetClassName(Thread* self, ShadowFrame* shadow_frame, siz
return param->AsString();
}
+static std::function<hiddenapi::AccessContext()> GetHiddenapiAccessContextFunction(
+ ShadowFrame* frame) {
+ return [=]() REQUIRES_SHARED(Locks::mutator_lock_) {
+ return hiddenapi::AccessContext(frame->GetMethod()->GetDeclaringClass());
+ };
+}
+
template<typename T>
static ALWAYS_INLINE bool ShouldDenyAccessToMember(T* member, ShadowFrame* frame)
REQUIRES_SHARED(Locks::mutator_lock_) {
// All uses in this file are from reflection
constexpr hiddenapi::AccessMethod kAccessMethod = hiddenapi::AccessMethod::kReflection;
- return hiddenapi::ShouldDenyAccessToMember(
- member,
- [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
- return hiddenapi::AccessContext(frame->GetMethod()->GetDeclaringClass());
- },
- kAccessMethod);
+ return hiddenapi::ShouldDenyAccessToMember(member,
+ GetHiddenapiAccessContextFunction(frame),
+ kAccessMethod);
}
void UnstartedRuntime::UnstartedClassForNameCommon(Thread* self,
@@ -390,22 +394,23 @@ void UnstartedRuntime::UnstartedClassGetDeclaredMethod(
Runtime* runtime = Runtime::Current();
bool transaction = runtime->IsActiveTransaction();
PointerSize pointer_size = runtime->GetClassLinker()->GetImagePointerSize();
+ auto fn_hiddenapi_access_context = GetHiddenapiAccessContextFunction(shadow_frame);
ObjPtr<mirror::Method> method;
if (transaction) {
if (pointer_size == PointerSize::k64) {
method = mirror::Class::GetDeclaredMethodInternal<PointerSize::k64, true>(
- self, klass, name, args);
+ self, klass, name, args, fn_hiddenapi_access_context);
} else {
method = mirror::Class::GetDeclaredMethodInternal<PointerSize::k32, true>(
- self, klass, name, args);
+ self, klass, name, args, fn_hiddenapi_access_context);
}
} else {
if (pointer_size == PointerSize::k64) {
method = mirror::Class::GetDeclaredMethodInternal<PointerSize::k64, false>(
- self, klass, name, args);
+ self, klass, name, args, fn_hiddenapi_access_context);
} else {
method = mirror::Class::GetDeclaredMethodInternal<PointerSize::k32, false>(
- self, klass, name, args);
+ self, klass, name, args, fn_hiddenapi_access_context);
}
}
if (method != nullptr && ShouldDenyAccessToMember(method->GetArtMethod(), shadow_frame)) {
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 53c9cc72e5..fcd3714b99 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -35,6 +35,7 @@
#include "gc/accounting/card_table-inl.h"
#include "gc/heap-inl.h"
#include "handle_scope-inl.h"
+#include "hidden_api.h"
#include "subtype_check.h"
#include "method.h"
#include "object-inl.h"
@@ -1252,18 +1253,50 @@ dex::TypeIndex Class::FindTypeIndexInOtherDexFile(const DexFile& dex_file) {
return (type_id == nullptr) ? dex::TypeIndex() : dex_file.GetIndexForTypeId(*type_id);
}
+ALWAYS_INLINE
+static bool IsMethodPreferredOver(ArtMethod* orig_method,
+ bool orig_method_hidden,
+ ArtMethod* new_method,
+ bool new_method_hidden) {
+ DCHECK(new_method != nullptr);
+
+ // Is this the first result?
+ if (orig_method == nullptr) {
+ return true;
+ }
+
+ // Original method is hidden, the new one is not?
+ if (orig_method_hidden && !new_method_hidden) {
+ return true;
+ }
+
+ // We iterate over virtual methods first and then over direct ones,
+ // so we can never be in situation where `orig_method` is direct and
+ // `new_method` is virtual.
+ DCHECK(!orig_method->IsDirect() || new_method->IsDirect());
+
+ // Original method is synthetic, the new one is not?
+ if (orig_method->IsSynthetic() && !new_method->IsSynthetic()) {
+ return true;
+ }
+
+ return false;
+}
+
template <PointerSize kPointerSize, bool kTransactionActive>
ObjPtr<Method> Class::GetDeclaredMethodInternal(
Thread* self,
ObjPtr<Class> klass,
ObjPtr<String> name,
- ObjPtr<ObjectArray<Class>> args) {
- // Covariant return types permit the class to define multiple
- // methods with the same name and parameter types. Prefer to
- // return a non-synthetic method in such situations. We may
- // still return a synthetic method to handle situations like
- // escalated visibility. We never return miranda methods that
- // were synthesized by the runtime.
+ ObjPtr<ObjectArray<Class>> args,
+ const std::function<hiddenapi::AccessContext()>& fn_get_access_context) {
+ // Covariant return types (or smali) permit the class to define
+ // multiple methods with the same name and parameter types.
+ // Prefer (in decreasing order of importance):
+ // 1) non-hidden method over hidden
+ // 2) virtual methods over direct
+ // 3) non-synthetic methods over synthetic
+ // We never return miranda methods that were synthesized by the runtime.
StackHandleScope<3> hs(self);
auto h_method_name = hs.NewHandle(name);
if (UNLIKELY(h_method_name == nullptr)) {
@@ -1272,8 +1305,13 @@ ObjPtr<Method> Class::GetDeclaredMethodInternal(
}
auto h_args = hs.NewHandle(args);
Handle<Class> h_klass = hs.NewHandle(klass);
+ constexpr hiddenapi::AccessMethod access_method = hiddenapi::AccessMethod::kNone;
ArtMethod* result = nullptr;
+ bool result_hidden = false;
for (auto& m : h_klass->GetDeclaredVirtualMethods(kPointerSize)) {
+ if (m.IsMiranda()) {
+ continue;
+ }
auto* np_method = m.GetInterfaceMethodIfProxy(kPointerSize);
// May cause thread suspension.
ObjPtr<String> np_name = np_method->ResolveNameString();
@@ -1283,14 +1321,24 @@ ObjPtr<Method> Class::GetDeclaredMethodInternal(
}
continue;
}
- if (!m.IsMiranda()) {
- if (!m.IsSynthetic()) {
- return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
- }
- result = &m; // Remember as potential result if it's not a miranda method.
+ bool m_hidden = hiddenapi::ShouldDenyAccessToMember(&m, fn_get_access_context, access_method);
+ if (!m_hidden && !m.IsSynthetic()) {
+ // Non-hidden, virtual, non-synthetic. Best possible result, exit early.
+ return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
+ } else if (IsMethodPreferredOver(result, result_hidden, &m, m_hidden)) {
+ // Remember as potential result.
+ result = &m;
+ result_hidden = m_hidden;
}
}
- if (result == nullptr) {
+
+ if ((result != nullptr) && !result_hidden) {
+ // We have not found a non-hidden, virtual, non-synthetic method, but
+ // if we have found a non-hidden, virtual, synthetic method, we cannot
+ // do better than that later.
+ DCHECK(!result->IsDirect());
+ DCHECK(result->IsSynthetic());
+ } else {
for (auto& m : h_klass->GetDirectMethods(kPointerSize)) {
auto modifiers = m.GetAccessFlags();
if ((modifiers & kAccConstructor) != 0) {
@@ -1310,12 +1358,20 @@ ObjPtr<Method> Class::GetDeclaredMethodInternal(
continue;
}
DCHECK(!m.IsMiranda()); // Direct methods cannot be miranda methods.
- if ((modifiers & kAccSynthetic) == 0) {
+ bool m_hidden = hiddenapi::ShouldDenyAccessToMember(&m, fn_get_access_context, access_method);
+ if (!m_hidden && !m.IsSynthetic()) {
+ // Non-hidden, direct, non-synthetic. Any virtual result could only have been
+ // hidden, therefore this is the best possible match. Exit now.
+ DCHECK((result == nullptr) || result_hidden);
return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m);
+ } else if (IsMethodPreferredOver(result, result_hidden, &m, m_hidden)) {
+ // Remember as potential result.
+ result = &m;
+ result_hidden = m_hidden;
}
- result = &m; // Remember as potential result.
}
}
+
return result != nullptr
? Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, result)
: nullptr;
@@ -1326,25 +1382,29 @@ ObjPtr<Method> Class::GetDeclaredMethodInternal<PointerSize::k32, false>(
Thread* self,
ObjPtr<Class> klass,
ObjPtr<String> name,
- ObjPtr<ObjectArray<Class>> args);
+ ObjPtr<ObjectArray<Class>> args,
+ const std::function<hiddenapi::AccessContext()>& fn_get_access_context);
template
ObjPtr<Method> Class::GetDeclaredMethodInternal<PointerSize::k32, true>(
Thread* self,
ObjPtr<Class> klass,
ObjPtr<String> name,
- ObjPtr<ObjectArray<Class>> args);
+ ObjPtr<ObjectArray<Class>> args,
+ const std::function<hiddenapi::AccessContext()>& fn_get_access_context);
template
ObjPtr<Method> Class::GetDeclaredMethodInternal<PointerSize::k64, false>(
Thread* self,
ObjPtr<Class> klass,
ObjPtr<String> name,
- ObjPtr<ObjectArray<Class>> args);
+ ObjPtr<ObjectArray<Class>> args,
+ const std::function<hiddenapi::AccessContext()>& fn_get_access_context);
template
ObjPtr<Method> Class::GetDeclaredMethodInternal<PointerSize::k64, true>(
Thread* self,
ObjPtr<Class> klass,
ObjPtr<String> name,
- ObjPtr<ObjectArray<Class>> args);
+ ObjPtr<ObjectArray<Class>> args,
+ const std::function<hiddenapi::AccessContext()>& fn_get_access_context);
template <PointerSize kPointerSize, bool kTransactionActive>
ObjPtr<Constructor> Class::GetDeclaredConstructorInternal(
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 981ecf1ace..c9c542d175 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -37,6 +37,10 @@ struct ClassDef;
class TypeList;
} // namespace dex
+namespace hiddenapi {
+class AccessContext;
+} // namespace hiddenapi
+
template<typename T> class ArraySlice;
class ArtField;
class ArtMethod;
@@ -702,10 +706,12 @@ class MANAGED Class final : public Object {
REQUIRES_SHARED(Locks::mutator_lock_);
template <PointerSize kPointerSize, bool kTransactionActive>
- static ObjPtr<Method> GetDeclaredMethodInternal(Thread* self,
- ObjPtr<Class> klass,
- ObjPtr<String> name,
- ObjPtr<ObjectArray<Class>> args)
+ static ObjPtr<Method> GetDeclaredMethodInternal(
+ Thread* self,
+ ObjPtr<Class> klass,
+ ObjPtr<String> name,
+ ObjPtr<ObjectArray<Class>> args,
+ const std::function<hiddenapi::AccessContext()>& fn_get_access_context)
REQUIRES_SHARED(Locks::mutator_lock_);
template <PointerSize kPointerSize, bool kTransactionActive>
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 411579117c..db62475608 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -113,15 +113,18 @@ static hiddenapi::AccessContext GetReflectionCaller(Thread* self)
: hiddenapi::AccessContext(caller);
}
+static std::function<hiddenapi::AccessContext()> GetHiddenapiAccessContextFunction(Thread* self) {
+ return [=]() REQUIRES_SHARED(Locks::mutator_lock_) { return GetReflectionCaller(self); };
+}
+
// Returns true if the first non-ClassClass caller up the stack should not be
// allowed access to `member`.
template<typename T>
ALWAYS_INLINE static bool ShouldDenyAccessToMember(T* member, Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
- return hiddenapi::ShouldDenyAccessToMember(
- member,
- [&]() REQUIRES_SHARED(Locks::mutator_lock_) { return GetReflectionCaller(self); },
- hiddenapi::AccessMethod::kReflection);
+ return hiddenapi::ShouldDenyAccessToMember(member,
+ GetHiddenapiAccessContextFunction(self),
+ hiddenapi::AccessMethod::kReflection);
}
// Returns true if a class member should be discoverable with reflection given
@@ -563,7 +566,8 @@ static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
soa.Self(),
DecodeClass(soa, javaThis),
soa.Decode<mirror::String>(name),
- soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
+ soa.Decode<mirror::ObjectArray<mirror::Class>>(args),
+ GetHiddenapiAccessContextFunction(soa.Self())));
if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) {
return nullptr;
}
diff --git a/test/690-hiddenapi-same-name-methods/build b/test/690-hiddenapi-same-name-methods/build
new file mode 100644
index 0000000000..c364b3b057
--- /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 0000000000..6a5618ebc6
--- /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 0000000000..001ab80db9
--- /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 0000000000..be5b195ea2
--- /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 0000000000..856497600c
--- /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 0000000000..f47219ff6a
--- /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 0000000000..afb4d331e8
--- /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 0000000000..fb26c749c3
--- /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 0000000000..c404402288
--- /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 0000000000..dd3a8357b1
--- /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 0000000000..12cfdd702b
--- /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();
+}
diff --git a/test/etc/default-build b/test/etc/default-build
index 5eba804507..d20369846c 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -80,6 +80,12 @@ else
HAS_JASMIN_MULTIDEX=false
fi
+if [ -d smali-ex ]; then
+ HAS_SMALI_EX=true
+else
+ HAS_SMALI_EX=false
+fi
+
if [ -d src-ex ]; then
HAS_SRC_EX=true
else
@@ -454,13 +460,25 @@ if [ ${HAS_SRC_EX} = "true" ]; then
javac_with_bootclasspath -d classes-tmp-for-ex `find src-art -name '*.java'`
src_tmp_for_ex="-cp classes-tmp-for-ex"
fi
- mkdir classes-ex
+ mkdir -p classes-ex
javac_with_bootclasspath -d classes-ex $src_tmp_for_ex `find src-ex -name '*.java'`
fi
if [[ -d classes-ex ]] && [ ${NEED_DEX} = "true" ]; then
make_dex classes-ex
+fi
+
+if [ "${HAS_SMALI_EX}" = "true" -a ${NEED_DEX} = "true" ]; then
+ # Compile Smali classes
+ ${SMALI} -JXmx512m assemble ${SMALI_ARGS} --output smali_classes-ex.dex `find smali-ex -name '*.smali'`
+ if [[ ! -s smali_classes-ex.dex ]] ; then
+ fail "${SMALI} produced no output."
+ fi
+ # Merge smali files into classes-ex.dex.
+ make_dexmerge classes-ex.dex smali_classes-ex.dex
+fi
+if [[ -f classes-ex.dex ]]; then
# Apply hiddenapi on the dex files if the test has API list file(s).
if [ ${USE_HIDDENAPI} = "true" -a ${HAS_HIDDENAPI_SPEC} = "true" ]; then
make_hiddenapi classes-ex.dex
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 9c01ba9c55..b9a0963de8 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -534,8 +534,9 @@
"097-duplicate-method",
"138-duplicate-classes-check2",
"159-app-image-fields",
- "674-hiddenapi",
"649-vdex-duplicate-method",
+ "674-hiddenapi",
+ "690-hiddenapi-same-name-methods",
"804-class-extends-itself",
"921-hello-failure",
"999-redefine-hiddenapi"
@@ -555,6 +556,7 @@
"629-vdex-speed",
"647-jni-get-field-id",
"674-hiddenapi",
+ "690-hiddenapi-same-name-methods",
"944-transform-classloaders",
"999-redefine-hiddenapi"
],
@@ -1085,6 +1087,7 @@
"678-quickening",
"679-locks",
"688-shared-library",
+ "690-hiddenapi-same-name-methods",
"999-redefine-hiddenapi",
"1000-non-moving-space-stress",
"1001-app-image-regions",