diff options
author | 2017-06-12 15:41:56 +0100 | |
---|---|---|
committer | 2017-07-20 13:35:06 +0100 | |
commit | ba118827465d12177f3996e50133960087b1c916 (patch) | |
tree | f39728cdafc7810004d51c0bef2728b98993daa9 /runtime/class_linker-inl.h | |
parent | 64a102dde8c5daad83b991710decb418ce43aac5 (diff) |
ART: Change method lookup to be more consistent to JLS and the RI.
The method lookup for different invoke types was previously
widely different and didn't work well with the dex cache
method array where we have only a single slot for each
MethodId. The new behavior is to perform the same lookup for
all cases, distinguishing only between interface and
non-interface referencing class, and to further align the
behavior with the JLS and the RI. Where the JLS conflicts
with the RI, we follow the JLS semantics.
The new lookup for class methods first searches the methods
declared in the superclass chain (ignoring "copied" methods)
and only then looks in the "copied" methods. If the search
in the superclass chain finds a method that has not been
inherited (i.e. either a private method or a package-access
method where one of the classes in the chain does not belong
to the same package, see JLS 8.4.8), we still search the
"copied" methods as there may actually be a method inherited
from an interface. This follows the JLS semantics where
inherited methods are included in the search (JLS 15.12.2.1)
but conflicts with the RI where the private or
package-access method takes precedence over methods
inherited from interfaces.
Note that this search can find an accessible method that is
not inherited by the qualifying type, either for a package
access method when the referrer is in the same package but
the qualifying type is in another package, or for a private
method where the referrer is in the same class but the
qualifying type is actually a subclass. For the moment we
allow such calls and we shall consider whether to throw
an IncompatibleClassChangeError in this situation in future
to comply with JLS 15.12.4.3.
The new lookup for interface methods searches the interface
class, then all the superinterfaces and then the
java.lang.Object class, see implicitly declared methods in
interfaces, JLS 9.2. The search for the maximally-specific
non-abstract superinterface method is not yet implemented,
but the difference should be difficult to observe as the
usual subsequent call to FindVirtualMethodForInterface()
should yield the same result for any matching method.
The new test 162-method-idx-clash exposes several cases
where we previously completely messed up due to the effects
of the DexCache, or where we were out of line with the RI.
It also tests a case where the JLS and the RI disagree and
we follow the JLS.
Test: art/test/run-test --host --jvm 162-method-resolution
Test: m test-art-host-gtest
Test: testrunner.py --host
Test: testrunner.py --host --interp-ac
Test: Nexus 6P boots.
Test: testrunner.py --target
Bug: 62855082
Bug: 30627598
Change-Id: If450c8cff2751369011d649c25d28a482a2c61a3
Diffstat (limited to 'runtime/class_linker-inl.h')
-rw-r--r-- | runtime/class_linker-inl.h | 151 |
1 files changed, 128 insertions, 23 deletions
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h index 3c51f52616..d29db15f0a 100644 --- a/runtime/class_linker-inl.h +++ b/runtime/class_linker-inl.h @@ -90,33 +90,105 @@ inline mirror::Class* ClassLinker::ResolveType(dex::TypeIndex type_idx, ArtMetho return resolved_type.Ptr(); } +template <bool kThrowOnError, typename ClassGetter> +inline bool ClassLinker::CheckInvokeClassMismatch(ObjPtr<mirror::DexCache> dex_cache, + InvokeType type, + ClassGetter class_getter) { + switch (type) { + case kStatic: + case kSuper: + break; + case kInterface: { + // We have to check whether the method id really belongs to an interface (dex static bytecode + // constraints A15, A16). Otherwise you must not invoke-interface on it. + ObjPtr<mirror::Class> klass = class_getter(); + if (UNLIKELY(!klass->IsInterface())) { + if (kThrowOnError) { + ThrowIncompatibleClassChangeError(klass, + "Found class %s, but interface was expected", + klass->PrettyDescriptor().c_str()); + } + return true; + } + break; + } + case kDirect: + if (dex_cache->GetDexFile()->GetVersion() >= DexFile::kDefaultMethodsVersion) { + break; + } + FALLTHROUGH_INTENDED; + case kVirtual: { + // Similarly, invoke-virtual (and invoke-direct without default methods) must reference + // a non-interface class (dex static bytecode constraint A24, A25). + ObjPtr<mirror::Class> klass = class_getter(); + if (UNLIKELY(klass->IsInterface())) { + if (kThrowOnError) { + ThrowIncompatibleClassChangeError(klass, + "Found interface %s, but class was expected", + klass->PrettyDescriptor().c_str()); + } + return true; + } + break; + } + default: + LOG(FATAL) << "Unreachable - invocation type: " << type; + UNREACHABLE(); + } + return false; +} + +template <bool kThrow> +inline bool ClassLinker::CheckInvokeClassMismatch(ObjPtr<mirror::DexCache> dex_cache, + InvokeType type, + uint32_t method_idx, + ObjPtr<mirror::ClassLoader> class_loader) { + return CheckInvokeClassMismatch<kThrow>( + dex_cache, + type, + [this, dex_cache, method_idx, class_loader]() REQUIRES_SHARED(Locks::mutator_lock_) { + const DexFile& dex_file = *dex_cache->GetDexFile(); + const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx); + ObjPtr<mirror::Class> klass = + LookupResolvedType(dex_file, method_id.class_idx_, dex_cache, class_loader); + DCHECK(klass != nullptr); + return klass; + }); +} + +template <InvokeType type, ClassLinker::ResolveMode kResolveMode> inline ArtMethod* ClassLinker::GetResolvedMethod(uint32_t method_idx, ArtMethod* referrer) { + DCHECK(referrer != nullptr); + // Note: The referrer can be a Proxy constructor. In that case, we need to do the + // lookup in the context of the original method from where it steals the code. + // However, we delay the GetInterfaceMethodIfProxy() until needed. + DCHECK(!referrer->IsProxyMethod() || referrer->IsConstructor()); ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_); if (resolved_method == nullptr || resolved_method->IsRuntimeMethod()) { return nullptr; } - return resolved_method; -} - -inline mirror::Class* ClassLinker::ResolveReferencedClassOfMethod( - uint32_t method_idx, - Handle<mirror::DexCache> dex_cache, - Handle<mirror::ClassLoader> class_loader) { - // NB: We cannot simply use `GetResolvedMethod(method_idx, ...)->GetDeclaringClass()`. This is - // because if we did so than an invoke-super could be incorrectly dispatched in cases where - // GetMethodId(method_idx).class_idx_ refers to a non-interface, non-direct-superclass - // (super*-class?) of the referrer and the direct superclass of the referrer contains a concrete - // implementation of the method. If this class's implementation of the method is copied from an - // interface (either miranda, default or conflict) we would incorrectly assume that is what we - // want to invoke on, instead of the 'concrete' implementation that the direct superclass - // contains. - const DexFile* dex_file = dex_cache->GetDexFile(); - const DexFile::MethodId& method = dex_file->GetMethodId(method_idx); - ObjPtr<mirror::Class> resolved_type = dex_cache->GetResolvedType(method.class_idx_); - if (UNLIKELY(resolved_type == nullptr)) { - resolved_type = ResolveType(*dex_file, method.class_idx_, dex_cache, class_loader); + if (kResolveMode == ResolveMode::kCheckICCEAndIAE) { + referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_); + // Check if the invoke type matches the class type. + ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache(); + ObjPtr<mirror::ClassLoader> class_loader = referrer->GetClassLoader(); + if (CheckInvokeClassMismatch</* kThrow */ false>(dex_cache, type, method_idx, class_loader)) { + return nullptr; + } + // Check access. + ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass(); + if (!referring_class->CanAccessResolvedMethod(resolved_method->GetDeclaringClass(), + resolved_method, + dex_cache, + method_idx)) { + return nullptr; + } + // Check if the invoke type matches the method type. + if (UNLIKELY(resolved_method->CheckIncompatibleClassChange(type))) { + return nullptr; + } } - return resolved_type.Ptr(); + return resolved_method; } template <ClassLinker::ResolveMode kResolveMode> @@ -124,9 +196,15 @@ inline ArtMethod* ClassLinker::ResolveMethod(Thread* self, uint32_t method_idx, ArtMethod* referrer, InvokeType type) { - ArtMethod* resolved_method = GetResolvedMethod(method_idx, referrer); + DCHECK(referrer != nullptr); + // Note: The referrer can be a Proxy constructor. In that case, we need to do the + // lookup in the context of the original method from where it steals the code. + // However, we delay the GetInterfaceMethodIfProxy() until needed. + DCHECK(!referrer->IsProxyMethod() || referrer->IsConstructor()); Thread::PoisonObjectPointersIfDebug(); - if (UNLIKELY(resolved_method == nullptr)) { + ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_); + if (UNLIKELY(resolved_method == nullptr || resolved_method->IsRuntimeMethod())) { + referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_); ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass(); StackHandleScope<2> hs(self); Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(referrer->GetDexCache())); @@ -138,6 +216,33 @@ inline ArtMethod* ClassLinker::ResolveMethod(Thread* self, h_class_loader, referrer, type); + } else if (kResolveMode == ResolveMode::kCheckICCEAndIAE) { + referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_); + // Check if the invoke type matches the class type. + ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache(); + ObjPtr<mirror::ClassLoader> class_loader = referrer->GetClassLoader(); + if (CheckInvokeClassMismatch</* kThrow */ true>(dex_cache, type, method_idx, class_loader)) { + DCHECK(Thread::Current()->IsExceptionPending()); + return nullptr; + } + // Check access. + ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass(); + if (!referring_class->CheckResolvedMethodAccess(resolved_method->GetDeclaringClass(), + resolved_method, + dex_cache, + method_idx, + type)) { + DCHECK(Thread::Current()->IsExceptionPending()); + return nullptr; + } + // Check if the invoke type matches the method type. + if (UNLIKELY(resolved_method->CheckIncompatibleClassChange(type))) { + ThrowIncompatibleClassChangeError(type, + resolved_method->GetInvokeType(), + resolved_method, + referrer); + return nullptr; + } } // Note: We cannot check here to see whether we added the method to the cache. It // might be an erroneous class, which results in it being hidden from us. |