summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/dex/verified_method.cc5
-rw-r--r--compiler/driver/compiler_driver-inl.h4
-rw-r--r--compiler/image_writer.cc6
-rw-r--r--compiler/optimizing/inliner.cc2
-rw-r--r--runtime/art_method.cc16
-rw-r--r--runtime/art_method.h21
-rw-r--r--runtime/class_linker.cc532
-rw-r--r--runtime/class_linker.h129
-rw-r--r--runtime/common_throws.cc9
-rw-r--r--runtime/common_throws.h3
-rw-r--r--runtime/dex_file.h1
-rw-r--r--runtime/entrypoints/quick/quick_trampoline_entrypoints.cc4
-rw-r--r--runtime/instrumentation.cc6
-rw-r--r--runtime/interpreter/interpreter.cc6
-rw-r--r--runtime/interpreter/interpreter_common.h13
-rw-r--r--runtime/mirror/class.cc4
-rw-r--r--runtime/modifiers.h4
-rwxr-xr-xtest/966-default-conflict/build34
-rw-r--r--test/966-default-conflict/build-src/Iface2.java25
-rw-r--r--test/966-default-conflict/expected.txt18
-rw-r--r--test/966-default-conflict/info.txt6
-rwxr-xr-xtest/966-default-conflict/run17
-rw-r--r--test/966-default-conflict/smali/Iface.smali39
-rw-r--r--test/966-default-conflict/smali/Iface2.smali31
-rw-r--r--test/966-default-conflict/smali/Main.smali227
-rwxr-xr-xtest/967-default-ame/build35
-rw-r--r--test/967-default-ame/build-src/Iface2.java21
-rw-r--r--test/967-default-ame/build-src/Iface3.java19
-rw-r--r--test/967-default-ame/expected.txt18
-rw-r--r--test/967-default-ame/info.txt6
-rwxr-xr-xtest/967-default-ame/run17
-rw-r--r--test/967-default-ame/smali/Iface.smali39
-rw-r--r--test/967-default-ame/smali/Iface2.smali27
-rw-r--r--test/967-default-ame/smali/Iface3.smali26
-rw-r--r--test/967-default-ame/smali/Main.smali228
-rwxr-xr-xtest/968-default-partial-compilation-generated/build50
-rw-r--r--test/968-default-partial-compilation-generated/expected.txt1
-rw-r--r--test/968-default-partial-compilation-generated/info.txt17
-rwxr-xr-xtest/968-default-partial-compilation-generated/run17
-rwxr-xr-xtest/968-default-partial-compilation-generated/util-src/generate_java.py134
-rwxr-xr-xtest/968-default-partial-compilation-generated/util-src/generate_smali.py607
-rw-r--r--test/Android.run-test.mk1
-rw-r--r--test/utils/python/testgen/mixins.py6
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*/&current_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*/&current_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