diff options
author | 2015-12-08 22:33:23 +0000 | |
---|---|---|
committer | 2015-12-08 22:33:23 +0000 | |
commit | eb5ddd7b02ce2d25d4f28f85a13aac628526e1c1 (patch) | |
tree | 8f655b382e73222e70e30daaeedc3999014aa7ae | |
parent | 2433d4e17c3006b8262a0d9421e201fc84777208 (diff) | |
parent | dae24142127c64551142a50423085aabdb0a6060 (diff) |
Merge "ART: Check invoke-interface earlier in verifier"
-rw-r--r-- | compiler/driver/compiler_driver-inl.h | 20 | ||||
-rw-r--r-- | compiler/driver/compiler_driver.cc | 4 | ||||
-rw-r--r-- | compiler/oat_writer.cc | 8 | ||||
-rw-r--r-- | compiler/optimizing/builder.cc | 2 | ||||
-rw-r--r-- | compiler/optimizing/reference_type_propagation.cc | 2 | ||||
-rw-r--r-- | runtime/class_linker-inl.h | 13 | ||||
-rw-r--r-- | runtime/class_linker.cc | 55 | ||||
-rw-r--r-- | runtime/class_linker.h | 9 | ||||
-rw-r--r-- | runtime/entrypoints/entrypoint_utils-inl.h | 7 | ||||
-rw-r--r-- | runtime/entrypoints/quick/quick_trampoline_entrypoints.cc | 3 | ||||
-rw-r--r-- | runtime/verifier/method_verifier.cc | 43 | ||||
-rw-r--r-- | test/800-smali/expected.txt | 1 | ||||
-rw-r--r-- | test/800-smali/smali/b_21869691A.smali | 47 | ||||
-rw-r--r-- | test/800-smali/smali/b_21869691B.smali | 33 | ||||
-rw-r--r-- | test/800-smali/smali/b_21869691C.smali | 12 | ||||
-rw-r--r-- | test/800-smali/smali/b_21869691I.smali | 11 | ||||
-rw-r--r-- | test/800-smali/src/Main.java | 4 |
17 files changed, 227 insertions, 47 deletions
diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index 10841e6700..0eb3e439ac 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -264,18 +264,16 @@ inline ArtMethod* CompilerDriver::ResolveMethod( Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit, uint32_t method_idx, InvokeType invoke_type, bool check_incompatible_class_change) { DCHECK_EQ(class_loader.Get(), soa.Decode<mirror::ClassLoader*>(mUnit->GetClassLoader())); - ArtMethod* resolved_method = mUnit->GetClassLinker()->ResolveMethod( - *dex_cache->GetDexFile(), method_idx, dex_cache, class_loader, nullptr, invoke_type); - DCHECK_EQ(resolved_method == nullptr, soa.Self()->IsExceptionPending()); + ArtMethod* resolved_method = + check_incompatible_class_change + ? mUnit->GetClassLinker()->ResolveMethod<ClassLinker::kForceICCECheck>( + *dex_cache->GetDexFile(), method_idx, dex_cache, class_loader, nullptr, invoke_type) + : mUnit->GetClassLinker()->ResolveMethod<ClassLinker::kNoICCECheckForCache>( + *dex_cache->GetDexFile(), method_idx, dex_cache, class_loader, nullptr, invoke_type); if (UNLIKELY(resolved_method == nullptr)) { + DCHECK(soa.Self()->IsExceptionPending()); // Clean up any exception left by type resolution. soa.Self()->ClearException(); - return nullptr; - } - if (check_incompatible_class_change && - UNLIKELY(resolved_method->CheckIncompatibleClassChange(invoke_type))) { - // Silently return null on incompatible class change. - return nullptr; } return resolved_method; } @@ -361,7 +359,7 @@ inline int CompilerDriver::IsFastInvoke( ArtMethod* called_method; ClassLinker* class_linker = mUnit->GetClassLinker(); if (LIKELY(devirt_target->dex_file == mUnit->GetDexFile())) { - called_method = class_linker->ResolveMethod( + called_method = class_linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>( *devirt_target->dex_file, devirt_target->dex_method_index, dex_cache, class_loader, nullptr, kVirtual); } else { @@ -369,7 +367,7 @@ inline int CompilerDriver::IsFastInvoke( auto target_dex_cache(hs.NewHandle(class_linker->RegisterDexFile( *devirt_target->dex_file, class_linker->GetOrCreateAllocatorForClassLoader(class_loader.Get())))); - called_method = class_linker->ResolveMethod( + called_method = class_linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>( *devirt_target->dex_file, devirt_target->dex_method_index, target_dex_cache, class_loader, nullptr, kVirtual); } diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 9d3af1681a..a05105b84e 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -1902,7 +1902,7 @@ class ResolveClassFieldsAndMethodsVisitor : public CompilationVisitor { } if (resolve_fields_and_methods) { while (it.HasNextDirectMethod()) { - ArtMethod* method = class_linker->ResolveMethod( + ArtMethod* method = class_linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>( dex_file, it.GetMemberIndex(), dex_cache, class_loader, nullptr, it.GetMethodInvokeType(class_def)); if (method == nullptr) { @@ -1911,7 +1911,7 @@ class ResolveClassFieldsAndMethodsVisitor : public CompilationVisitor { it.Next(); } while (it.HasNextVirtualMethod()) { - ArtMethod* method = class_linker->ResolveMethod( + ArtMethod* method = class_linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>( dex_file, it.GetMemberIndex(), dex_cache, class_loader, nullptr, it.GetMethodInvokeType(class_def)); if (method == nullptr) { diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index 0087a0df57..e8e775f2c8 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -641,8 +641,12 @@ class OatWriter::InitImageMethodVisitor : public OatDexMethodVisitor { StackHandleScope<1> hs(soa.Self()); Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->FindDexCache( Thread::Current(), *dex_file_))); - ArtMethod* method = linker->ResolveMethod( - *dex_file_, it.GetMemberIndex(), dex_cache, NullHandle<mirror::ClassLoader>(), nullptr, + ArtMethod* method = linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>( + *dex_file_, + it.GetMemberIndex(), + dex_cache, + NullHandle<mirror::ClassLoader>(), + nullptr, invoke_type); if (method == nullptr) { LOG(INTERNAL_FATAL) << "Unexpected failure to resolve a method: " diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index 8e75bdcdc9..2bbf500a98 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -744,7 +744,7 @@ ArtMethod* HGraphBuilder::ResolveMethod(uint16_t method_idx, InvokeType invoke_t soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader()))); Handle<mirror::Class> compiling_class(hs.NewHandle(GetCompilingClass())); - ArtMethod* resolved_method = class_linker->ResolveMethod( + ArtMethod* resolved_method = class_linker->ResolveMethod<ClassLinker::kForceICCECheck>( *dex_compilation_unit_->GetDexFile(), method_idx, dex_compilation_unit_->GetDexCache(), diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc index dd349240f7..fea903d9cf 100644 --- a/compiler/optimizing/reference_type_propagation.cc +++ b/compiler/optimizing/reference_type_propagation.cc @@ -469,7 +469,7 @@ void RTPVisitor::SetClassAsTypeInfo(HInstruction* instr, // but then we would need to pass it to RTPVisitor just for this debug check. Since // the method is from the String class, the null loader is good enough. Handle<mirror::ClassLoader> loader; - ArtMethod* method = cl->ResolveMethod( + ArtMethod* method = cl->ResolveMethod<ClassLinker::kNoICCECheckForCache>( invoke->GetDexFile(), invoke->GetDexMethodIndex(), dex_cache, loader, nullptr, kDirect); DCHECK(method != nullptr); mirror::Class* declaring_class = method->GetDeclaringClass(); diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h index 88a3996577..a5d10b265f 100644 --- a/runtime/class_linker-inl.h +++ b/runtime/class_linker-inl.h @@ -116,6 +116,7 @@ inline ArtMethod* ClassLinker::GetResolvedMethod(uint32_t method_idx, ArtMethod* return resolved_method; } +template <ClassLinker::ResolveMode kResolveMode> inline ArtMethod* ClassLinker::ResolveMethod(Thread* self, uint32_t method_idx, ArtMethod* referrer, @@ -127,12 +128,12 @@ inline ArtMethod* ClassLinker::ResolveMethod(Thread* self, Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(declaring_class->GetDexCache())); Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(declaring_class->GetClassLoader())); const DexFile* dex_file = h_dex_cache->GetDexFile(); - resolved_method = ResolveMethod(*dex_file, - method_idx, - h_dex_cache, - h_class_loader, - referrer, - type); + resolved_method = ResolveMethod<kResolveMode>(*dex_file, + method_idx, + h_dex_cache, + h_class_loader, + referrer, + type); } // 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. diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 879544f42f..0a37f26135 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -6160,6 +6160,7 @@ mirror::Class* ClassLinker::ResolveType(const DexFile& dex_file, return resolved; } +template <ClassLinker::ResolveMode kResolveMode> ArtMethod* ClassLinker::ResolveMethod(const DexFile& dex_file, uint32_t method_idx, Handle<mirror::DexCache> dex_cache, @@ -6171,6 +6172,12 @@ ArtMethod* ClassLinker::ResolveMethod(const DexFile& dex_file, ArtMethod* resolved = dex_cache->GetResolvedMethod(method_idx, image_pointer_size_); if (resolved != nullptr && !resolved->IsRuntimeMethod()) { DCHECK(resolved->GetDeclaringClassUnchecked() != nullptr) << resolved->GetDexMethodIndex(); + if (kResolveMode == ClassLinker::kForceICCECheck) { + if (resolved->CheckIncompatibleClassChange(type)) { + ThrowIncompatibleClassChangeError(type, resolved->GetInvokeType(), resolved, referrer); + return nullptr; + } + } return resolved; } // Fail, get the declaring class. @@ -6189,8 +6196,36 @@ ArtMethod* ClassLinker::ResolveMethod(const DexFile& dex_file, DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr); break; case kInterface: - resolved = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, image_pointer_size_); - DCHECK(resolved == nullptr || resolved->GetDeclaringClass()->IsInterface()); + // We have to check whether the method id really belongs to an interface (dex static bytecode + // constraint A15). Otherwise you must not invoke-interface on it. + // + // This is not symmetric to A12-A14 (direct, static, virtual), as using FindInterfaceMethod + // assumes that the given type is an interface, and will check the interface table if the + // method isn't declared in the class. So it may find an interface method (usually by name + // in the handling below, but we do the constraint check early). In that case, + // CheckIncompatibleClassChange will succeed (as it is called on an interface method) + // unexpectedly. + // Example: + // interface I { + // foo() + // } + // class A implements I { + // ... + // } + // class B extends A { + // ... + // } + // invoke-interface B.foo + // -> FindInterfaceMethod finds I.foo (interface method), not A.foo (miranda method) + if (UNLIKELY(!klass->IsInterface())) { + ThrowIncompatibleClassChangeError(klass, + "Found class %s, but interface was expected", + PrettyDescriptor(klass).c_str()); + return nullptr; + } else { + resolved = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, image_pointer_size_); + DCHECK(resolved == nullptr || resolved->GetDeclaringClass()->IsInterface()); + } break; case kSuper: // Fall-through. case kVirtual: @@ -6792,4 +6827,20 @@ void ClassLinker::CleanupClassLoaders() { } } +// Instantiate ResolveMethod. +template ArtMethod* ClassLinker::ResolveMethod<ClassLinker::kForceICCECheck>( + const DexFile& dex_file, + uint32_t method_idx, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader, + ArtMethod* referrer, + InvokeType type); +template ArtMethod* ClassLinker::ResolveMethod<ClassLinker::kNoICCECheckForCache>( + const DexFile& dex_file, + uint32_t method_idx, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader, + ArtMethod* referrer, + InvokeType type); + } // namespace art diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 29aac312c1..0d3bc1e2c3 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -246,11 +246,19 @@ class ClassLinker { SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!dex_lock_, !Roles::uninterruptible_); + // Determine whether a dex cache result should be trusted, or an IncompatibleClassChangeError + // check should be performed even after a hit. + enum ResolveMode { // private. + kNoICCECheckForCache, + kForceICCECheck + }; + // Resolve a method with a given ID from the DexFile, storing the // result in DexCache. The ClassLinker and ClassLoader are used as // in ResolveType. What is unique is the method type argument which // is used to determine if this method is a direct, static, or // virtual method. + template <ResolveMode kResolveMode> ArtMethod* ResolveMethod(const DexFile& dex_file, uint32_t method_idx, Handle<mirror::DexCache> dex_cache, @@ -262,6 +270,7 @@ class ClassLinker { ArtMethod* GetResolvedMethod(uint32_t method_idx, ArtMethod* referrer) SHARED_REQUIRES(Locks::mutator_lock_); + template <ResolveMode kResolveMode> ArtMethod* ResolveMethod(Thread* self, uint32_t method_idx, ArtMethod* referrer, InvokeType type) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!dex_lock_, !Roles::uninterruptible_); diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index dccb1dad3b..ba2fb9493f 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -68,7 +68,7 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, class_loader.Assign(caller->GetClassLoader()); } - return class_linker->ResolveMethod( + return class_linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>( *outer_method->GetDexFile(), method_index, dex_cache, class_loader, nullptr, invoke_type); } @@ -401,7 +401,10 @@ inline ArtMethod* FindMethodFromCode(uint32_t method_idx, mirror::Object** this_ mirror::Object* null_this = nullptr; HandleWrapper<mirror::Object> h_this( hs.NewHandleWrapper(type == kStatic ? &null_this : this_object)); - resolved_method = class_linker->ResolveMethod(self, method_idx, referrer, type); + constexpr ClassLinker::ResolveMode resolve_mode = + access_check ? ClassLinker::kForceICCECheck + : ClassLinker::kNoICCECheckForCache; + resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, referrer, type); } if (UNLIKELY(resolved_method == nullptr)) { DCHECK(self->IsExceptionPending()); // Throw exception and unwind. diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 2c8ed88f69..08c9b49729 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -1015,7 +1015,8 @@ extern "C" const void* artQuickResolutionTrampoline( HandleWrapper<mirror::Object> h_receiver( hs.NewHandleWrapper(virtual_or_interface ? &receiver : &dummy)); DCHECK_EQ(caller->GetDexFile(), called_method.dex_file); - called = linker->ResolveMethod(self, called_method.dex_method_index, caller, invoke_type); + called = linker->ResolveMethod<ClassLinker::kForceICCECheck>( + self, called_method.dex_method_index, caller, invoke_type); } const void* code = nullptr; if (LIKELY(!self->IsExceptionPending())) { diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 364b8cefbc..cf27ff2f60 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -192,7 +192,7 @@ void MethodVerifier::VerifyMethods(Thread* self, } previous_method_idx = method_idx; InvokeType type = it->GetMethodInvokeType(*class_def); - ArtMethod* method = linker->ResolveMethod( + ArtMethod* method = linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>( *dex_file, method_idx, dex_cache, class_loader, nullptr, type); if (method == nullptr) { DCHECK(self->IsExceptionPending()); @@ -3638,6 +3638,30 @@ ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess( const RegType& referrer = GetDeclaringClass(); auto* cl = Runtime::Current()->GetClassLinker(); auto pointer_size = cl->GetImagePointerSize(); + + // Check that interface methods are static or match interface classes. + // We only allow statics if we don't have default methods enabled. + if (klass->IsInterface()) { + Runtime* runtime = Runtime::Current(); + const bool default_methods_supported = + runtime == nullptr || + runtime->AreExperimentalFlagsEnabled(ExperimentalFlags::kDefaultMethods); + if (method_type != METHOD_INTERFACE && + (!default_methods_supported || method_type != METHOD_STATIC)) { + Fail(VERIFY_ERROR_CLASS_CHANGE) + << "non-interface method " << PrettyMethod(dex_method_idx, *dex_file_) + << " is in an interface class " << PrettyClass(klass); + return nullptr; + } + } else { + if (method_type == METHOD_INTERFACE) { + Fail(VERIFY_ERROR_CLASS_CHANGE) + << "interface method " << PrettyMethod(dex_method_idx, *dex_file_) + << " is in a non-interface class " << PrettyClass(klass); + return nullptr; + } + } + ArtMethod* res_method = dex_cache_->GetResolvedMethod(dex_method_idx, pointer_size); if (res_method == nullptr) { const char* name = dex_file_->GetMethodName(method_id); @@ -3692,23 +3716,6 @@ ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess( << PrettyMethod(res_method); return nullptr; } - // Check that interface methods are static or match interface classes. - // We only allow statics if we don't have default methods enabled. - Runtime* runtime = Runtime::Current(); - const bool default_methods_supported = - runtime == nullptr || - runtime->AreExperimentalFlagsEnabled(ExperimentalFlags::kDefaultMethods); - if (klass->IsInterface() && - method_type != METHOD_INTERFACE && - (!default_methods_supported || method_type != METHOD_STATIC)) { - Fail(VERIFY_ERROR_CLASS_CHANGE) << "non-interface method " << PrettyMethod(res_method) - << " is in an interface class " << PrettyClass(klass); - return nullptr; - } else if (!klass->IsInterface() && method_type == METHOD_INTERFACE) { - Fail(VERIFY_ERROR_CLASS_CHANGE) << "interface method " << PrettyMethod(res_method) - << " is in a non-interface class " << PrettyClass(klass); - return nullptr; - } // See if the method type implied by the invoke instruction matches the access flags for the // target method. if ((method_type == METHOD_DIRECT && (!res_method->IsDirect() || res_method->IsStatic())) || diff --git a/test/800-smali/expected.txt b/test/800-smali/expected.txt index a590cf1e0b..ebefeea405 100644 --- a/test/800-smali/expected.txt +++ b/test/800-smali/expected.txt @@ -47,4 +47,5 @@ b/23300986 (2) b/23502994 (if-eqz) b/23502994 (check-cast) b/25494456 +b/21869691 Done! diff --git a/test/800-smali/smali/b_21869691A.smali b/test/800-smali/smali/b_21869691A.smali new file mode 100644 index 0000000000..a7a6ef4bc2 --- /dev/null +++ b/test/800-smali/smali/b_21869691A.smali @@ -0,0 +1,47 @@ +# Test that the verifier does not stash methods incorrectly because they are being invoked with +# the wrong opcode. +# +# When using invoke-interface on a method id that is not from an interface class, we should throw +# an IncompatibleClassChangeError. FindInterfaceMethod assumes that the given type is an interface, +# so we can construct a class hierarchy that would have a surprising result: +# +# interface I { +# void a(); +# } +# +# class B implements I { +# // miranda method for a, or a implemented. +# } +# +# class C extends B { +# } +# +# Then calling invoke-interface C.a() will go wrong if there is no explicit check: a can't be found +# in C, but in the interface table, so we will find an interface method and pass ICCE checks. +# +# If we do this before a correct invoke-virtual C.a(), we poison the dex cache with an incorrect +# method. In this test, this is done in A (A < B, so processed first). The "real" call is in B. + +.class public LB21869691A; + +.super Ljava/lang/Object; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public run()V + .registers 3 + new-instance v0, LB21869691C; + invoke-direct {v0}, LB21869691C;-><init>()V + invoke-virtual {v2, v0}, LB21869691A;->callinf(LB21869691C;)V + return-void +.end method + +.method public callinf(LB21869691C;)V + .registers 2 + invoke-interface {p1}, LB21869691C;->a()V + return-void +.end method diff --git a/test/800-smali/smali/b_21869691B.smali b/test/800-smali/smali/b_21869691B.smali new file mode 100644 index 0000000000..1172bdba52 --- /dev/null +++ b/test/800-smali/smali/b_21869691B.smali @@ -0,0 +1,33 @@ +# Test that the verifier does not stash methods incorrectly because they are being invoked with +# the wrong opcode. See b_21869691A.smali for explanation. + +.class public abstract LB21869691B; + +.super Ljava/lang/Object; +.implements LB21869691I; + +.method protected constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +# Have an implementation for the interface method. +.method public a()V + .registers 1 + return-void +.end method + +# Call ourself with invoke-virtual. +.method public callB()V + .registers 1 + invoke-virtual {p0}, LB21869691B;->a()V + return-void +.end method + +# Call C with invoke-virtual. +.method public callB(LB21869691C;)V + .registers 2 + invoke-virtual {p1}, LB21869691C;->a()V + return-void +.end method diff --git a/test/800-smali/smali/b_21869691C.smali b/test/800-smali/smali/b_21869691C.smali new file mode 100644 index 0000000000..4f89a046cd --- /dev/null +++ b/test/800-smali/smali/b_21869691C.smali @@ -0,0 +1,12 @@ +# Test that the verifier does not stash methods incorrectly because they are being invoked with +# the wrong opcode. See b_21869691A.smali for explanation. + +.class public LB21869691C; + +.super LB21869691B; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, LB21869691B;-><init>()V + return-void +.end method diff --git a/test/800-smali/smali/b_21869691I.smali b/test/800-smali/smali/b_21869691I.smali new file mode 100644 index 0000000000..72a27ddd2c --- /dev/null +++ b/test/800-smali/smali/b_21869691I.smali @@ -0,0 +1,11 @@ +# Test that the verifier does not stash methods incorrectly because they are being invoked with +# the wrong opcode. +# +# This is the interface class that has an "a" method. + +.class public abstract interface LB21869691I; + +.super Ljava/lang/Object; + +.method public abstract a()V +.end method diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java index 4844848337..3b62a46fd3 100644 --- a/test/800-smali/src/Main.java +++ b/test/800-smali/src/Main.java @@ -139,6 +139,8 @@ public class Main { new Object[] { "abc" }, null, null)); testCases.add(new TestCase("b/25494456", "B25494456", "run", null, new VerifyError(), null)); + testCases.add(new TestCase("b/21869691", "B21869691A", "run", null, + new IncompatibleClassChangeError(), null)); } public void runTests() { @@ -208,7 +210,7 @@ public class Main { tc.expectedException.getClass().getName() + ", but got " + exc.getClass(), exc); } else { - // Expected exception, do nothing. + // Expected exception, do nothing. } } finally { if (errorReturn != null) { |