diff options
43 files changed, 2232 insertions, 199 deletions
diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc index 8eb37cf3dc..0355f116f1 100644 --- a/compiler/dex/verified_method.cc +++ b/compiler/dex/verified_method.cc @@ -313,8 +313,9 @@ void VerifiedMethod::GenerateDevirtMap(verifier::MethodVerifier* method_verifier concrete_method = reg_type.GetClass()->FindVirtualMethodForVirtual( abstract_method, pointer_size); } - if (concrete_method == nullptr || concrete_method->IsAbstract()) { - // In cases where concrete_method is not found, or is abstract, continue to the next invoke. + if (concrete_method == nullptr || !concrete_method->IsInvokable()) { + // In cases where concrete_method is not found, or is not invokable, continue to the next + // invoke. continue; } if (reg_type.IsPreciseReference() || concrete_method->IsFinal() || diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index 14ba81d193..10841e6700 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -329,7 +329,7 @@ inline int CompilerDriver::IsFastInvoke( resolved_method->GetMethodIndex() < methods_class->GetVTableLength() && (methods_class->GetVTableEntry( resolved_method->GetMethodIndex(), pointer_size) == resolved_method) && - !resolved_method->IsAbstract(); + resolved_method->IsInvokable(); if (can_sharpen_virtual_based_on_type || can_sharpen_super_based_on_type) { // Sharpen a virtual call into a direct call. The method_idx is into referrer's @@ -374,7 +374,7 @@ inline int CompilerDriver::IsFastInvoke( class_loader, nullptr, kVirtual); } CHECK(called_method != nullptr); - CHECK(!called_method->IsAbstract()); + CHECK(called_method->IsInvokable()); int stats_flags = kFlagMethodResolved; GetCodeAndMethodForDirectCall(/*out*/invoke_type, kDirect, // Sharp type diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 7c1281fd77..dd2ea9e443 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -1621,7 +1621,7 @@ const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, bool* quick_is_inter DCHECK(!method->IsResolutionMethod()) << PrettyMethod(method); DCHECK(!method->IsImtConflictMethod()) << PrettyMethod(method); DCHECK(!method->IsImtUnimplementedMethod()) << PrettyMethod(method); - DCHECK(!method->IsAbstract()) << PrettyMethod(method); + DCHECK(method->IsInvokable()) << PrettyMethod(method); DCHECK(!IsInBootImage(method)) << PrettyMethod(method); // Use original code if it exists. Otherwise, set the code pointer to the resolution @@ -1668,7 +1668,7 @@ const uint8_t* ImageWriter::GetQuickEntryPoint(ArtMethod* method) { // We assume all methods have code. If they don't currently then we set them to the use the // resolution trampoline. Abstract methods never have code and so we need to make sure their // use results in an AbstractMethodError. We use the interpreter to achieve this. - if (UNLIKELY(method->IsAbstract())) { + if (UNLIKELY(!method->IsInvokable())) { return GetOatAddress(kOatAddressQuickToInterpreterBridge); } else { bool quick_is_interpreted; @@ -1714,7 +1714,7 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy) { // We assume all methods have code. If they don't currently then we set them to the use the // resolution trampoline. Abstract methods never have code and so we need to make sure their // use results in an AbstractMethodError. We use the interpreter to achieve this. - if (UNLIKELY(orig->IsAbstract())) { + if (UNLIKELY(!orig->IsInvokable())) { copy->SetEntryPointFromQuickCompiledCodePtrSize( GetOatAddress(kOatAddressQuickToInterpreterBridge), target_ptr_size_); } else { diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index df2013bef4..0363f203b2 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -148,7 +148,7 @@ static ArtMethod* FindVirtualOrInterfaceTarget(HInvoke* invoke, ArtMethod* resol // the target method. Since we check above the exact type of the receiver, // the only reason this can happen is an IncompatibleClassChangeError. return nullptr; - } else if (resolved_method->IsAbstract()) { + } else if (!resolved_method->IsInvokable()) { // The information we had on the receiver was not enough to find // the target method. Since we check above the exact type of the receiver, // the only reason this can happen is an IncompatibleClassChangeError. diff --git a/runtime/art_method.cc b/runtime/art_method.cc index dbb546da29..f7ed81254f 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -67,6 +67,18 @@ mirror::String* ArtMethod::GetNameAsString(Thread* self) { dex_cache); } +void ArtMethod::ThrowInvocationTimeError() { + DCHECK(!IsInvokable()); + // NOTE: IsDefaultConflicting must be first since the actual method might or might not be abstract + // due to the way we select it. + if (IsDefaultConflicting()) { + ThrowIncompatibleClassChangeErrorForMethodConflict(this); + } else { + DCHECK(IsAbstract()); + ThrowAbstractMethodError(this); + } +} + InvokeType ArtMethod::GetInvokeType() { // TODO: kSuper? if (GetDeclaringClass()->IsInterface()) { @@ -330,6 +342,10 @@ void ArtMethod::UnregisterNative() { RegisterNative(GetJniDlsymLookupStub(), false); } +bool ArtMethod::IsOverridableByDefaultMethod() { + return GetDeclaringClass()->IsInterface(); +} + bool ArtMethod::EqualParameters(Handle<mirror::ObjectArray<mirror::Class>> params) { auto* dex_cache = GetDexCache(); auto* dex_file = dex_cache->GetDexFile(); diff --git a/runtime/art_method.h b/runtime/art_method.h index 201b3e64da..5a2d6c36ed 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -136,6 +136,19 @@ class ArtMethod FINAL { return (GetAccessFlags() & kAccMiranda) != 0; } + // Returns true if invoking this method will not throw an AbstractMethodError or + // IncompatibleClassChangeError. + bool IsInvokable() { + return !IsAbstract() && !IsDefaultConflicting(); + } + + // A default conflict method is a special sentinel method that stands for a conflict between + // multiple default methods. It cannot be invoked, throwing an IncompatibleClassChangeError if one + // attempts to do so. + bool IsDefaultConflicting() { + return (GetAccessFlags() & kAccDefaultConflict) != 0u; + } + // This is set by the class linker. bool IsDefault() { return (GetAccessFlags() & kAccDefault) != 0; @@ -170,12 +183,14 @@ class ArtMethod FINAL { } // Returns true if this method could be overridden by a default method. - bool IsOverridableByDefaultMethod() { - return IsDefault() || IsAbstract(); - } + bool IsOverridableByDefaultMethod() SHARED_REQUIRES(Locks::mutator_lock_); bool CheckIncompatibleClassChange(InvokeType type) SHARED_REQUIRES(Locks::mutator_lock_); + // Throws the error that would result from trying to invoke this method (i.e. + // IncompatibleClassChangeError or AbstractMethodError). Only call if !IsInvokable(); + void ThrowInvocationTimeError() SHARED_REQUIRES(Locks::mutator_lock_); + uint16_t GetMethodIndex() SHARED_REQUIRES(Locks::mutator_lock_); // Doesn't do erroneous / unresolved class checks. diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index bafd5edfc6..f252c1edec 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1858,7 +1858,7 @@ const OatFile::OatMethod ClassLinker::FindOatMethodFor(ArtMethod* method, bool* // Special case to get oat code without overwriting a trampoline. const void* ClassLinker::GetQuickOatCodeFor(ArtMethod* method) { - CHECK(!method->IsAbstract()) << PrettyMethod(method); + CHECK(method->IsInvokable()) << PrettyMethod(method); if (method->IsProxyMethod()) { return GetQuickProxyInvokeHandler(); } @@ -1878,7 +1878,7 @@ const void* ClassLinker::GetQuickOatCodeFor(ArtMethod* method) { } const void* ClassLinker::GetOatMethodQuickCodeFor(ArtMethod* method) { - if (method->IsNative() || method->IsAbstract() || method->IsProxyMethod()) { + if (method->IsNative() || !method->IsInvokable() || method->IsProxyMethod()) { return nullptr; } bool found; @@ -1973,6 +1973,13 @@ void ClassLinker::FixupStaticTrampolines(mirror::Class* klass) { // Ignore virtual methods on the iterator. } +void ClassLinker::EnsureThrowsInvocationError(ArtMethod* method) { + DCHECK(method != nullptr); + DCHECK(!method->IsInvokable()); + method->SetEntryPointFromQuickCompiledCodePtrSize(quick_to_interpreter_bridge_trampoline_, + image_pointer_size_); +} + void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class, uint32_t class_def_method_index) { Runtime* const runtime = Runtime::Current(); @@ -1992,8 +1999,8 @@ void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class // Install entry point from interpreter. bool enter_interpreter = NeedsInterpreter(method, method->GetEntryPointFromQuickCompiledCode()); - if (method->IsAbstract()) { - method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); + if (!method->IsInvokable()) { + EnsureThrowsInvocationError(method); return; } @@ -3404,7 +3411,7 @@ void ClassLinker::CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) cons // Basic sanity CHECK(!prototype->IsFinal()); CHECK(method->IsFinal()); - CHECK(!method->IsAbstract()); + CHECK(method->IsInvokable()); // The proxy method doesn't have its own dex cache or dex file and so it steals those of its // interface prototype. The exception to this are Constructors and the Class of the Proxy itself. @@ -4480,7 +4487,7 @@ bool ClassLinker::LinkMethods(Thread* self, // A map from vtable indexes to the method they need to be updated to point to. Used because we // need to have default methods be in the virtuals array of each class but we don't set that up // until LinkInterfaceMethods. - std::unordered_map<size_t, ArtMethod*> default_translations; + std::unordered_map<size_t, ClassLinker::MethodTranslation> default_translations; // Link virtual methods then interface methods. // We set up the interface lookup table first because we need it to determine if we need to update // any vtable entries with new default method implementations. @@ -4613,7 +4620,7 @@ const uint32_t LinkVirtualHashTable::removed_index_ = std::numeric_limits<uint32 bool ClassLinker::LinkVirtualMethods( Thread* self, Handle<mirror::Class> klass, - /*out*/std::unordered_map<size_t, ArtMethod*>* default_translations) { + /*out*/std::unordered_map<size_t, ClassLinker::MethodTranslation>* default_translations) { const size_t num_virtual_methods = klass->NumVirtualMethods(); if (klass->IsInterface()) { // No vtable. @@ -4736,46 +4743,55 @@ bool ClassLinker::LinkVirtualMethods( << " would have incorrectly overridden the package-private method in " << PrettyDescriptor(super_method->GetDeclaringClassDescriptor()); } - } else if (super_method->IsDefault()) { + } else if (super_method->IsOverridableByDefaultMethod()) { // We didn't directly override this method but we might through default methods... // Check for default method update. ArtMethod* default_method = nullptr; - std::string icce_message; - if (!FindDefaultMethodImplementation(self, - super_method, - klass, - /*out*/&default_method, - /*out*/&icce_message)) { - // An error occurred while finding default methods. - // TODO This should actually be thrown when we attempt to invoke this method. - ThrowIncompatibleClassChangeError(klass.Get(), "%s", icce_message.c_str()); - return false; - } - // This should always work because we inherit superclass interfaces. We should either get - // 1) An IncompatibleClassChangeError because of conflicting default method - // implementations. - // 2) The same default method implementation as the superclass. - // 3) A default method that overrides the superclass's. - // Therefore this check should never fail. - CHECK(default_method != nullptr); - if (UNLIKELY(default_method->GetDeclaringClass() != super_method->GetDeclaringClass())) { - // TODO Refactor this add default methods to virtuals here and not in - // LinkInterfaceMethods maybe. - // The problem is default methods might override previously present default-method or - // miranda-method vtable entries from the superclass. Unfortunately we need these to - // be entries in this class's virtuals. We do not give these entries there until - // LinkInterfaceMethods so we pass this map around to let it know which vtable - // entries need to be updated. - // Make a note that vtable entry j must be updated, store what it needs to be updated to. - // We will allocate a virtual method slot in LinkInterfaceMethods and fix it up then. - default_translations->insert({j, default_method}); - VLOG(class_linker) << "Method " << PrettyMethod(super_method) << " overridden by default " - << PrettyMethod(default_method) << " in " << PrettyClass(klass.Get()); - } else { - // They are the same method/no override - // Cannot do direct comparison because we had to copy the ArtMethod object into the - // superclass's vtable. - continue; + switch (FindDefaultMethodImplementation(self, + super_method, + klass, + /*out*/&default_method)) { + case DefaultMethodSearchResult::kDefaultConflict: { + // A conflict was found looking for default methods. Note this (assuming it wasn't + // pre-existing) in the translations map. + if (UNLIKELY(!super_method->IsDefaultConflicting())) { + // Don't generate another conflict method to reduce memory use as an optimization. + default_translations->insert( + {j, ClassLinker::MethodTranslation::CreateConflictingMethod()}); + } + break; + } + case DefaultMethodSearchResult::kAbstractFound: { + // No conflict but method is abstract. + // We note that this vtable entry must be made abstract. + if (UNLIKELY(!super_method->IsAbstract())) { + default_translations->insert( + {j, ClassLinker::MethodTranslation::CreateAbstractMethod()}); + } + break; + } + case DefaultMethodSearchResult::kDefaultFound: { + if (UNLIKELY(super_method->IsDefaultConflicting() || + default_method->GetDeclaringClass() != super_method->GetDeclaringClass())) { + // Found a default method implementation that is new. + // TODO Refactor this add default methods to virtuals here and not in + // LinkInterfaceMethods maybe. + // The problem is default methods might override previously present + // default-method or miranda-method vtable entries from the superclass. + // Unfortunately we need these to be entries in this class's virtuals. We do not + // give these entries there until LinkInterfaceMethods so we pass this map around + // to let it know which vtable entries need to be updated. + // Make a note that vtable entry j must be updated, store what it needs to be updated + // to. We will allocate a virtual method slot in LinkInterfaceMethods and fix it up + // then. + default_translations->insert( + {j, ClassLinker::MethodTranslation::CreateTranslatedMethod(default_method)}); + VLOG(class_linker) << "Method " << PrettyMethod(super_method) + << " overridden by default " << PrettyMethod(default_method) + << " in " << PrettyClass(klass.Get()); + } + break; + } } } } @@ -4828,23 +4844,75 @@ bool ClassLinker::LinkVirtualMethods( return true; } +// Determine if the given iface has any subinterface in the given list that declares the method +// specified by 'target'. +// +// Arguments +// - self: The thread we are running on +// - target: A comparator that will match any method that overrides the method we are checking for +// - iftable: The iftable we are searching for an overriding method on. +// - ifstart: The index of the interface we are checking to see if anything overrides +// - iface: The interface we are checking to see if anything overrides. +// - image_pointer_size: +// The image pointer size. +// +// Returns +// - True: There is some method that matches the target comparator defined in an interface that +// is a subtype of iface. +// - False: There is no method that matches the target comparator in any interface that is a subtype +// of iface. +static bool ContainsOverridingMethodOf(Thread* self, + MethodNameAndSignatureComparator& target, + Handle<mirror::IfTable> iftable, + size_t ifstart, + Handle<mirror::Class> iface, + size_t image_pointer_size) + SHARED_REQUIRES(Locks::mutator_lock_) { + DCHECK(self != nullptr); + DCHECK(iface.Get() != nullptr); + DCHECK(iftable.Get() != nullptr); + DCHECK_GE(ifstart, 0u); + DCHECK_LT(ifstart, iftable->Count()); + DCHECK_EQ(iface.Get(), iftable->GetInterface(ifstart)); + DCHECK(iface->IsInterface()); + + size_t iftable_count = iftable->Count(); + StackHandleScope<1> hs(self); + MutableHandle<mirror::Class> current_iface(hs.NewHandle<mirror::Class>(nullptr)); + for (size_t k = ifstart + 1; k < iftable_count; k++) { + // Skip ifstart since our current interface obviously cannot override itself. + current_iface.Assign(iftable->GetInterface(k)); + size_t num_instance_methods = current_iface->NumVirtualMethods(); + // Iterate through every method on this interface. The order does not matter so we go forwards. + for (size_t m = 0; m < num_instance_methods; m++) { + ArtMethod* current_method = current_iface->GetVirtualMethodUnchecked(m, image_pointer_size); + if (UNLIKELY(target.HasSameNameAndSignature( + current_method->GetInterfaceMethodIfProxy(image_pointer_size)))) { + // Check if the i'th interface is a subtype of this one. + if (iface->IsAssignableFrom(current_iface.Get())) { + return true; + } + break; + } + } + } + return false; +} + // Find the default method implementation for 'interface_method' in 'klass'. Stores it into -// out_default_method and returns true on success. If no default method was found stores nullptr -// into out_default_method and returns true. If an error occurs (such as a default_method conflict) -// it will fill the icce_message with an appropriate message for an IncompatibleClassChangeError, -// which should then be thrown by the caller. -bool ClassLinker::FindDefaultMethodImplementation(Thread* self, - ArtMethod* target_method, - Handle<mirror::Class> klass, - /*out*/ArtMethod** out_default_method, - /*out*/std::string* icce_message) const { +// out_default_method and returns kDefaultFound on success. If no default method was found return +// kAbstractFound and store nullptr into out_default_method. If an error occurs (such as a +// default_method conflict) it will return kDefaultConflict. +ClassLinker::DefaultMethodSearchResult ClassLinker::FindDefaultMethodImplementation( + Thread* self, + ArtMethod* target_method, + Handle<mirror::Class> klass, + /*out*/ArtMethod** out_default_method) const { DCHECK(self != nullptr); DCHECK(target_method != nullptr); DCHECK(out_default_method != nullptr); - DCHECK(icce_message != nullptr); *out_default_method = nullptr; - mirror::Class* chosen_iface = nullptr; // We organize the interface table so that, for interface I any subinterfaces J follow it in the // table. This lets us walk the table backwards when searching for default methods. The first one @@ -4855,19 +4923,23 @@ bool ClassLinker::FindDefaultMethodImplementation(Thread* self, // The order of unrelated interfaces does not matter and is not defined. size_t iftable_count = klass->GetIfTableCount(); if (iftable_count == 0) { - // No interfaces. We have already reset out to null so just return true. - return true; + // No interfaces. We have already reset out to null so just return kAbstractFound. + return DefaultMethodSearchResult::kAbstractFound; } - StackHandleScope<1> hs(self); + StackHandleScope<3> hs(self); + MutableHandle<mirror::Class> chosen_iface(hs.NewHandle<mirror::Class>(nullptr)); MutableHandle<mirror::IfTable> iftable(hs.NewHandle(klass->GetIfTable())); + MutableHandle<mirror::Class> iface(hs.NewHandle<mirror::Class>(nullptr)); MethodNameAndSignatureComparator target_name_comparator( target_method->GetInterfaceMethodIfProxy(image_pointer_size_)); // Iterates over the klass's iftable in reverse - // We have a break at the end because size_t is unsigned. - for (size_t k = iftable_count - 1; /* break if k == 0 at end */; --k) { + for (size_t k = iftable_count; k != 0; ) { + --k; + DCHECK_LT(k, iftable->Count()); - mirror::Class* iface = iftable->GetInterface(k); + + iface.Assign(iftable->GetInterface(k)); size_t num_instance_methods = iface->NumVirtualMethods(); // Iterate through every method on this interface. The order does not matter so we go forwards. for (size_t m = 0; m < num_instance_methods; m++) { @@ -4880,31 +4952,60 @@ bool ClassLinker::FindDefaultMethodImplementation(Thread* self, } // The verifier should have caught the non-public method. DCHECK(current_method->IsPublic()) << "Interface method is not public!"; - if (UNLIKELY(chosen_iface != nullptr)) { - // We have multiple default impls of the same method. We need to check they do not - // conflict and throw an error if they do. Conflicting means that the current iface is not - // masked by the chosen interface. - if (!iface->IsAssignableFrom(chosen_iface)) { - *icce_message = StringPrintf("Conflicting default method implementations: '%s' and '%s'", - PrettyMethod(current_method).c_str(), - PrettyMethod(*out_default_method).c_str()); - return false; + if (UNLIKELY(chosen_iface.Get() != nullptr)) { + // We have multiple default impls of the same method. This is a potential default conflict. + // We need to check if this possibly conflicting method is either a superclass of the chosen + // default implementation or is overridden by a non-default interface method. In either case + // there is no conflict. + if (!iface->IsAssignableFrom(chosen_iface.Get()) && + !ContainsOverridingMethodOf(self, + target_name_comparator, + iftable, + k, + iface, + image_pointer_size_)) { + LOG(WARNING) << "Conflicting default method implementations found: " + << PrettyMethod(current_method) << " and " + << PrettyMethod(*out_default_method) << " in class " + << PrettyClass(klass.Get()) << " conflict."; + *out_default_method = nullptr; + return DefaultMethodSearchResult::kDefaultConflict; } else { break; // Continue checking at the next interface. } } else { - *out_default_method = current_method; - chosen_iface = iface; - // We should now finish traversing the graph to find if we have default methods that - // conflict. - break; + // chosen_iface == null + if (!ContainsOverridingMethodOf(self, + target_name_comparator, + iftable, + k, + iface, + image_pointer_size_)) { + // Don't set this as the chosen interface if something else is overriding it (because that + // other interface would be potentially chosen instead if it was default). If the other + // interface was abstract then we wouldn't select this interface as chosen anyway since + // the abstract method masks it. + *out_default_method = current_method; + chosen_iface.Assign(iface.Get()); + // We should now finish traversing the graph to find if we have default methods that + // conflict. + } else { + VLOG(class_linker) << "A default method '" << PrettyMethod(current_method) << "' was " + << "skipped because it was overridden by an abstract method in a " + << "subinterface on class '" << PrettyClass(klass.Get()) << "'"; + } } - } - if (k == 0) { break; } } - return true; + if (*out_default_method != nullptr) { + VLOG(class_linker) << "Default method '" << PrettyMethod(*out_default_method) << "' selected " + << "as the implementation for '" << PrettyMethod(target_method) << "' " + << "in '" << PrettyClass(klass.Get()) << "'"; + return DefaultMethodSearchResult::kDefaultFound; + } else { + return DefaultMethodSearchResult::kAbstractFound; + } } // Sets imt_ref appropriately for LinkInterfaceMethods. @@ -4912,7 +5013,7 @@ bool ClassLinker::FindDefaultMethodImplementation(Thread* self, // Otherwise it will set the conflict method which will figure out which method to use during // runtime. static void SetIMTRef(ArtMethod* unimplemented_method, - ArtMethod* conflict_method, + ArtMethod* imt_conflict_method, size_t image_pointer_size, ArtMethod* current_method, /*out*/ArtMethod** imt_ref) @@ -4920,7 +5021,7 @@ static void SetIMTRef(ArtMethod* unimplemented_method, // Place method in imt if entry is empty, place conflict otherwise. if (*imt_ref == unimplemented_method) { *imt_ref = current_method; - } else if (*imt_ref != conflict_method) { + } else if (*imt_ref != imt_conflict_method) { // If we are not a conflict and we have the same signature and name as the imt // entry, it must be that we overwrote a superclass vtable entry. MethodNameAndSignatureComparator imt_comparator( @@ -4929,7 +5030,7 @@ static void SetIMTRef(ArtMethod* unimplemented_method, current_method->GetInterfaceMethodIfProxy(image_pointer_size))) { *imt_ref = current_method; } else { - *imt_ref = conflict_method; + *imt_ref = imt_conflict_method; } } } @@ -5136,10 +5237,23 @@ bool ClassLinker::SetupInterfaceLookupTable(Thread* self, Handle<mirror::Class> return true; } +// Finds the method with a name/signature that matches cmp in the given list of methods. The list of +// methods must be unique. +static ArtMethod* FindSameNameAndSignature(MethodNameAndSignatureComparator& cmp, + const ScopedArenaVector<ArtMethod*>& list) + SHARED_REQUIRES(Locks::mutator_lock_) { + for (ArtMethod* method : list) { + if (cmp.HasSameNameAndSignature(method)) { + return method; + } + } + return nullptr; +} + bool ClassLinker::LinkInterfaceMethods( Thread* self, Handle<mirror::Class> klass, - const std::unordered_map<size_t, ArtMethod*>& default_translations, + const std::unordered_map<size_t, ClassLinker::MethodTranslation>& default_translations, ArtMethod** out_imt) { StackHandleScope<3> hs(self); Runtime* const runtime = Runtime::Current(); @@ -5162,12 +5276,14 @@ bool ClassLinker::LinkInterfaceMethods( // Use the linear alloc pool since this one is in the low 4gb for the compiler. ArenaStack stack(runtime->GetLinearAlloc()->GetArenaPool()); ScopedArenaAllocator allocator(&stack); + + ScopedArenaVector<ArtMethod*> default_conflict_methods(allocator.Adapter()); ScopedArenaVector<ArtMethod*> miranda_methods(allocator.Adapter()); ScopedArenaVector<ArtMethod*> default_methods(allocator.Adapter()); MutableHandle<mirror::PointerArray> vtable(hs.NewHandle(klass->GetVTableDuringLinking())); ArtMethod* const unimplemented_method = runtime->GetImtUnimplementedMethod(); - ArtMethod* const conflict_method = runtime->GetImtConflictMethod(); + ArtMethod* const imt_conflict_method = runtime->GetImtConflictMethod(); // Copy the IMT from the super class if possible. bool extend_super_iftable = false; if (has_superclass) { @@ -5203,8 +5319,8 @@ bool ClassLinker::LinkInterfaceMethods( auto** imt_ref = &out_imt[imt_index]; if (*imt_ref == unimplemented_method) { *imt_ref = method; - } else if (*imt_ref != conflict_method) { - *imt_ref = conflict_method; + } else if (*imt_ref != imt_conflict_method) { + *imt_ref = imt_conflict_method; } } } @@ -5239,7 +5355,16 @@ bool ClassLinker::LinkInterfaceMethods( auto* old_cause = self->StartAssertNoThreadSuspension( "Copying ArtMethods for LinkInterfaceMethods"); - for (size_t i = 0; i < ifcount; ++i) { + // Going in reverse to ensure that we will hit abstract methods that override defaults before the + // defaults. This means we don't need to do any trickery when creating the Miranda methods, since + // they will already be null. This has the additional benefit that the declarer of a miranda + // method will actually declare an abstract method. + for (size_t i = ifcount; i != 0; ) { + --i; + + DCHECK_GE(i, 0u); + DCHECK_LT(i, ifcount); + size_t num_methods = iftable->GetInterface(i)->NumVirtualMethods(); if (num_methods > 0) { StackHandleScope<2> hs2(self); @@ -5250,6 +5375,11 @@ bool ClassLinker::LinkInterfaceMethods( LengthPrefixedArray<ArtMethod>* input_virtual_methods = nullptr; Handle<mirror::PointerArray> input_vtable_array = NullHandle<mirror::PointerArray>(); int32_t input_array_length = 0; + // TODO Cleanup Needed: In the presence of default methods this optimization is rather dirty + // and confusing. Default methods should always look through all the superclasses + // because they are the last choice of an implementation. We get around this by looking + // at the super-classes iftable methods (copied into method_array previously) when we are + // looking for the implementation of a super-interface method but that is rather dirty. if (super_interface) { // We are overwriting a super class interface, try to only virtual methods instead of the // whole vtable. @@ -5279,8 +5409,7 @@ bool ClassLinker::LinkInterfaceMethods( // // To find defaults we need to do the same but also go over interfaces. bool found_impl = false; - ArtMethod* default_impl = nullptr; - bool found_default_impl = false; + ArtMethod* vtable_impl = nullptr; for (int32_t k = input_array_length - 1; k >= 0; --k) { ArtMethod* vtable_method = input_virtual_methods != nullptr ? &input_virtual_methods->At(k, method_size, method_alignment) : @@ -5297,77 +5426,138 @@ bool ClassLinker::LinkInterfaceMethods( "Method '%s' implementing interface method '%s' is not public", PrettyMethod(vtable_method).c_str(), PrettyMethod(interface_method).c_str()); return false; - } else if (vtable_method->IsDefault()) { + } else if (UNLIKELY(vtable_method->IsOverridableByDefaultMethod())) { // We might have a newer, better, default method for this, so we just skip it. If we // are still using this we will select it again when scanning for default methods. To // obviate the need to copy the method again we will make a note that we already found // a default here. // TODO This should be much cleaner. - found_default_impl = true; - default_impl = vtable_method; + vtable_impl = vtable_method; break; } else { found_impl = true; + method_array->SetElementPtrSize(j, vtable_method, image_pointer_size_); + // Place method in imt if entry is empty, place conflict otherwise. + SetIMTRef(unimplemented_method, + imt_conflict_method, + image_pointer_size_, + vtable_method, + /*out*/imt_ptr); + break; } - method_array->SetElementPtrSize(j, vtable_method, image_pointer_size_); - // Place method in imt if entry is empty, place conflict otherwise. - SetIMTRef(unimplemented_method, - conflict_method, - image_pointer_size_, - vtable_method, - /*out*/imt_ptr); - break; } } - // We should only search for default implementations when the class does not implement the - // method directly and either (1) the interface is newly implemented on this class and not - // on any of its superclasses, (2) the superclass's implementation is a default method, or - // (3) the superclass does not have an implementation. - if (!found_impl && (!super_interface || - method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_) - ->IsOverridableByDefaultMethod())) { - ArtMethod* current_method = nullptr; - std::string icce_message; - if (!FindDefaultMethodImplementation(self, - interface_method, - klass, - /*out*/¤t_method, - /*out*/&icce_message)) { - // There was a conflict with default method implementations. - self->EndAssertNoThreadSuspension(old_cause); - // TODO This should actually be thrown when we attempt to invoke this method. - ThrowIncompatibleClassChangeError(klass.Get(), "%s", icce_message.c_str()); - return false; - } else if (current_method != nullptr) { - if (found_default_impl && - current_method->GetDeclaringClass() == default_impl->GetDeclaringClass()) { + // Continue on to the next method if we are done. + if (LIKELY(found_impl)) { + continue; + } else if (LIKELY(super_interface)) { + // Don't look for a default implementation when the super-method is implemented directly + // by the class. + // + // See if we can use the superclasses method and skip searching everything else. + // Note: !found_impl && super_interface + CHECK(extend_super_iftable); + // If this is a super_interface method it is possible we shouldn't override it because a + // superclass could have implemented it directly. We get the method the superclass used + // to implement this to know if we can override it with a default method. Doing this is + // safe since we know that the super_iftable is filled in so we can simply pull it from + // there. We don't bother if this is not a super-classes interface since in that case we + // have scanned the entire vtable anyway and would have found it. + // TODO This is rather dirty but it is faster than searching through the entire vtable + // every time. + ArtMethod* supers_method = + method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_); + DCHECK(supers_method != nullptr); + DCHECK(interface_name_comparator.HasSameNameAndSignature(supers_method)); + if (!supers_method->IsOverridableByDefaultMethod()) { + // The method is not overridable by a default method (i.e. it is directly implemented + // in some class). Therefore move onto the next interface method. + continue; + } + } + // If we haven't found it yet we should search through the interfaces for default methods. + ArtMethod* current_method = nullptr; + switch (FindDefaultMethodImplementation(self, + interface_method, + klass, + /*out*/¤t_method)) { + case DefaultMethodSearchResult::kDefaultConflict: { + // Default method conflict. + DCHECK(current_method == nullptr); + ArtMethod* default_conflict_method = nullptr; + if (vtable_impl != nullptr && vtable_impl->IsDefaultConflicting()) { + // We can reuse the method from the superclass, don't bother adding it to virtuals. + default_conflict_method = vtable_impl; + } else { + // See if we already have a conflict method for this method. + ArtMethod* preexisting_conflict = FindSameNameAndSignature(interface_name_comparator, + default_conflict_methods); + if (LIKELY(preexisting_conflict != nullptr)) { + // We already have another conflict we can reuse. + default_conflict_method = preexisting_conflict; + } else { + // Create a new conflict method for this to use. + default_conflict_method = + reinterpret_cast<ArtMethod*>(allocator.Alloc(method_size)); + new(default_conflict_method) ArtMethod(interface_method, image_pointer_size_); + default_conflict_methods.push_back(default_conflict_method); + } + } + current_method = default_conflict_method; + break; + } + case DefaultMethodSearchResult::kDefaultFound: { + DCHECK(current_method != nullptr); + // Found a default method. + if (vtable_impl != nullptr && + current_method->GetDeclaringClass() == vtable_impl->GetDeclaringClass()) { // We found a default method but it was the same one we already have from our // superclass. Don't bother adding it to our vtable again. - current_method = default_impl; + current_method = vtable_impl; } else { - // We found a default method implementation and there were no conflicts. - // Save the default method. We need to add it to the vtable. - default_methods.push_back(current_method); + // Only record this default method if it is new to save space. + ArtMethod* old = FindSameNameAndSignature(interface_name_comparator, default_methods); + if (old == nullptr) { + // We found a default method implementation and there were no conflicts. + // Save the default method. We need to add it to the vtable. + default_methods.push_back(current_method); + } else { + CHECK(old == current_method) << "Multiple default implementations selected!"; + } } - method_array->SetElementPtrSize(j, current_method, image_pointer_size_); - SetIMTRef(unimplemented_method, - conflict_method, - image_pointer_size_, - current_method, - /*out*/imt_ptr); - found_impl = true; + break; } - } - if (!found_impl && !super_interface) { - // It is defined in this class or any of its subclasses. - ArtMethod* miranda_method = nullptr; - for (auto& mir_method : miranda_methods) { - if (interface_name_comparator.HasSameNameAndSignature(mir_method)) { - miranda_method = mir_method; - break; + case DefaultMethodSearchResult::kAbstractFound: { + DCHECK(current_method == nullptr); + // Abstract method masks all defaults. + if (vtable_impl != nullptr && + vtable_impl->IsAbstract() && + !vtable_impl->IsDefaultConflicting()) { + // We need to make this an abstract method but the version in the vtable already is so + // don't do anything. + current_method = vtable_impl; } + break; } + } + if (current_method != nullptr) { + // We found a default method implementation. Record it in the iftable and IMT. + method_array->SetElementPtrSize(j, current_method, image_pointer_size_); + SetIMTRef(unimplemented_method, + imt_conflict_method, + image_pointer_size_, + current_method, + /*out*/imt_ptr); + } else if (!super_interface) { + // We could not find an implementation for this method and since it is a brand new + // interface we searched the entire vtable (and all default methods) for an implementation + // but couldn't find one. We therefore need to make a miranda method. + // + // Find out if there is already a miranda method we can use. + ArtMethod* miranda_method = FindSameNameAndSignature(interface_name_comparator, + miranda_methods); if (miranda_method == nullptr) { + DCHECK(interface_method->IsAbstract()) << PrettyMethod(interface_method); miranda_method = reinterpret_cast<ArtMethod*>(allocator.Alloc(method_size)); CHECK(miranda_method != nullptr); // Point the interface table at a phantom slot. @@ -5379,10 +5569,15 @@ bool ClassLinker::LinkInterfaceMethods( } } } - if (!miranda_methods.empty() || !default_methods.empty()) { + if (!miranda_methods.empty() || !default_methods.empty() || !default_conflict_methods.empty()) { + VLOG(class_linker) << PrettyClass(klass.Get()) << ": miranda_methods=" << miranda_methods.size() + << " default_methods=" << default_methods.size() + << " default_conflict_methods=" << default_conflict_methods.size(); const size_t old_method_count = klass->NumVirtualMethods(); - const size_t new_method_count = - old_method_count + miranda_methods.size() + default_methods.size(); + const size_t new_method_count = old_method_count + + miranda_methods.size() + + default_methods.size() + + default_conflict_methods.size(); // Attempt to realloc to save RAM if possible. LengthPrefixedArray<ArtMethod>* old_virtuals = klass->GetVirtualMethodsPtr(); // The Realloced virtual methods aren't visiblef from the class roots, so there is no issue @@ -5440,15 +5635,32 @@ bool ClassLinker::LinkInterfaceMethods( for (ArtMethod* def_method : default_methods) { ArtMethod& new_method = *out; new_method.CopyFrom(def_method, image_pointer_size_); - new_method.SetAccessFlags(new_method.GetAccessFlags() | kAccDefault); // Clear the preverified flag if it is present. Since this class hasn't been verified yet it // shouldn't have methods that are preverified. // TODO This is rather arbitrary. We should maybe support classes where only some of its // methods are preverified. - new_method.SetAccessFlags(new_method.GetAccessFlags() & ~kAccPreverified); + new_method.SetAccessFlags((new_method.GetAccessFlags() | kAccDefault) & ~kAccPreverified); move_table.emplace(def_method, &new_method); ++out; } + for (ArtMethod* conf_method : default_conflict_methods) { + ArtMethod& new_method = *out; + new_method.CopyFrom(conf_method, image_pointer_size_); + // This is a type of default method (there are default method impls, just a conflict) so mark + // this as a default, non-abstract method, since thats what it is. Also clear the preverified + // bit since this class hasn't been verified yet it shouldn't have methods that are + // preverified. + constexpr uint32_t kSetFlags = kAccDefault | kAccDefaultConflict; + constexpr uint32_t kMaskFlags = ~(kAccAbstract | kAccPreverified); + new_method.SetAccessFlags((new_method.GetAccessFlags() | kSetFlags) & kMaskFlags); + DCHECK(new_method.IsDefaultConflicting()); + // The actual method might or might not be marked abstract since we just copied it from a + // (possibly default) interface method. We need to set it entry point to be the bridge so that + // the compiler will not invoke the implementation of whatever method we copied from. + EnsureThrowsInvocationError(&new_method); + move_table.emplace(conf_method, &new_method); + ++out; + } virtuals->SetSize(new_method_count); UpdateClassVirtualMethods(klass.Get(), virtuals); // Done copying methods, they are all roots in the class now, so we can end the no thread @@ -5456,8 +5668,10 @@ bool ClassLinker::LinkInterfaceMethods( self->EndAssertNoThreadSuspension(old_cause); const size_t old_vtable_count = vtable->GetLength(); - const size_t new_vtable_count = - old_vtable_count + miranda_methods.size() + default_methods.size(); + const size_t new_vtable_count = old_vtable_count + + miranda_methods.size() + + default_methods.size() + + default_conflict_methods.size(); miranda_methods.clear(); vtable.Assign(down_cast<mirror::PointerArray*>(vtable->CopyOf(self, new_vtable_count))); if (UNLIKELY(vtable.Get() == nullptr)) { @@ -5482,9 +5696,27 @@ bool ClassLinker::LinkInterfaceMethods( auto translation_it = default_translations.find(i); bool found_translation = false; if (translation_it != default_translations.end()) { - size_t vtable_index; - std::tie(vtable_index, translated_method) = *translation_it; - DCHECK_EQ(vtable_index, i); + if (translation_it->second.IsInConflict()) { + // Find which conflict method we are to use for this method. + MethodNameAndSignatureComparator old_method_comparator( + translated_method->GetInterfaceMethodIfProxy(image_pointer_size_)); + ArtMethod* new_conflict_method = FindSameNameAndSignature(old_method_comparator, + default_conflict_methods); + CHECK(new_conflict_method != nullptr) << "Expected a conflict method!"; + translated_method = new_conflict_method; + } else if (translation_it->second.IsAbstract()) { + // Find which miranda method we are to use for this method. + MethodNameAndSignatureComparator old_method_comparator( + translated_method->GetInterfaceMethodIfProxy(image_pointer_size_)); + ArtMethod* miranda_method = FindSameNameAndSignature(old_method_comparator, + miranda_methods); + DCHECK(miranda_method != nullptr); + translated_method = miranda_method; + } else { + // Normal default method (changed from an older default or abstract interface method). + DCHECK(translation_it->second.IsTranslation()); + translated_method = translation_it->second.GetTranslation(); + } found_translation = true; } DCHECK(translated_method != nullptr); diff --git a/runtime/class_linker.h b/runtime/class_linker.h index cae62ef50f..d6a27b18b2 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -721,6 +721,83 @@ class ClassLinker { ArtMethod** out_imt) SHARED_REQUIRES(Locks::mutator_lock_); + // Does anything needed to make sure that the compiler will not generate a direct invoke to this + // method. Should only be called on non-invokable methods. + void EnsureThrowsInvocationError(ArtMethod* method) + SHARED_REQUIRES(Locks::mutator_lock_); + + // A wrapper class representing the result of a method translation used for linking methods and + // updating superclass default methods. For each method in a classes vtable there are 4 states it + // could be in: + // 1) No translation is necessary. In this case there is no MethodTranslation object for it. This + // is the standard case and is true when the method is not overridable by a default method, + // the class defines a concrete implementation of the method, the default method implementation + // remains the same, or an abstract method stayed abstract. + // 2) The method must be translated to a different default method. We note this with + // CreateTranslatedMethod. + // 3) The method must be replaced with a conflict method. This happens when a superclass + // implements an interface with a default method and this class implements an unrelated + // interface that also defines that default method. We note this with CreateConflictingMethod. + // 4) The method must be replaced with an abstract miranda method. This happens when a superclass + // implements an interface with a default method and this class implements a subinterface of + // the superclass's interface which declares the default method abstract. We note this with + // CreateAbstractMethod. + // + // When a method translation is unnecessary (case #1), we don't put it into the + // default_translation maps. So an instance of MethodTranslation must be in one of #2-#4. + class MethodTranslation { + public: + // This slot must become a default conflict method. + static MethodTranslation CreateConflictingMethod() { + return MethodTranslation(Type::kConflict, /*translation*/nullptr); + } + + // This slot must become an abstract method. + static MethodTranslation CreateAbstractMethod() { + return MethodTranslation(Type::kAbstract, /*translation*/nullptr); + } + + // Use the given method as the current value for this vtable slot during translation. + static MethodTranslation CreateTranslatedMethod(ArtMethod* new_method) { + return MethodTranslation(Type::kTranslation, new_method); + } + + // Returns true if this is a method that must become a conflict method. + bool IsInConflict() const { + return type_ == Type::kConflict; + } + + // Returns true if this is a method that must become an abstract method. + bool IsAbstract() const { + return type_ == Type::kAbstract; + } + + // Returns true if this is a method that must become a different method. + bool IsTranslation() const { + return type_ == Type::kTranslation; + } + + // Get the translated version of this method. + ArtMethod* GetTranslation() const { + DCHECK(IsTranslation()); + DCHECK(translation_ != nullptr); + return translation_; + } + + private: + enum class Type { + kTranslation, + kConflict, + kAbstract, + }; + + MethodTranslation(Type type, ArtMethod* translation) + : translation_(translation), type_(type) {} + + ArtMethod* const translation_; + const Type type_; + }; + // Links the virtual methods for the given class and records any default methods that will need to // be updated later. // @@ -737,9 +814,10 @@ class ClassLinker { // scan, we therefore store the vtable index's that might need to be // updated with the method they will turn into. // TODO This whole default_translations thing is very dirty. There should be a better way. - bool LinkVirtualMethods(Thread* self, - Handle<mirror::Class> klass, - /*out*/std::unordered_map<size_t, ArtMethod*>* default_translations) + bool LinkVirtualMethods( + Thread* self, + Handle<mirror::Class> klass, + /*out*/std::unordered_map<size_t, MethodTranslation>* default_translations) SHARED_REQUIRES(Locks::mutator_lock_); // Sets up the interface lookup table (IFTable) in the correct order to allow searching for @@ -749,6 +827,13 @@ class ClassLinker { Handle<mirror::ObjectArray<mirror::Class>> interfaces) SHARED_REQUIRES(Locks::mutator_lock_); + + enum class DefaultMethodSearchResult { + kDefaultFound, + kAbstractFound, + kDefaultConflict + }; + // Find the default method implementation for 'interface_method' in 'klass', if one exists. // // Arguments: @@ -756,31 +841,31 @@ class ClassLinker { // * target_method - The method we are trying to find a default implementation for. // * klass - The class we are searching for a definition of target_method. // * out_default_method - The pointer we will store the found default method to on success. - // * icce_message - A string we will store an appropriate IncompatibleClassChangeError message - // into in case of failure. Note we must do it this way since we do not know - // whether we can allocate the exception object, which could cause us to go to - // sleep. // // Return value: - // * True - There were no conflicting method implementations found in the class while searching - // for target_method. The default method implementation is stored into out_default_method - // if it was found. Otherwise *out_default_method will be set to nullptr. - // * False - Conflicting method implementations were found when searching for target_method. The - // value of *out_default_method is undefined and *icce_message is a string that should - // be used to create an IncompatibleClassChangeError as soon as possible. - bool FindDefaultMethodImplementation(Thread* self, - ArtMethod* target_method, - Handle<mirror::Class> klass, - /*out*/ArtMethod** out_default_method, - /*out*/std::string* icce_message) const + // * kDefaultFound - There were no conflicting method implementations found in the class while + // searching for target_method. The default method implementation is stored into + // out_default_method. + // * kAbstractFound - There were no conflicting method implementations found in the class while + // searching for target_method but no default implementation was found either. + // out_default_method is set to null and the method should be considered not + // implemented. + // * kDefaultConflict - Conflicting method implementations were found when searching for + // target_method. The value of *out_default_method is null. + DefaultMethodSearchResult FindDefaultMethodImplementation( + Thread* self, + ArtMethod* target_method, + Handle<mirror::Class> klass, + /*out*/ArtMethod** out_default_method) const SHARED_REQUIRES(Locks::mutator_lock_); // Sets the imt entries and fixes up the vtable for the given class by linking all the interface // methods. See LinkVirtualMethods for an explanation of what default_translations is. - bool LinkInterfaceMethods(Thread* self, - Handle<mirror::Class> klass, - const std::unordered_map<size_t, ArtMethod*>& default_translations, - ArtMethod** out_imt) + bool LinkInterfaceMethods( + Thread* self, + Handle<mirror::Class> klass, + const std::unordered_map<size_t, MethodTranslation>& default_translations, + ArtMethod** out_imt) SHARED_REQUIRES(Locks::mutator_lock_); bool LinkStaticFields(Thread* self, Handle<mirror::Class> klass, size_t* class_size) diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc index de692d1368..d68b463950 100644 --- a/runtime/common_throws.cc +++ b/runtime/common_throws.cc @@ -242,6 +242,15 @@ void ThrowIncompatibleClassChangeError(mirror::Class* referrer, const char* fmt, va_end(args); } +void ThrowIncompatibleClassChangeErrorForMethodConflict(ArtMethod* method) { + DCHECK(method != nullptr); + ThrowException("Ljava/lang/IncompatibleClassChangeError;", + /*referrer*/nullptr, + StringPrintf("Conflicting default method implementations %s", + PrettyMethod(method).c_str()).c_str()); +} + + // IOException void ThrowIOException(const char* fmt, ...) { diff --git a/runtime/common_throws.h b/runtime/common_throws.h index 2402e6f7a0..2a0934fb5b 100644 --- a/runtime/common_throws.h +++ b/runtime/common_throws.h @@ -120,6 +120,9 @@ void ThrowIncompatibleClassChangeError(mirror::Class* referrer, const char* fmt, __attribute__((__format__(__printf__, 2, 3))) SHARED_REQUIRES(Locks::mutator_lock_) COLD_ATTR; +void ThrowIncompatibleClassChangeErrorForMethodConflict(ArtMethod* method) + SHARED_REQUIRES(Locks::mutator_lock_) COLD_ATTR; + // IOException void ThrowIOException(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2))) diff --git a/runtime/dex_file.h b/runtime/dex_file.h index e7877b2e78..1e44f509f1 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -722,6 +722,7 @@ class DexFile { // const CodeItem* GetCodeItem(const uint32_t code_off) const { + DCHECK_LT(code_off, size_) << "Code item offset larger then maximum allowed offset"; if (code_off == 0) { return nullptr; // native or abstract method } else { diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 5eda6d6bd3..abf9ac49e6 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -645,8 +645,8 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, // frame. ScopedQuickEntrypointChecks sqec(self); - if (method->IsAbstract()) { - ThrowAbstractMethodError(method); + if (UNLIKELY(!method->IsInvokable())) { + method->ThrowInvocationTimeError(); return 0; } diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 0bc9dd8375..bc2c197d33 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -108,7 +108,7 @@ static void UpdateEntrypoints(ArtMethod* method, const void* quick_code) } void Instrumentation::InstallStubsForMethod(ArtMethod* method) { - if (method->IsAbstract() || method->IsProxyMethod()) { + if (!method->IsInvokable() || method->IsProxyMethod()) { // Do not change stubs for these methods. return; } @@ -734,7 +734,7 @@ bool Instrumentation::IsDeoptimizedMethodsEmpty() const { void Instrumentation::Deoptimize(ArtMethod* method) { CHECK(!method->IsNative()); CHECK(!method->IsProxyMethod()); - CHECK(!method->IsAbstract()); + CHECK(method->IsInvokable()); Thread* self = Thread::Current(); { @@ -757,7 +757,7 @@ void Instrumentation::Deoptimize(ArtMethod* method) { void Instrumentation::Undeoptimize(ArtMethod* method) { CHECK(!method->IsNative()); CHECK(!method->IsProxyMethod()); - CHECK(!method->IsAbstract()); + CHECK(method->IsInvokable()); Thread* self = Thread::Current(); bool empty; diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index 7c0594a8bb..d686f749f3 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -262,7 +262,7 @@ static JValue Execute(Thread* self, const DexFile::CodeItem* code_item, ShadowFr static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame& shadow_frame, JValue result_register) { - DCHECK(!shadow_frame.GetMethod()->IsAbstract()); + DCHECK(shadow_frame.GetMethod()->IsInvokable()); DCHECK(!shadow_frame.GetMethod()->IsNative()); shadow_frame.GetMethod()->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self); @@ -318,9 +318,9 @@ void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receive if (code_item != nullptr) { num_regs = code_item->registers_size_; num_ins = code_item->ins_size_; - } else if (method->IsAbstract()) { + } else if (!method->IsInvokable()) { self->EndAssertNoThreadSuspension(old_cause); - ThrowAbstractMethodError(method); + method->ThrowInvocationTimeError(); return; } else { DCHECK(method->IsNative()); diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index c8650c4238..9f6699f730 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -141,8 +141,9 @@ static inline bool IsValidLambdaTargetOrThrow(ArtMethod* called_method) if (UNLIKELY(called_method == nullptr)) { // The shadow frame should already be pushed, so we don't need to update it. - } else if (UNLIKELY(called_method->IsAbstract())) { - ThrowAbstractMethodError(called_method); + } else if (UNLIKELY(!called_method->IsInvokable())) { + called_method->ThrowInvocationTimeError(); + // We got an error. // TODO(iam): Also handle the case when the method is non-static, what error do we throw? // TODO(iam): Also make sure that ACC_LAMBDA is set. } else if (UNLIKELY(called_method->GetCodeItem() == nullptr)) { @@ -617,8 +618,8 @@ static inline bool DoInvoke(Thread* self, ShadowFrame& shadow_frame, const Instr CHECK(self->IsExceptionPending()); result->SetJ(0); return false; - } else if (UNLIKELY(called_method->IsAbstract())) { - ThrowAbstractMethodError(called_method); + } else if (UNLIKELY(!called_method->IsInvokable())) { + called_method->ThrowInvocationTimeError(); result->SetJ(0); return false; } else { @@ -656,8 +657,8 @@ static inline bool DoInvokeVirtualQuick(Thread* self, ShadowFrame& shadow_frame, CHECK(self->IsExceptionPending()); result->SetJ(0); return false; - } else if (UNLIKELY(called_method->IsAbstract())) { - ThrowAbstractMethodError(called_method); + } else if (UNLIKELY(!called_method->IsInvokable())) { + called_method->ThrowInvocationTimeError(); result->SetJ(0); return false; } else { diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc index 91e1cecbdf..3590586228 100644 --- a/runtime/mirror/class.cc +++ b/runtime/mirror/class.cc @@ -726,12 +726,12 @@ ArtField* Class::FindField(Thread* self, Handle<Class> klass, const StringPiece& void Class::SetPreverifiedFlagOnAllMethods(size_t pointer_size) { DCHECK(IsVerified()); for (auto& m : GetDirectMethods(pointer_size)) { - if (!m.IsNative() && !m.IsAbstract()) { + if (!m.IsNative() && m.IsInvokable()) { m.SetPreverified(); } } for (auto& m : GetVirtualMethods(pointer_size)) { - if (!m.IsNative() && !m.IsAbstract()) { + if (!m.IsNative() && m.IsInvokable()) { m.SetPreverified(); } } diff --git a/runtime/modifiers.h b/runtime/modifiers.h index 116cbe9254..9946eabc82 100644 --- a/runtime/modifiers.h +++ b/runtime/modifiers.h @@ -50,6 +50,10 @@ static constexpr uint32_t kAccPreverified = 0x00080000; // class (runt static constexpr uint32_t kAccFastNative = 0x00080000; // method (dex only) static constexpr uint32_t kAccMiranda = 0x00200000; // method (dex only) static constexpr uint32_t kAccDefault = 0x00400000; // method (runtime) +// This is set by the class linker during LinkInterfaceMethods. Prior to that point we do not know +// if any particular method needs to be a default conflict. Used to figure out at runtime if +// invoking this method will throw an exception. +static constexpr uint32_t kAccDefaultConflict = 0x00800000; // method (runtime) // Special runtime-only flags. // Interface and all its super-interfaces with default methods have been recursively initialized. diff --git a/test/966-default-conflict/build b/test/966-default-conflict/build new file mode 100755 index 0000000000..e66e8409c6 --- /dev/null +++ b/test/966-default-conflict/build @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Copyright 2015 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. + +# make us exit on a failure +set -e + +# TODO: Support running with jack. + +if [[ $@ == *"--jvm"* ]]; then + # Build the Java files if we are running a --jvm test + mkdir -p src + mkdir -p classes + ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src + # Build with the non-conflicting version + ${JAVAC} -implicit:none -d classes src/Iface.java build-src/Iface2.java src/Main.java + rm classes/Iface2.class + # Build with the conflicting version + ${JAVAC} -implicit:none -cp classes -d classes src/Iface2.java +else + ./default-build "$@" --experimental default-methods +fi diff --git a/test/966-default-conflict/build-src/Iface2.java b/test/966-default-conflict/build-src/Iface2.java new file mode 100644 index 0000000000..8d97df8385 --- /dev/null +++ b/test/966-default-conflict/build-src/Iface2.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 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. + */ + +// We extend Iface so that javac will not complain that Iface2 does not declare a sayHi method or +// has a soft-conflict on the sayHi method if it did. +public interface Iface2 extends Iface { + // public default String sayHi() { + // return "hello"; + // } +} + + diff --git a/test/966-default-conflict/expected.txt b/test/966-default-conflict/expected.txt new file mode 100644 index 0000000000..fad2c25979 --- /dev/null +++ b/test/966-default-conflict/expected.txt @@ -0,0 +1,18 @@ +Create Main instance +Calling functions on concrete Main +Calling non-conflicting function on Main +CHARGE +Calling conflicting function on Main +Expected ICCE Thrown on Main +Calling non-conflicting function on Main +CHARGE +Calling functions on interface Iface +Calling non-conflicting function on Iface +CHARGE +Calling conflicting function on Iface +Expected ICCE Thrown on Iface +Calling non-conflicting function on Iface +CHARGE +Calling functions on interface Iface2 +Calling conflicting function on Iface2 +Expected ICCE Thrown on Iface2 diff --git a/test/966-default-conflict/info.txt b/test/966-default-conflict/info.txt new file mode 100644 index 0000000000..2b67657dcc --- /dev/null +++ b/test/966-default-conflict/info.txt @@ -0,0 +1,6 @@ +Smali-based tests for experimental interface static methods. + +Tests handling of default method conflicts. + +To run with --jvm you must export JAVA_HOME to a Java 8 Language installation +and pass the --use-java-home to run-test diff --git a/test/966-default-conflict/run b/test/966-default-conflict/run new file mode 100755 index 0000000000..8944ea92d3 --- /dev/null +++ b/test/966-default-conflict/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2015 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. + +${RUN} "$@" --experimental default-methods diff --git a/test/966-default-conflict/smali/Iface.smali b/test/966-default-conflict/smali/Iface.smali new file mode 100644 index 0000000000..e996b3a4f4 --- /dev/null +++ b/test/966-default-conflict/smali/Iface.smali @@ -0,0 +1,39 @@ +# /* +# * Copyright (C) 2015 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 Iface { +# public default String sayHi() { +# return "Hi"; +# } +# public default String charge() { +# return "CHARGE"; +# } +# } + +.class public abstract interface LIface; +.super Ljava/lang/Object; + +.method public sayHi()Ljava/lang/String; + .locals 1 + const-string v0, "Hi" + return-object v0 +.end method + +.method public charge()Ljava/lang/String; + .locals 1 + const-string v0, "CHARGE" + return-object v0 +.end method diff --git a/test/966-default-conflict/smali/Iface2.smali b/test/966-default-conflict/smali/Iface2.smali new file mode 100644 index 0000000000..82fa547dea --- /dev/null +++ b/test/966-default-conflict/smali/Iface2.smali @@ -0,0 +1,31 @@ +# /* +# * Copyright (C) 2015 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 Iface2 { +# public default String sayHi() { +# return "hello"; +# } +# } + +.class public abstract interface LIface2; +.super Ljava/lang/Object; + +.method public sayHi()Ljava/lang/String; + .locals 1 + const-string v0, "hello" + return-object v0 +.end method + diff --git a/test/966-default-conflict/smali/Main.smali b/test/966-default-conflict/smali/Main.smali new file mode 100644 index 0000000000..ce974d8135 --- /dev/null +++ b/test/966-default-conflict/smali/Main.smali @@ -0,0 +1,227 @@ +# /* +# * Copyright (C) 2015 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 Main implements Iface, Iface2 { +# public static void main(String[] args) { +# System.out.println("Create Main instance"); +# Main m = new Main(); +# System.out.println("Calling functions on concrete Main"); +# callMain(m); +# System.out.println("Calling functions on interface Iface"); +# callIface(m); +# System.out.println("Calling functions on interface Iface2"); +# callIface2(m); +# } +# +# public static void callMain(Main m) { +# System.out.println("Calling non-conflicting function on Main"); +# System.out.println(m.charge()); +# System.out.println("Calling conflicting function on Main"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Main"); +# } catch (AbstractMethodError e) { +# System.out.println("Unexpected AME Thrown on Main"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Expected ICCE Thrown on Main"); +# } +# System.out.println("Calling non-conflicting function on Main"); +# System.out.println(m.charge()); +# return; +# } +# +# public static void callIface(Iface m) { +# System.out.println("Calling non-conflicting function on Iface"); +# System.out.println(m.charge()); +# System.out.println("Calling conflicting function on Iface"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Iface"); +# } catch (AbstractMethodError e) { +# System.out.println("Unexpected AME Thrown on Iface"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Expected ICCE Thrown on Iface"); +# } +# System.out.println("Calling non-conflicting function on Iface"); +# System.out.println(m.charge()); +# return; +# } +# +# public static void callIface2(Iface2 m) { +# System.out.println("Calling conflicting function on Iface2"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Iface2"); +# } catch (AbstractMethodError e) { +# System.out.println("Unexpected AME Thrown on Iface2"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Expected ICCE Thrown on Iface2"); +# } +# return; +# } +# } + +.class public LMain; +.super Ljava/lang/Object; +.implements LIface; +.implements LIface2; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 3 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + + const-string v0, "Create Main instance" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + new-instance v2, LMain; + invoke-direct {v2}, LMain;-><init>()V + + const-string v0, "Calling functions on concrete Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callMain(LMain;)V + + const-string v0, "Calling functions on interface Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface(LIface;)V + + const-string v0, "Calling functions on interface Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface2(LIface2;)V + + return-void +.end method + +.method public static callIface(LIface;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling non-conflicting function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling conflicting function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Unexpected AME Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Expected ICCE Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + const-string v0, "Calling non-conflicting function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method + +.method public static callIface2(LIface2;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling conflicting function on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface2;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Unexpected AME Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Expected ICCE Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + + return-void +.end method + +.method public static callMain(LMain;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling non-conflicting function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling conflicting function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-virtual {p0}, LMain;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Unexpected AME Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Expected ICCE Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + const-string v0, "Calling non-conflicting function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method diff --git a/test/967-default-ame/build b/test/967-default-ame/build new file mode 100755 index 0000000000..53001a9ad2 --- /dev/null +++ b/test/967-default-ame/build @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Copyright 2015 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. + +# make us exit on a failure +set -e + +# TODO: Support running with jack. + +if [[ $@ == *"--jvm"* ]]; then + # Build the Java files if we are running a --jvm test + mkdir -p src + mkdir -p classes + ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src + # Build with the non-conflicting version + ${JAVAC} -implicit:none -d classes src/Iface.java build-src/Iface2.java build-src/Iface3.java src/Main.java + rm classes/Iface2.class + rm classes/Iface3.class + # Build with the conflicting version + ${JAVAC} -implicit:none -cp classes -d classes src/Iface2.java src/Iface3.java +else + ./default-build "$@" --experimental default-methods +fi diff --git a/test/967-default-ame/build-src/Iface2.java b/test/967-default-ame/build-src/Iface2.java new file mode 100644 index 0000000000..55b2ac01b0 --- /dev/null +++ b/test/967-default-ame/build-src/Iface2.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 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 Iface2 extends Iface { + // public String sayHi(); +} + + diff --git a/test/967-default-ame/build-src/Iface3.java b/test/967-default-ame/build-src/Iface3.java new file mode 100644 index 0000000000..a6faa451e5 --- /dev/null +++ b/test/967-default-ame/build-src/Iface3.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 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 Iface3 { + // public String charge(); +} diff --git a/test/967-default-ame/expected.txt b/test/967-default-ame/expected.txt new file mode 100644 index 0000000000..cbd4ad32eb --- /dev/null +++ b/test/967-default-ame/expected.txt @@ -0,0 +1,18 @@ +Create Main instance +Calling functions on concrete Main +Calling non-abstract function on Main +CHARGE +Calling abstract function on Main +Expected AME Thrown on Main +Calling non-abstract function on Main +CHARGE +Calling functions on interface Iface +Calling non-abstract function on Iface +CHARGE +Calling abstract function on Iface +Expected AME Thrown on Iface +Calling non-abstract function on Iface +CHARGE +Calling functions on interface Iface2 +Calling abstract function on Iface2 +Expected AME Thrown on Iface2 diff --git a/test/967-default-ame/info.txt b/test/967-default-ame/info.txt new file mode 100644 index 0000000000..a346a322ec --- /dev/null +++ b/test/967-default-ame/info.txt @@ -0,0 +1,6 @@ +Smali-based tests for experimental interface static methods. + +Tests handling of default method overrides. + +To run with --jvm you must export JAVA_HOME to a Java 8 Language installation +and pass the --use-java-home to run-test diff --git a/test/967-default-ame/run b/test/967-default-ame/run new file mode 100755 index 0000000000..8944ea92d3 --- /dev/null +++ b/test/967-default-ame/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2015 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. + +${RUN} "$@" --experimental default-methods diff --git a/test/967-default-ame/smali/Iface.smali b/test/967-default-ame/smali/Iface.smali new file mode 100644 index 0000000000..e996b3a4f4 --- /dev/null +++ b/test/967-default-ame/smali/Iface.smali @@ -0,0 +1,39 @@ +# /* +# * Copyright (C) 2015 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 Iface { +# public default String sayHi() { +# return "Hi"; +# } +# public default String charge() { +# return "CHARGE"; +# } +# } + +.class public abstract interface LIface; +.super Ljava/lang/Object; + +.method public sayHi()Ljava/lang/String; + .locals 1 + const-string v0, "Hi" + return-object v0 +.end method + +.method public charge()Ljava/lang/String; + .locals 1 + const-string v0, "CHARGE" + return-object v0 +.end method diff --git a/test/967-default-ame/smali/Iface2.smali b/test/967-default-ame/smali/Iface2.smali new file mode 100644 index 0000000000..a21a8ddbc7 --- /dev/null +++ b/test/967-default-ame/smali/Iface2.smali @@ -0,0 +1,27 @@ +# /* +# * Copyright (C) 2015 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 Iface2 extends Iface { +# public String sayHi(); +# } + +.class public abstract interface LIface2; +.super Ljava/lang/Object; +.implements LIface; + +.method public abstract sayHi()Ljava/lang/String; +.end method + diff --git a/test/967-default-ame/smali/Iface3.smali b/test/967-default-ame/smali/Iface3.smali new file mode 100644 index 0000000000..874e96d069 --- /dev/null +++ b/test/967-default-ame/smali/Iface3.smali @@ -0,0 +1,26 @@ +# /* +# * Copyright (C) 2015 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 Iface3 { +# public String charge(); +# } + +.class public abstract interface LIface3; +.super Ljava/lang/Object; + +.method public abstract charge()Ljava/lang/String; +.end method + diff --git a/test/967-default-ame/smali/Main.smali b/test/967-default-ame/smali/Main.smali new file mode 100644 index 0000000000..e4d63cfa24 --- /dev/null +++ b/test/967-default-ame/smali/Main.smali @@ -0,0 +1,228 @@ +# /* +# * Copyright (C) 2015 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 Main implements Iface, Iface2, Iface3 { +# public static void main(String[] args) { +# System.out.println("Create Main instance"); +# Main m = new Main(); +# System.out.println("Calling functions on concrete Main"); +# callMain(m); +# System.out.println("Calling functions on interface Iface"); +# callIface(m); +# System.out.println("Calling functions on interface Iface2"); +# callIface2(m); +# } +# +# public static void callMain(Main m) { +# System.out.println("Calling non-abstract function on Main"); +# System.out.println(m.charge()); +# System.out.println("Calling abstract function on Main"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Main"); +# } catch (AbstractMethodError e) { +# System.out.println("Expected AME Thrown on Main"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Unexpected ICCE Thrown on Main"); +# } +# System.out.println("Calling non-abstract function on Main"); +# System.out.println(m.charge()); +# return; +# } +# +# public static void callIface(Iface m) { +# System.out.println("Calling non-abstract function on Iface"); +# System.out.println(m.charge()); +# System.out.println("Calling abstract function on Iface"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Iface"); +# } catch (AbstractMethodError e) { +# System.out.println("Expected AME Thrown on Iface"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Unexpected ICCE Thrown on Iface"); +# } +# System.out.println("Calling non-abstract function on Iface"); +# System.out.println(m.charge()); +# return; +# } +# +# public static void callIface2(Iface2 m) { +# System.out.println("Calling abstract function on Iface2"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Iface2"); +# } catch (AbstractMethodError e) { +# System.out.println("Expected AME Thrown on Iface2"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Unexpected ICCE Thrown on Iface2"); +# } +# return; +# } +# } + +.class public LMain; +.super Ljava/lang/Object; +.implements LIface; +.implements LIface2; +.implements LIface3; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 3 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + + const-string v0, "Create Main instance" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + new-instance v2, LMain; + invoke-direct {v2}, LMain;-><init>()V + + const-string v0, "Calling functions on concrete Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callMain(LMain;)V + + const-string v0, "Calling functions on interface Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface(LIface;)V + + const-string v0, "Calling functions on interface Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface2(LIface2;)V + + return-void +.end method + +.method public static callIface(LIface;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling non-abstract function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling abstract function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Expected AME Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Unexpected ICCE Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + const-string v0, "Calling non-abstract function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method + +.method public static callIface2(LIface2;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling abstract function on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface2;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Expected AME Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Unexpected ICCE Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + + return-void +.end method + +.method public static callMain(LMain;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling non-abstract function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling abstract function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-virtual {p0}, LMain;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Expected AME Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Unexpected ICCE Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + const-string v0, "Calling non-abstract function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method diff --git a/test/968-default-partial-compilation-generated/build b/test/968-default-partial-compilation-generated/build new file mode 100755 index 0000000000..1e9f8aadd5 --- /dev/null +++ b/test/968-default-partial-compilation-generated/build @@ -0,0 +1,50 @@ +#!/bin/bash +# +# Copyright 2015 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. + +# make us exit on a failure +set -e + +# We will be making more files than the ulimit is set to allow. Remove it temporarily. +OLD_ULIMIT=`ulimit -S` +ulimit -S unlimited + +restore_ulimit() { + ulimit -S "$OLD_ULIMIT" +} +trap 'restore_ulimit' ERR + +# TODO: Support running with jack. + +if [[ $@ == *"--jvm"* ]]; then + # Build the Java files if we are running a --jvm test + mkdir -p classes + mkdir -p src + echo "${JAVAC} \$@" >> ./javac_exec.sh + # This will use java_exec.sh to execute the javac compiler. It will place the + # compiled class files in ./classes and the expected values in expected.txt + # + # After this the src directory will contain the final versions of all files. + ./util-src/generate_java.py ./javac_exec.sh ./src ./classes ./expected.txt ./build_log +else + mkdir -p ./smali + # Generate the smali files and expected.txt or fail + ./util-src/generate_smali.py ./smali ./expected.txt + # Use the default build script + ./default-build "$@" "$EXTRA_ARGS" --experimental default-methods +fi + +# Reset the ulimit back to its initial value +restore_ulimit diff --git a/test/968-default-partial-compilation-generated/expected.txt b/test/968-default-partial-compilation-generated/expected.txt new file mode 100644 index 0000000000..1ddd65d177 --- /dev/null +++ b/test/968-default-partial-compilation-generated/expected.txt @@ -0,0 +1 @@ +This file is generated by util-src/generate_smali.py do not directly modify! diff --git a/test/968-default-partial-compilation-generated/info.txt b/test/968-default-partial-compilation-generated/info.txt new file mode 100644 index 0000000000..bc1c42816e --- /dev/null +++ b/test/968-default-partial-compilation-generated/info.txt @@ -0,0 +1,17 @@ +Smali-based tests for experimental interface default methods. + +This tests that interface method resolution order is correct in the presence of +partial compilation/illegal invokes. + +Obviously needs to run under ART or a Java 8 Language runtime and compiler. + +When run smali test files are generated by the util-src/generate_smali.py +script. If we run with --jvm we will use the util-src/generate_java.py script +will generate equivalent java code based on the smali code. + +Care should be taken when updating the generate_smali.py script. It should always +return equivalent output when run multiple times and the expected output should +be valid. + +Do not modify the expected.txt file. It is generated on each run by +util-src/generate_smali.py. diff --git a/test/968-default-partial-compilation-generated/run b/test/968-default-partial-compilation-generated/run new file mode 100755 index 0000000000..6d2930d463 --- /dev/null +++ b/test/968-default-partial-compilation-generated/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2015 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. + +${RUN} "$@" --experimental default-methods diff --git a/test/968-default-partial-compilation-generated/util-src/generate_java.py b/test/968-default-partial-compilation-generated/util-src/generate_java.py new file mode 100755 index 0000000000..35290efe1d --- /dev/null +++ b/test/968-default-partial-compilation-generated/util-src/generate_java.py @@ -0,0 +1,134 @@ +#!/usr/bin/python3 +# +# Copyright (C) 2015 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. + +""" +Generate java test files for test 966. +""" + +import generate_smali as base +import os +import sys +from pathlib import Path + +BUILD_TOP = os.getenv("ANDROID_BUILD_TOP") +if BUILD_TOP is None: + print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr) + sys.exit(1) + +# Allow us to import mixins. +sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python")) + +import testgen.mixins as mixins +import functools +import operator +import subprocess + +class JavaConverter(mixins.DumpMixin, mixins.Named, mixins.JavaFileMixin): + """ + A class that can convert a SmaliFile to a JavaFile. + """ + def __init__(self, inner): + self.inner = inner + + def get_name(self): + """Gets the name of this file.""" + return self.inner.get_name() + + def __str__(self): + out = "" + for line in str(self.inner).splitlines(keepends = True): + if line.startswith("#"): + out += line[1:] + return out + +class Compiler: + def __init__(self, sources, javac, temp_dir, classes_dir): + self.javac = javac + self.temp_dir = temp_dir + self.classes_dir = classes_dir + self.sources = sources + + def compile_files(self, args, files): + """ + Compile the files given with the arguments given. + """ + args = args.split() + files = list(map(str, files)) + cmd = ['sh', '-a', '-e', '--', str(self.javac)] + args + files + print("Running compile command: {}".format(cmd)) + subprocess.check_call(cmd) + print("Compiled {} files".format(len(files))) + + def execute(self): + """ + Compiles this test, doing partial compilation as necessary. + """ + # Compile Main and all classes first. Force all interfaces to be default so that there will be + # no compiler problems (works since classes only implement 1 interface). + for f in self.sources: + if isinstance(f, base.TestInterface): + JavaConverter(f.get_specific_version(base.InterfaceType.default)).dump(self.temp_dir) + else: + JavaConverter(f).dump(self.temp_dir) + self.compile_files("-d {}".format(self.classes_dir), self.temp_dir.glob("*.java")) + + # Now we compile the interfaces + ifaces = set(i for i in self.sources if isinstance(i, base.TestInterface)) + while len(ifaces) != 0: + # Find those ifaces where there are no (uncompiled) interfaces that are subtypes. + tops = set(filter(lambda a: not any(map(lambda i: a in i.get_super_types(), ifaces)), ifaces)) + files = [] + # Dump these ones, they are getting compiled. + for f in tops: + out = JavaConverter(f) + out.dump(self.temp_dir) + files.append(self.temp_dir / out.get_file_name()) + # Force all superinterfaces of these to be empty so there will be no conflicts + overrides = functools.reduce(operator.or_, map(lambda i: i.get_super_types(), tops), set()) + for overridden in overrides: + out = JavaConverter(overridden.get_specific_version(base.InterfaceType.empty)) + out.dump(self.temp_dir) + files.append(self.temp_dir / out.get_file_name()) + self.compile_files("-d {outdir} -cp {outdir}".format(outdir = self.classes_dir), files) + # Remove these from the set of interfaces to be compiled. + ifaces -= tops + print("Finished compiling all files.") + return + +def main(argv): + javac_exec = Path(argv[1]) + if not javac_exec.exists() or not javac_exec.is_file(): + print("{} is not a shell script".format(javac_exec), file=sys.stderr) + sys.exit(1) + temp_dir = Path(argv[2]) + if not temp_dir.exists() or not temp_dir.is_dir(): + print("{} is not a valid source dir".format(temp_dir), file=sys.stderr) + sys.exit(1) + classes_dir = Path(argv[3]) + if not classes_dir.exists() or not classes_dir.is_dir(): + print("{} is not a valid classes directory".format(classes_dir), file=sys.stderr) + sys.exit(1) + expected_txt = Path(argv[4]) + mainclass, all_files = base.create_all_test_files() + + with expected_txt.open('w') as out: + print(mainclass.get_expected(), file=out) + print("Wrote expected output") + + Compiler(all_files, javac_exec, temp_dir, classes_dir).execute() + +if __name__ == '__main__': + main(sys.argv) diff --git a/test/968-default-partial-compilation-generated/util-src/generate_smali.py b/test/968-default-partial-compilation-generated/util-src/generate_smali.py new file mode 100755 index 0000000000..9855bcf854 --- /dev/null +++ b/test/968-default-partial-compilation-generated/util-src/generate_smali.py @@ -0,0 +1,607 @@ +#!/usr/bin/python3 +# +# Copyright (C) 2015 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. + +""" +Generate Smali test files for test 967. +""" + +import os +import sys +from pathlib import Path + +BUILD_TOP = os.getenv("ANDROID_BUILD_TOP") +if BUILD_TOP is None: + print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr) + sys.exit(1) + +# Allow us to import utils and mixins. +sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python")) + +from testgen.utils import get_copyright, subtree_sizes, gensym, filter_blanks +import testgen.mixins as mixins + +from enum import Enum +from functools import total_ordering +import itertools +import string + +# The max depth the type tree can have. +MAX_IFACE_DEPTH = 3 + +class MainClass(mixins.DumpMixin, mixins.Named, mixins.SmaliFileMixin): + """ + A Main.smali file containing the Main class and the main function. It will run + all the test functions we have. + """ + + MAIN_CLASS_TEMPLATE = """{copyright} + +.class public LMain; +.super Ljava/lang/Object; + +# class Main {{ + +.method public constructor <init>()V + .registers 1 + invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V + return-void +.end method + +{test_funcs} + +{main_func} + +# }} +""" + + MAIN_FUNCTION_TEMPLATE = """ +# public static void main(String[] args) {{ +.method public static main([Ljava/lang/String;)V + .locals 0 + + {test_group_invoke} + + return-void +.end method +# }} +""" + + TEST_GROUP_INVOKE_TEMPLATE = """ +# {test_name}(); + invoke-static {{}}, {test_name}()V +""" + + def __init__(self): + """ + Initialize this MainClass. We start out with no tests. + """ + self.tests = set() + + def get_expected(self): + """ + Get the expected output of this test. + """ + all_tests = sorted(self.tests) + return filter_blanks("\n".join(a.get_expected() for a in all_tests)) + + def add_test(self, ty): + """ + Add a test for the concrete type 'ty' + """ + self.tests.add(Func(ty)) + + def get_name(self): + """ + Get the name of this class + """ + return "Main" + + def __str__(self): + """ + Print the MainClass smali code. + """ + all_tests = sorted(self.tests) + test_invoke = "" + test_funcs = "" + for t in all_tests: + test_funcs += str(t) + for t in all_tests: + test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name()) + main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke) + + return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright("smali"), + test_funcs = test_funcs, + main_func = main_func) + +class Func(mixins.Named, mixins.NameComparableMixin): + """ + A function that tests the functionality of a concrete type. Should only be + constructed by MainClass.add_test. + """ + + TEST_FUNCTION_TEMPLATE = """ +# public static void {fname}() {{ +# {farg} v = null; +# try {{ +# v = new {farg}(); +# }} catch (Throwable e) {{ +# System.out.println("Unexpected error occurred which creating {farg} instance"); +# e.printStackTrace(System.out); +# return; +# }} +# try {{ +# System.out.printf("{tree} calls %s\\n", v.getName()); +# return; +# }} catch (AbstractMethodError e) {{ +# System.out.println("{tree} threw AbstractMethodError"); +# }} catch (NoSuchMethodError e) {{ +# System.out.println("{tree} threw NoSuchMethodError"); +# }} catch (IncompatibleClassChangeError e) {{ +# System.out.println("{tree} threw IncompatibleClassChangeError"); +# }} catch (Throwable e) {{ +# e.printStackTrace(System.out); +# return; +# }} +# }} +.method public static {fname}()V + .locals 7 + sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream; + + :new_{fname}_try_start + new-instance v0, L{farg}; + invoke-direct {{v0}}, L{farg};-><init>()V + goto :call_{fname}_try_start + :new_{fname}_try_end + .catch Ljava/lang/Throwable; {{:new_{fname}_try_start .. :new_{fname}_try_end}} :new_error_{fname}_start + :new_error_{fname}_start + move-exception v6 + const-string v5, "Unexpected error occurred which creating {farg} instance" + invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-virtual {{v6,v4}}, Ljava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V + return-void + :call_{fname}_try_start + const/4 v1, 1 + new-array v2,v1, [Ljava/lang/Object; + const/4 v1, 0 + invoke-virtual {{v0}}, L{farg};->getName()Ljava/lang/String; + move-result-object v3 + aput-object v3,v2,v1 + + const-string v5, "{tree} calls %s\\n" + + invoke-virtual {{v4,v5,v2}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; + return-void + :call_{fname}_try_end + .catch Ljava/lang/AbstractMethodError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :AME_{fname}_start + .catch Ljava/lang/NoSuchMethodError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :NSME_{fname}_start + .catch Ljava/lang/IncompatibleClassChangeError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :ICCE_{fname}_start + .catch Ljava/lang/Throwable; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start + :AME_{fname}_start + const-string v5, "{tree} threw AbstractMethodError" + invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void + :NSME_{fname}_start + const-string v5, "{tree} threw NoSuchMethodError" + invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void + :ICCE_{fname}_start + const-string v5, "{tree} threw IncompatibleClassChangeError" + invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void + :error_{fname}_start + move-exception v6 + invoke-virtual {{v6,v4}}, Ljava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V + return-void +.end method +""" + + NSME_RESULT_TEMPLATE = "{tree} threw NoSuchMethodError" + ICCE_RESULT_TEMPLATE = "{tree} threw IncompatibleClassChangeError" + AME_RESULT_TEMPLATE = "{tree} threw AbstractMethodError" + NORMAL_RESULT_TEMPLATE = "{tree} calls {result}" + + def __init__(self, farg): + """ + Initialize a test function for the given argument + """ + self.farg = farg + + def get_expected(self): + """ + Get the expected output calling this function. + """ + exp = self.farg.get_called() + if exp.is_empty(): + return self.NSME_RESULT_TEMPLATE.format(tree = self.farg.get_tree()) + elif exp.is_abstract(): + return self.AME_RESULT_TEMPLATE.format(tree = self.farg.get_tree()) + elif exp.is_conflict(): + return self.ICCE_RESULT_TEMPLATE.format(tree = self.farg.get_tree()) + else: + assert exp.is_default() + return self.NORMAL_RESULT_TEMPLATE.format(tree = self.farg.get_tree(), + result = exp.get_tree()) + + def get_name(self): + """ + Get the name of this function + """ + return "TEST_FUNC_{}".format(self.farg.get_name()) + + def __str__(self): + """ + Print the smali code of this function. + """ + return self.TEST_FUNCTION_TEMPLATE.format(tree = self.farg.get_tree(), + fname = self.get_name(), + farg = self.farg.get_name()) + +class TestClass(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin): + """ + A class that will be instantiated to test default method resolution order. + """ + + TEST_CLASS_TEMPLATE = """{copyright} + +.class public L{class_name}; +.super Ljava/lang/Object; +.implements L{iface_name}; + +# public class {class_name} implements {iface_name} {{ + +.method public constructor <init>()V + .registers 1 + invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V + return-void +.end method + +{funcs} + +# }} +""" + + def __init__(self, iface): + """ + Initialize this test class which implements the given interface + """ + self.iface = iface + self.class_name = "CLASS_"+gensym() + + def get_name(self): + """ + Get the name of this class + """ + return self.class_name + + def get_tree(self): + """ + Print out a representation of the type tree of this class + """ + return "[{class_name} {iface_tree}]".format(class_name = self.class_name, + iface_tree = self.iface.get_tree()) + + def __iter__(self): + """ + Step through all interfaces implemented transitively by this class + """ + yield self.iface + yield from self.iface + + def get_called(self): + """ + Returns the interface that will be called when the method on this class is invoked or + CONFLICT_TYPE if there is no interface that will be called. + """ + return self.iface.get_called() + + def __str__(self): + """ + Print the smali code of this class. + """ + return self.TEST_CLASS_TEMPLATE.format(copyright = get_copyright('smali'), + iface_name = self.iface.get_name(), + tree = self.get_tree(), + class_name = self.class_name, + funcs = "") + +class InterfaceType(Enum): + """ + An enumeration of all the different types of interfaces we can have. + + default: It has a default method + abstract: It has a method declared but not defined + empty: It does not have the method + """ + default = 0 + abstract = 1 + empty = 2 + + def get_suffix(self): + if self == InterfaceType.default: + return "_DEFAULT" + elif self == InterfaceType.abstract: + return "_ABSTRACT" + elif self == InterfaceType.empty: + return "_EMPTY" + else: + raise TypeError("Interface type had illegal value.") + +class ConflictInterface: + """ + A singleton representing a conflict of default methods. + """ + + def is_conflict(self): + """ + Returns true if this is a conflict interface and calling the method on this interface will + result in an IncompatibleClassChangeError. + """ + return True + + def is_abstract(self): + """ + Returns true if this is an abstract interface and calling the method on this interface will + result in an AbstractMethodError. + """ + return False + + def is_empty(self): + """ + Returns true if this is an abstract interface and calling the method on this interface will + result in a NoSuchMethodError. + """ + return False + + def is_default(self): + """ + Returns true if this is a default interface and calling the method on this interface will + result in a method actually being called. + """ + return False + +CONFLICT_TYPE = ConflictInterface() + +class TestInterface(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin): + """ + An interface that will be used to test default method resolution order. + """ + + TEST_INTERFACE_TEMPLATE = """{copyright} +.class public abstract interface L{class_name}; +.super Ljava/lang/Object; +{implements_spec} + +# public interface {class_name} {extends} {ifaces} {{ + +{funcs} + +# }} +""" + + DEFAULT_FUNC_TEMPLATE = """ +# public default String getName() {{ +# return "{tree}"; +# }} +.method public getName()Ljava/lang/String; + .locals 1 + const-string v0, "{tree}" + return-object v0 +.end method +""" + + ABSTRACT_FUNC_TEMPLATE = """ +# public String getName(); +.method public abstract getName()Ljava/lang/String; +.end method +""" + + EMPTY_FUNC_TEMPLATE = """""" + + IMPLEMENTS_TEMPLATE = """ +.implements L{iface_name}; +""" + + def __init__(self, ifaces, iface_type, full_name = None): + """ + Initialize interface with the given super-interfaces + """ + self.ifaces = sorted(ifaces) + self.iface_type = iface_type + if full_name is None: + end = self.iface_type.get_suffix() + self.class_name = "INTERFACE_"+gensym()+end + else: + self.class_name = full_name + + def get_specific_version(self, v): + """ + Returns a copy of this interface of the given type for use in partial compilation. + """ + return TestInterface(self.ifaces, v, full_name = self.class_name) + + def get_super_types(self): + """ + Returns a set of all the supertypes of this interface + """ + return set(i2 for i2 in self) + + def is_conflict(self): + """ + Returns true if this is a conflict interface and calling the method on this interface will + result in an IncompatibleClassChangeError. + """ + return False + + def is_abstract(self): + """ + Returns true if this is an abstract interface and calling the method on this interface will + result in an AbstractMethodError. + """ + return self.iface_type == InterfaceType.abstract + + def is_empty(self): + """ + Returns true if this is an abstract interface and calling the method on this interface will + result in a NoSuchMethodError. + """ + return self.iface_type == InterfaceType.empty + + def is_default(self): + """ + Returns true if this is a default interface and calling the method on this interface will + result in a method actually being called. + """ + return self.iface_type == InterfaceType.default + + def get_called(self): + """ + Returns the interface that will be called when the method on this class is invoked or + CONFLICT_TYPE if there is no interface that will be called. + """ + if not self.is_empty() or len(self.ifaces) == 0: + return self + else: + best = self + for super_iface in self.ifaces: + super_best = super_iface.get_called() + if super_best.is_conflict(): + return CONFLICT_TYPE + elif best.is_default(): + if super_best.is_default(): + return CONFLICT_TYPE + elif best.is_abstract(): + if super_best.is_default(): + best = super_best + else: + assert best.is_empty() + best = super_best + return best + + def get_name(self): + """ + Get the name of this class + """ + return self.class_name + + def get_tree(self): + """ + Print out a representation of the type tree of this class + """ + return "[{class_name} {iftree}]".format(class_name = self.get_name(), + iftree = print_tree(self.ifaces)) + + def __iter__(self): + """ + Performs depth-first traversal of the interface tree this interface is the + root of. Does not filter out repeats. + """ + for i in self.ifaces: + yield i + yield from i + + def __str__(self): + """ + Print the smali code of this interface. + """ + s_ifaces = " " + j_ifaces = " " + for i in self.ifaces: + s_ifaces += self.IMPLEMENTS_TEMPLATE.format(iface_name = i.get_name()) + j_ifaces += " {},".format(i.get_name()) + j_ifaces = j_ifaces[0:-1] + if self.is_default(): + funcs = self.DEFAULT_FUNC_TEMPLATE.format(tree = self.get_tree()) + elif self.is_abstract(): + funcs = self.ABSTRACT_FUNC_TEMPLATE.format() + else: + funcs = "" + return self.TEST_INTERFACE_TEMPLATE.format(copyright = get_copyright('smali'), + implements_spec = s_ifaces, + extends = "extends" if len(self.ifaces) else "", + ifaces = j_ifaces, + funcs = funcs, + tree = self.get_tree(), + class_name = self.class_name) + +def print_tree(ifaces): + """ + Prints a list of iface trees + """ + return " ".join(i.get_tree() for i in ifaces) + +# The deduplicated output of subtree_sizes for each size up to +# MAX_LEAF_IFACE_PER_OBJECT. +SUBTREES = [set(tuple(sorted(l)) for l in subtree_sizes(i)) + for i in range(MAX_IFACE_DEPTH + 1)] + +def create_test_classes(): + """ + Yield all the test classes with the different interface trees + """ + for num in range(1, MAX_IFACE_DEPTH + 1): + for iface in create_interface_trees(num): + yield TestClass(iface) + +def create_interface_trees(num): + """ + Yield all the interface trees up to 'num' depth. + """ + if num == 0: + for iftype in InterfaceType: + yield TestInterface(tuple(), iftype) + return + for split in SUBTREES[num]: + ifaces = [] + for sub in split: + ifaces.append(list(create_interface_trees(sub))) + yield TestInterface(tuple(), InterfaceType.default) + for supers in itertools.product(*ifaces): + for iftype in InterfaceType: + if iftype == InterfaceType.default: + # We can just stop at defaults. We have other tests that a default can override an + # abstract and this cuts down on the number of cases significantly, improving speed of + # this test. + continue + yield TestInterface(supers, iftype) + +def create_all_test_files(): + """ + Creates all the objects representing the files in this test. They just need to + be dumped. + """ + mc = MainClass() + classes = {mc} + for clazz in create_test_classes(): + classes.add(clazz) + for i in clazz: + classes.add(i) + mc.add_test(clazz) + return mc, classes + +def main(argv): + smali_dir = Path(argv[1]) + if not smali_dir.exists() or not smali_dir.is_dir(): + print("{} is not a valid smali dir".format(smali_dir), file=sys.stderr) + sys.exit(1) + expected_txt = Path(argv[2]) + mainclass, all_files = create_all_test_files() + with expected_txt.open('w') as out: + print(mainclass.get_expected(), file=out) + for f in all_files: + f.dump(smali_dir) + +if __name__ == '__main__': + main(sys.argv) diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 8744674a30..3a0bea3971 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -226,6 +226,7 @@ TEST_ART_PYTHON3_DEPENDENCY_RUN_TESTS := \ 960-default-smali \ 961-default-iface-resolution-generated \ 964-default-iface-init-generated \ + 968-default-partial-compilation-generated # Check if we have python3 to run our tests. ifeq ($(wildcard /usr/bin/python3),) diff --git a/test/utils/python/testgen/mixins.py b/test/utils/python/testgen/mixins.py index 085e51def2..aa8943baf3 100644 --- a/test/utils/python/testgen/mixins.py +++ b/test/utils/python/testgen/mixins.py @@ -79,6 +79,12 @@ class SmaliFileMixin(get_file_extension_mixin(".smali")): """ pass +class JavaFileMixin(get_file_extension_mixin(".java")): + """ + A mixin that defines that the file this class belongs to is get_name() + ".java". + """ + pass + class NameComparableMixin(object): """ A mixin that defines the object comparison and related functionality in terms |