diff options
| author | 2015-10-13 21:02:28 +0000 | |
|---|---|---|
| committer | 2015-10-13 21:02:28 +0000 | |
| commit | 85506c2ff34e930b4589568f78ee30f60d49d456 (patch) | |
| tree | c7e4b30a54046ed90a212e298f871a5df257c648 | |
| parent | 7598f145d4fd4219c14c6e2c8a03b34ce7943ffe (diff) | |
| parent | eb7c144a6aff7da673ba53d501c46f00311d4d7f (diff) | |
Merge "Add initial default method support to Art"
74 files changed, 3962 insertions, 153 deletions
diff --git a/.gitignore b/.gitignore index c4cf98b37c..4e806c6514 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ JIT_ART +**/__pycache__/** diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc index 52df7deb25..f34b5edcc4 100644 --- a/cmdline/cmdline_parser_test.cc +++ b/cmdline/cmdline_parser_test.cc @@ -21,6 +21,7 @@ #include "utils.h" #include <numeric> #include "gtest/gtest.h" +#include "runtime/experimental_flags.h" #define EXPECT_NULL(expected) EXPECT_EQ(reinterpret_cast<const void*>(expected), \ reinterpret_cast<void*>(nullptr)); @@ -529,22 +530,32 @@ TEST_F(CmdlineParserTest, TestProfilerOptions) { } } // TEST_F -/* -X[no]experimental-lambdas */ -TEST_F(CmdlineParserTest, TestExperimentalLambdas) { +/* -Xexperimental:_ */ +TEST_F(CmdlineParserTest, TestExperimentalFlags) { // Off by default - EXPECT_SINGLE_PARSE_DEFAULT_VALUE(false, + EXPECT_SINGLE_PARSE_DEFAULT_VALUE(ExperimentalFlags::kNone, "", - M::ExperimentalLambdas); + M::Experimental); // Disabled explicitly - EXPECT_SINGLE_PARSE_VALUE(false, - "-Xnoexperimental-lambdas", - M::ExperimentalLambdas); + EXPECT_SINGLE_PARSE_VALUE(ExperimentalFlags::kNone, + "-Xexperimental:none", + M::Experimental); // Enabled explicitly - EXPECT_SINGLE_PARSE_VALUE(true, - "-Xexperimental-lambdas", - M::ExperimentalLambdas); + EXPECT_SINGLE_PARSE_VALUE(ExperimentalFlags::kLambdas, + "-Xexperimental:lambdas", + M::Experimental); + // Enabled explicitly + EXPECT_SINGLE_PARSE_VALUE(ExperimentalFlags::kDefaultMethods, + "-Xexperimental:default-methods", + M::Experimental); + + // Enabled both + EXPECT_SINGLE_PARSE_VALUE(ExperimentalFlags::kDefaultMethods | ExperimentalFlags::kLambdas, + "-Xexperimental:default-methods " + "-Xexperimental:lambdas", + M::Experimental); } // -Xverify:_ diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index a57b6196de..c594adbc94 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -28,6 +28,7 @@ #include "jdwp/jdwp.h" #include "runtime/base/logging.h" #include "runtime/base/time_utils.h" +#include "runtime/experimental_flags.h" #include "gc/collector_type.h" #include "gc/space/large_object_space.h" #include "profiler_options.h" @@ -838,6 +839,23 @@ struct CmdlineType<TestProfilerOptions> : CmdlineTypeParser<TestProfilerOptions> static constexpr bool kCanParseBlankless = true; }; +template<> +struct CmdlineType<ExperimentalFlags> : CmdlineTypeParser<ExperimentalFlags> { + Result ParseAndAppend(const std::string& option, ExperimentalFlags& existing) { + if (option == "none") { + existing = existing | ExperimentalFlags::kNone; + } else if (option == "lambdas") { + existing = existing | ExperimentalFlags::kLambdas; + } else if (option == "default-methods") { + existing = existing | ExperimentalFlags::kDefaultMethods; + } else { + return Result::Failure(std::string("Unknown option '") + option + "'"); + } + return Result::SuccessNoValue(); + } + + static const char* Name() { return "ExperimentalFlags"; } +}; } // namespace art #endif // ART_CMDLINE_CMDLINE_TYPES_H_ diff --git a/compiler/dex/quick/quick_compiler.cc b/compiler/dex/quick/quick_compiler.cc index 1cd742abac..c2fe5538b7 100644 --- a/compiler/dex/quick/quick_compiler.cc +++ b/compiler/dex/quick/quick_compiler.cc @@ -37,6 +37,7 @@ #include "driver/compiler_driver.h" #include "driver/compiler_options.h" #include "elf_writer_quick.h" +#include "experimental_flags.h" #include "jni/quick/jni_compiler.h" #include "mir_to_lir.h" #include "mirror/object.h" @@ -523,7 +524,8 @@ static bool SkipScanningUnsupportedOpcodes(InstructionSet instruction_set) { // All opcodes are supported no matter what. Usually not the case // since experimental opcodes are not implemented in the quick compiler. return true; - } else if (LIKELY(!Runtime::Current()->AreExperimentalLambdasEnabled())) { + } else if (LIKELY(!Runtime::Current()-> + AreExperimentalFlagsEnabled(ExperimentalFlags::kLambdas))) { // Experimental opcodes are disabled. // // If all unsupported opcodes are experimental we don't need to do scanning. diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index c415073cd1..f741732046 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -219,8 +219,9 @@ inline bool ArtMethod::CheckIncompatibleClassChange(InvokeType type) { case kDirect: return !IsDirect() || IsStatic(); case kVirtual: { + // We have an error if we are direct or a non-default, non-miranda interface method. mirror::Class* methods_class = GetDeclaringClass(); - return IsDirect() || (methods_class->IsInterface() && !IsMiranda()); + return IsDirect() || (methods_class->IsInterface() && !IsDefault() && !IsMiranda()); } case kSuper: // Constructors and static methods are called with invoke-direct. diff --git a/runtime/art_method.h b/runtime/art_method.h index 3c58644e88..9743250cc0 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -135,6 +135,11 @@ class ArtMethod FINAL { return (GetAccessFlags() & kAccMiranda) != 0; } + // This is set by the class linker. + bool IsDefault() { + return (GetAccessFlags() & kAccDefault) != 0; + } + bool IsNative() { return (GetAccessFlags() & kAccNative) != 0; } @@ -163,6 +168,11 @@ class ArtMethod FINAL { SetAccessFlags(GetAccessFlags() | kAccPreverified); } + // Returns true if this method could be overridden by a default method. + bool IsOverridableByDefaultMethod() { + return IsDefault() || IsAbstract(); + } + bool CheckIncompatibleClassChange(InvokeType type) SHARED_REQUIRES(Locks::mutator_lock_); uint16_t GetMethodIndex() SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 370ea5cfa6..02f2e0b207 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -16,12 +16,15 @@ #include "class_linker.h" +#include <algorithm> #include <deque> #include <iostream> #include <memory> #include <queue> #include <string> +#include <tuple> #include <unistd.h> +#include <unordered_map> #include <utility> #include <vector> @@ -3342,6 +3345,18 @@ bool ClassLinker::CanWeInitializeClass(mirror::Class* klass, bool can_init_stati return false; } } + // If we are a class we need to initialize all interfaces with default methods when we are + // initialized. Check all of them. + if (!klass->IsInterface()) { + size_t num_interfaces = klass->GetIfTableCount(); + for (size_t i = 0; i < num_interfaces; i++) { + mirror::Class* iface = klass->GetIfTable()->GetInterface(i); + if (iface->HasDefaultMethods() && + !CanWeInitializeClass(iface, can_init_statics, can_init_parents)) { + return false; + } + } + } } if (klass->IsInterface() || !klass->HasSuperClass()) { return true; @@ -3468,6 +3483,35 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, } } + if (!klass->IsInterface()) { + // Initialize interfaces with default methods for the JLS. + size_t num_direct_interfaces = klass->NumDirectInterfaces(); + for (size_t i = 0; i < num_direct_interfaces; i++) { + StackHandleScope<1> hs_iface(self); + Handle<mirror::Class> handle_scope_iface( + hs_iface.NewHandle(mirror::Class::GetDirectInterface(self, klass, i))); + CHECK(handle_scope_iface.Get() != nullptr); + CHECK(handle_scope_iface->IsInterface()); + if (handle_scope_iface->HasBeenRecursivelyInitialized()) { + // We have already done this once for this interface. Skip it. + continue; + } + // We cannot just call initialize class directly because we need to ensure that ALL interfaces + // with default methods are initialized. Non-default interface initialization will not affect + // other non-default super-interfaces. + bool iface_initialized = InitializeDefaultInterfaceRecursive(self, + handle_scope_iface, + can_init_statics, + can_init_parents); + if (!iface_initialized) { + ObjectLock<mirror::Class> lock(self, klass); + // Initialization failed because one of our interfaces with default methods is erroneous. + mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + return false; + } + } + } + const size_t num_static_fields = klass->NumStaticFields(); if (num_static_fields > 0) { const DexFile::ClassDef* dex_class_def = klass->GetClassDef(); @@ -3557,6 +3601,48 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, return success; } +// We recursively run down the tree of interfaces. We need to do this in the order they are declared +// and perform the initialization only on those interfaces that contain default methods. +bool ClassLinker::InitializeDefaultInterfaceRecursive(Thread* self, + Handle<mirror::Class> iface, + bool can_init_statics, + bool can_init_parents) { + CHECK(iface->IsInterface()); + size_t num_direct_ifaces = iface->NumDirectInterfaces(); + // First we initialize all of iface's super-interfaces recursively. + for (size_t i = 0; i < num_direct_ifaces; i++) { + mirror::Class* super_iface = mirror::Class::GetDirectInterface(self, iface, i); + if (!super_iface->HasBeenRecursivelyInitialized()) { + // Recursive step + StackHandleScope<1> hs(self); + Handle<mirror::Class> handle_super_iface(hs.NewHandle(super_iface)); + if (!InitializeDefaultInterfaceRecursive(self, + handle_super_iface, + can_init_statics, + can_init_parents)) { + return false; + } + } + } + + bool result = true; + // Then we initialize 'iface' if it has default methods. We do not need to (and in fact must not) + // initialize if we don't have default methods. + if (iface->HasDefaultMethods()) { + result = EnsureInitialized(self, iface, can_init_statics, can_init_parents); + } + + // Mark that this interface has undergone recursive default interface initialization so we know we + // can skip it on any later class initializations. We do this even if we are not a default + // interface since we can still avoid the traversal. This is purely a performance optimization. + if (result) { + // TODO This should be done in a better way + ObjectLock<mirror::Class> lock(self, iface); + iface->SetRecursivelyInitialized(); + } + return result; +} + bool ClassLinker::WaitForInitializeClass(Handle<mirror::Class> klass, Thread* self, ObjectLock<mirror::Class>& lock) @@ -4289,20 +4375,16 @@ bool ClassLinker::LinkMethods(Thread* self, Handle<mirror::ObjectArray<mirror::Class>> interfaces, ArtMethod** out_imt) { self->AllowThreadSuspension(); - if (klass->IsInterface()) { - // No vtable. - size_t count = klass->NumVirtualMethods(); - if (!IsUint<16>(count)) { - ThrowClassFormatError(klass.Get(), "Too many methods on interface: %zd", count); - return false; - } - for (size_t i = 0; i < count; ++i) { - klass->GetVirtualMethodDuringLinking(i, image_pointer_size_)->SetMethodIndex(i); - } - } else if (!LinkVirtualMethods(self, klass)) { // Link virtual methods first. - return false; - } - return LinkInterfaceMethods(self, klass, interfaces, out_imt); // Link interface method last. + // 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; + // 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. + return SetupInterfaceLookupTable(self, klass, interfaces) + && LinkVirtualMethods(self, klass, /*out*/ &default_translations) + && LinkInterfaceMethods(self, klass, default_translations, out_imt); } // Comparator for name and signature of a method, used in finding overriding methods. Implementation @@ -4426,9 +4508,36 @@ class LinkVirtualHashTable { const uint32_t LinkVirtualHashTable::invalid_index_ = std::numeric_limits<uint32_t>::max(); const uint32_t LinkVirtualHashTable::removed_index_ = std::numeric_limits<uint32_t>::max() - 1; -bool ClassLinker::LinkVirtualMethods(Thread* self, Handle<mirror::Class> klass) { +bool ClassLinker::LinkVirtualMethods( + Thread* self, + Handle<mirror::Class> klass, + /*out*/std::unordered_map<size_t, ArtMethod*>* default_translations) { const size_t num_virtual_methods = klass->NumVirtualMethods(); - if (klass->HasSuperClass()) { + if (klass->IsInterface()) { + // No vtable. + if (!IsUint<16>(num_virtual_methods)) { + ThrowClassFormatError(klass.Get(), "Too many methods on interface: %zu", num_virtual_methods); + return false; + } + bool has_defaults = false; + // TODO May need to replace this with real VTable for invoke_super + // Assign each method an IMT index and set the default flag. + for (size_t i = 0; i < num_virtual_methods; ++i) { + ArtMethod* m = klass->GetVirtualMethodDuringLinking(i, image_pointer_size_); + m->SetMethodIndex(i); + if (!m->IsAbstract()) { + m->SetAccessFlags(m->GetAccessFlags() | kAccDefault); + has_defaults = true; + } + } + // Mark that we have default methods so that we won't need to scan the virtual_methods_ array + // during initialization. This is a performance optimization. We could simply traverse the + // virtual_methods_ array again during initialization. + if (has_defaults) { + klass->SetHasDefaultMethods(); + } + return true; + } else if (klass->HasSuperClass()) { const size_t super_vtable_length = klass->GetSuperClass()->GetVTableLength(); const size_t max_count = num_virtual_methods + super_vtable_length; StackHandleScope<2> hs(self); @@ -4444,14 +4553,22 @@ bool ClassLinker::LinkVirtualMethods(Thread* self, Handle<mirror::Class> klass) vtable->SetElementPtrSize( i, super_class->GetEmbeddedVTableEntry(i, image_pointer_size_), image_pointer_size_); } - if (num_virtual_methods == 0) { + // We might need to change vtable if we have new virtual methods or new interfaces (since that + // might give us new default methods). If no new interfaces then we can skip the rest since + // the class cannot override any of the super-class's methods. This is required for + // correctness since without it we might not update overridden default method vtable entries + // correctly. + if (num_virtual_methods == 0 && super_class->GetIfTableCount() == klass->GetIfTableCount()) { klass->SetVTable(vtable.Get()); return true; } } else { + DCHECK(super_class->IsAbstract() && !super_class->IsArrayClass()); auto* super_vtable = super_class->GetVTable(); CHECK(super_vtable != nullptr) << PrettyClass(super_class.Get()); - if (num_virtual_methods == 0) { + // We might need to change vtable if we have new virtual methods or new interfaces (since that + // might give us new default methods). See comment above. + if (num_virtual_methods == 0 && super_class->GetIfTableCount() == klass->GetIfTableCount()) { klass->SetVTable(super_vtable); return true; } @@ -4472,7 +4589,9 @@ bool ClassLinker::LinkVirtualMethods(Thread* self, Handle<mirror::Class> klass) // the need for the initial vtable which we later shrink back down). // 3. Add non overridden methods to the end of the vtable. static constexpr size_t kMaxStackHash = 250; - const size_t hash_table_size = num_virtual_methods * 3; + // + 1 so that even if we only have new default methods we will still be able to use this hash + // table (i.e. it will never have 0 size). + const size_t hash_table_size = num_virtual_methods * 3 + 1; uint32_t* hash_table_ptr; std::unique_ptr<uint32_t[]> hash_heap_storage; if (hash_table_size <= kMaxStackHash) { @@ -4489,10 +4608,10 @@ bool ClassLinker::LinkVirtualMethods(Thread* self, Handle<mirror::Class> klass) i, image_pointer_size_)->GetDeclaringClass() != nullptr); hash_table.Add(i); } - // Loop through each super vtable method and see if they are overriden by a method we added to + // Loop through each super vtable method and see if they are overridden by a method we added to // the hash table. for (size_t j = 0; j < super_vtable_length; ++j) { - // Search the hash table to see if we are overidden by any method. + // Search the hash table to see if we are overridden by any method. ArtMethod* super_method = vtable->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_); MethodNameAndSignatureComparator super_method_name_comparator( super_method->GetInterfaceMethodIfProxy(image_pointer_size_)); @@ -4515,10 +4634,51 @@ bool ClassLinker::LinkVirtualMethods(Thread* self, Handle<mirror::Class> klass) << " would have incorrectly overridden the package-private method in " << PrettyDescriptor(super_method->GetDeclaringClassDescriptor()); } + } else if (super_method->IsDefault()) { + // 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; + } } } - // Add the non overridden methods at the end. size_t actual_count = super_vtable_length; + // Add the non-overridden methods at the end. for (size_t i = 0; i < num_virtual_methods; ++i) { ArtMethod* local_method = klass->GetVirtualMethodDuringLinking(i, image_pointer_size_); size_t method_idx = local_method->GetMethodIndexDuringLinking(); @@ -4566,20 +4726,223 @@ bool ClassLinker::LinkVirtualMethods(Thread* self, Handle<mirror::Class> klass) return true; } -bool ClassLinker::LinkInterfaceMethods(Thread* self, - Handle<mirror::Class> klass, - Handle<mirror::ObjectArray<mirror::Class>> interfaces, - ArtMethod** out_imt) { - StackHandleScope<3> hs(self); - Runtime* const runtime = Runtime::Current(); - const bool has_superclass = klass->HasSuperClass(); - const size_t super_ifcount = has_superclass ? klass->GetSuperClass()->GetIfTableCount() : 0U; +// 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 { + 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 + // we encounter is the best candidate since it is the most specific. Once we have found it we keep + // track of it and then continue checking all other interfaces, since we need to throw an error if + // we encounter conflicting default method implementations (one is not a subtype of the other). + // + // 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; + } + + StackHandleScope<1> hs(self); + MutableHandle<mirror::IfTable> iftable(hs.NewHandle(klass->GetIfTable())); + 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) { + DCHECK_LT(k, iftable->Count()); + mirror::Class* iface = 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++) { + ArtMethod* current_method = iface->GetVirtualMethodUnchecked(m, image_pointer_size_); + // Skip abstract methods and methods with different names. + if (current_method->IsAbstract() || + !target_name_comparator.HasSameNameAndSignature( + current_method->GetInterfaceMethodIfProxy(image_pointer_size_))) { + continue; + } + // 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; + } 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; + } + } + if (k == 0) { + break; + } + } + return true; +} + +// Sets imt_ref appropriately for LinkInterfaceMethods. +// If there is no method in the imt location of imt_ref it will store the given method there. +// 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, + size_t image_pointer_size, + ArtMethod* current_method, + /*out*/ArtMethod** imt_ref) + SHARED_REQUIRES(Locks::mutator_lock_) { + // 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) { + // 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( + (*imt_ref)->GetInterfaceMethodIfProxy(image_pointer_size)); + if (imt_comparator.HasSameNameAndSignature( + current_method->GetInterfaceMethodIfProxy(image_pointer_size))) { + *imt_ref = current_method; + } else { + *imt_ref = conflict_method; + } + } +} + +// Simple helper function that checks that no subtypes of 'val' are contained within the 'classes' +// set. +static bool NotSubinterfaceOfAny(const std::unordered_set<mirror::Class*>& classes, + mirror::Class* val) + REQUIRES(Roles::uninterruptible_) + SHARED_REQUIRES(Locks::mutator_lock_) { + DCHECK(val != nullptr); + for (auto c : classes) { + if (val->IsAssignableFrom(&*c)) { + return false; + } + } + return true; +} + +// Fills in and flattens the interface inheritance hierarchy. +// +// By the end of this function all interfaces in the transitive closure of to_process are added to +// the iftable and every interface precedes all of its sub-interfaces in this list. +// +// all I, J: Interface | I <: J implies J precedes I +// +// (note A <: B means that A is a subtype of B) +// +// This returns the total number of items in the iftable. The iftable might be resized down after +// this call. +// +// We order this backwards so that we do not need to reorder superclass interfaces when new +// interfaces are added in subclass's interface tables. +// +// Upon entry into this function iftable is a copy of the superclass's iftable with the first +// super_ifcount entries filled in with the transitive closure of the interfaces of the superclass. +// The other entries are uninitialized. We will fill in the remaining entries in this function. The +// iftable must be large enough to hold all interfaces without changing its size. +static size_t FillIfTable(mirror::IfTable* iftable, + size_t super_ifcount, + std::vector<mirror::Class*> to_process) + REQUIRES(Roles::uninterruptible_) + SHARED_REQUIRES(Locks::mutator_lock_) { + // This is the set of all class's already in the iftable. Used to make checking if a class has + // already been added quicker. + std::unordered_set<mirror::Class*> classes_in_iftable; + // The first super_ifcount elements are from the superclass. We note that they are already added. + for (size_t i = 0; i < super_ifcount; i++) { + mirror::Class* iface = iftable->GetInterface(i); + DCHECK(NotSubinterfaceOfAny(classes_in_iftable, iface)) << "Bad ordering."; + classes_in_iftable.insert(iface); + } + size_t filled_ifcount = super_ifcount; + for (mirror::Class* interface : to_process) { + // Let us call the first filled_ifcount elements of iftable the current-iface-list. + // At this point in the loop current-iface-list has the invariant that: + // for every pair of interfaces I,J within it: + // if index_of(I) < index_of(J) then I is not a subtype of J + + // If we have already seen this element then all of its super-interfaces must already be in the + // current-iface-list so we can skip adding it. + if (!ContainsElement(classes_in_iftable, interface)) { + // We haven't seen this interface so add all of its super-interfaces onto the + // current-iface-list, skipping those already on it. + int32_t ifcount = interface->GetIfTableCount(); + for (int32_t j = 0; j < ifcount; j++) { + mirror::Class* super_interface = interface->GetIfTable()->GetInterface(j); + if (!ContainsElement(classes_in_iftable, super_interface)) { + DCHECK(NotSubinterfaceOfAny(classes_in_iftable, super_interface)) << "Bad ordering."; + classes_in_iftable.insert(super_interface); + iftable->SetInterface(filled_ifcount, super_interface); + filled_ifcount++; + } + } + DCHECK(NotSubinterfaceOfAny(classes_in_iftable, interface)) << "Bad ordering"; + // Place this interface onto the current-iface-list after all of its super-interfaces. + classes_in_iftable.insert(interface); + iftable->SetInterface(filled_ifcount, interface); + filled_ifcount++; + } else if (kIsDebugBuild) { + // Check all super-interfaces are already in the list. + int32_t ifcount = interface->GetIfTableCount(); + for (int32_t j = 0; j < ifcount; j++) { + mirror::Class* super_interface = interface->GetIfTable()->GetInterface(j); + DCHECK(ContainsElement(classes_in_iftable, super_interface)) + << "Iftable does not contain " << PrettyClass(super_interface) + << ", a superinterface of " << PrettyClass(interface); + } + } + } + if (kIsDebugBuild) { + // Check that the iftable is ordered correctly. + for (size_t i = 0; i < filled_ifcount; i++) { + mirror::Class* if_a = iftable->GetInterface(i); + for (size_t j = i + 1; j < filled_ifcount; j++) { + mirror::Class* if_b = iftable->GetInterface(j); + // !(if_a <: if_b) + CHECK(!if_b->IsAssignableFrom(if_a)) + << "Bad interface order: " << PrettyClass(if_a) << " (index " << i << ") extends " + << PrettyClass(if_b) << " (index " << j << ") and so should be after it in the " + << "interface list."; + } + } + } + return filled_ifcount; +} + +bool ClassLinker::SetupInterfaceLookupTable(Thread* self, Handle<mirror::Class> klass, + Handle<mirror::ObjectArray<mirror::Class>> interfaces) { + StackHandleScope<1> hs(self); + const size_t super_ifcount = + klass->HasSuperClass() ? klass->GetSuperClass()->GetIfTableCount() : 0U; const bool have_interfaces = interfaces.Get() != nullptr; - const size_t num_interfaces = have_interfaces - ? interfaces->GetLength() - : klass->NumDirectInterfaces(); - const size_t method_alignment = ArtMethod::Alignment(image_pointer_size_); - const size_t method_size = ArtMethod::Size(image_pointer_size_); + const size_t num_interfaces = + have_interfaces ? interfaces->GetLength() : klass->NumDirectInterfaces(); if (num_interfaces == 0) { if (super_ifcount == 0) { // Class implements no interfaces. @@ -4603,6 +4966,7 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, } } size_t ifcount = super_ifcount + num_interfaces; + // Check that every class being implemented is an interface. for (size_t i = 0; i < num_interfaces; i++) { mirror::Class* interface = have_interfaces ? interfaces->GetWithoutChecks(i) @@ -4618,11 +4982,13 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, } ifcount += interface->GetIfTableCount(); } + // Create the interface function table. MutableHandle<mirror::IfTable> iftable(hs.NewHandle(AllocIfTable(self, ifcount))); if (UNLIKELY(iftable.Get() == nullptr)) { self->AssertPendingOOMException(); return false; } + // Fill in table with superclass's iftable. if (super_ifcount != 0) { mirror::IfTable* super_iftable = klass->GetSuperClass()->GetIfTable(); for (size_t i = 0; i < super_ifcount; i++) { @@ -4630,56 +4996,59 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, iftable->SetInterface(i, super_interface); } } + + // Note that AllowThreadSuspension is to thread suspension as pthread_testcancel is to pthread + // cancellation. That is it will suspend if one has a pending suspend request but otherwise + // doesn't really do anything. self->AllowThreadSuspension(); - // Flatten the interface inheritance hierarchy. - size_t idx = super_ifcount; - for (size_t i = 0; i < num_interfaces; i++) { - mirror::Class* interface = have_interfaces ? interfaces->Get(i) : - mirror::Class::GetDirectInterface(self, klass, i); - // Check if interface is already in iftable - bool duplicate = false; - for (size_t j = 0; j < idx; j++) { - mirror::Class* existing_interface = iftable->GetInterface(j); - if (existing_interface == interface) { - duplicate = true; - break; - } - } - if (!duplicate) { - // Add this non-duplicate interface. - iftable->SetInterface(idx++, interface); - // Add this interface's non-duplicate super-interfaces. - for (int32_t j = 0; j < interface->GetIfTableCount(); j++) { - mirror::Class* super_interface = interface->GetIfTable()->GetInterface(j); - bool super_duplicate = false; - for (size_t k = 0; k < idx; k++) { - mirror::Class* existing_interface = iftable->GetInterface(k); - if (existing_interface == super_interface) { - super_duplicate = true; - break; - } - } - if (!super_duplicate) { - iftable->SetInterface(idx++, super_interface); - } - } + + size_t new_ifcount; + { + ScopedAssertNoThreadSuspension nts(self, "Copying mirror::Class*'s for FillIfTable"); + std::vector<mirror::Class*> to_add; + for (size_t i = 0; i < num_interfaces; i++) { + mirror::Class* interface = have_interfaces ? interfaces->Get(i) : + mirror::Class::GetDirectInterface(self, klass, i); + to_add.push_back(interface); } + + new_ifcount = FillIfTable(iftable.Get(), super_ifcount, std::move(to_add)); } + self->AllowThreadSuspension(); + // Shrink iftable in case duplicates were found - if (idx < ifcount) { + if (new_ifcount < ifcount) { DCHECK_NE(num_interfaces, 0U); iftable.Assign(down_cast<mirror::IfTable*>( - iftable->CopyOf(self, idx * mirror::IfTable::kMax))); + iftable->CopyOf(self, new_ifcount * mirror::IfTable::kMax))); if (UNLIKELY(iftable.Get() == nullptr)) { self->AssertPendingOOMException(); return false; } - ifcount = idx; + ifcount = new_ifcount; } else { - DCHECK_EQ(idx, ifcount); + DCHECK_EQ(new_ifcount, ifcount); } klass->SetIfTable(iftable.Get()); + return true; +} + +bool ClassLinker::LinkInterfaceMethods( + Thread* self, + Handle<mirror::Class> klass, + const std::unordered_map<size_t, ArtMethod*>& default_translations, + ArtMethod** out_imt) { + StackHandleScope<3> hs(self); + Runtime* const runtime = Runtime::Current(); + const bool has_superclass = klass->HasSuperClass(); + const size_t super_ifcount = has_superclass ? klass->GetSuperClass()->GetIfTableCount() : 0U; + const size_t method_alignment = ArtMethod::Alignment(image_pointer_size_); + const size_t method_size = ArtMethod::Size(image_pointer_size_); + const size_t ifcount = klass->GetIfTableCount(); + + MutableHandle<mirror::IfTable> iftable(hs.NewHandle(klass->GetIfTable())); + // If we're an interface, we don't need the vtable pointers, so we're done. if (klass->IsInterface()) { return true; @@ -4692,6 +5061,7 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, ArenaStack stack(runtime->GetLinearAlloc()->GetArenaPool()); ScopedArenaAllocator allocator(&stack); 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(); @@ -4721,7 +5091,9 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, for (size_t j = 0; j < num_virtuals; ++j) { auto method = method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_); DCHECK(method != nullptr) << PrettyClass(super_class); - if (method->IsMiranda()) { + // Miranda methods cannot be used to implement an interface method and defaults should be + // skipped in case we override it. + if (method->IsDefault() || method->IsMiranda()) { continue; } ArtMethod* interface_method = interface->GetVirtualMethod(j, image_pointer_size_); @@ -4742,6 +5114,8 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, size_t num_methods = iftable->GetInterface(i)->NumVirtualMethods(); if (num_methods > 0) { const bool is_super = i < super_ifcount; + // This is an interface implemented by a super-class. Therefore we can just copy the method + // array from the superclass. const bool super_interface = is_super && extend_super_iftable; mirror::PointerArray* method_array; if (super_interface) { @@ -4785,16 +5159,13 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, input_vtable_array = vtable; input_array_length = input_vtable_array->GetLength(); } - if (input_array_length == 0) { - // If the added virtual methods is empty, do nothing. - DCHECK(super_interface); - continue; - } + // For each method in interface for (size_t j = 0; j < num_methods; ++j) { auto* interface_method = iftable->GetInterface(i)->GetVirtualMethod(j, image_pointer_size_); MethodNameAndSignatureComparator interface_name_comparator( interface_method->GetInterfaceMethodIfProxy(image_pointer_size_)); - int32_t k; + uint32_t imt_index = interface_method->GetDexMethodIndex() % mirror::Class::kImtSize; + ArtMethod** imt_ptr = &out_imt[imt_index]; // For each method listed in the interface's method list, find the // matching method in our class's method list. We want to favor the // subclass over the superclass, which just requires walking @@ -4803,7 +5174,12 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, // it -- otherwise it would use the same vtable slot. In .dex files // those don't end up in the virtual method table, so it shouldn't // matter which direction we go. We walk it backward anyway.) - for (k = input_array_length - 1; k >= 0; --k) { + // + // 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; + 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) : input_vtable_array->GetElementPtrSize<ArtMethod*>(k, image_pointer_size_); @@ -4819,25 +5195,69 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, "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()) { + // 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; + 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. - uint32_t imt_index = interface_method->GetDexMethodIndex() % mirror::Class::kImtSize; - auto** imt_ref = &out_imt[imt_index]; - if (*imt_ref == unimplemented_method) { - *imt_ref = vtable_method; - } else if (*imt_ref != 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( - (*imt_ref)->GetInterfaceMethodIfProxy(image_pointer_size_)); - *imt_ref = imt_comparator.HasSameNameAndSignature(vtable_method_for_name_comparison) ? - vtable_method : conflict_method; - } + SetIMTRef(unimplemented_method, + conflict_method, + image_pointer_size_, + vtable_method, + /*out*/imt_ptr); break; } } - if (k < 0 && !super_interface) { + // We should only search for default implementations when the class does not implement the + // method directly and either (1) the interface is newly implemented on this class and not + // on any of its superclasses, (2) the superclass's implementation is a default method, or + // (3) the superclass does not have an implementation. + if (!found_impl && (!super_interface || + method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_) + ->IsOverridableByDefaultMethod())) { + ArtMethod* current_method = nullptr; + std::string icce_message; + if (!FindDefaultMethodImplementation(self, + interface_method, + klass, + /*out*/¤t_method, + /*out*/&icce_message)) { + // There was a conflict with default method implementations. + self->EndAssertNoThreadSuspension(old_cause); + // TODO This should actually be thrown when we attempt to invoke this method. + ThrowIncompatibleClassChangeError(klass.Get(), "%s", icce_message.c_str()); + return false; + } else if (current_method != nullptr) { + if (found_default_impl && + current_method->GetDeclaringClass() == default_impl->GetDeclaringClass()) { + // 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; + } 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); + } + 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; + } + } + 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)) { @@ -4857,9 +5277,10 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, } } } - if (!miranda_methods.empty()) { + if (!miranda_methods.empty() || !default_methods.empty()) { const size_t old_method_count = klass->NumVirtualMethods(); - const size_t new_method_count = old_method_count + miranda_methods.size(); + const size_t new_method_count = + old_method_count + miranda_methods.size() + default_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 @@ -4894,13 +5315,36 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, ++out; } } - StrideIterator<ArtMethod> out(virtuals->Begin(method_size, method_alignment) + old_method_count); + StrideIterator<ArtMethod> out(virtuals->Begin(method_size, method_alignment) + + old_method_count); // Copy over miranda methods before copying vtable since CopyOf may cause thread suspension and // we want the roots of the miranda methods to get visited. for (ArtMethod* mir_method : miranda_methods) { - out->CopyFrom(mir_method, image_pointer_size_); - out->SetAccessFlags(out->GetAccessFlags() | kAccMiranda); - move_table.emplace(mir_method, &*out); + ArtMethod& new_method = *out; + new_method.CopyFrom(mir_method, image_pointer_size_); + new_method.SetAccessFlags(new_method.GetAccessFlags() | kAccMiranda); + DCHECK_NE(new_method.GetAccessFlags() & kAccAbstract, 0u) + << "Miranda method should be abstract!"; + move_table.emplace(mir_method, &new_method); + ++out; + } + // We need to copy the default methods into our own virtual method table since the runtime + // requires that every method on a class's vtable be in that respective class's virtual method + // table. + // NOTE This means that two classes might have the same implementation of a method from the same + // interface but will have different ArtMethod*s for them. This also means we cannot compare a + // default method found on a class with one found on the declaring interface directly and must + // look at the declaring class to determine if they are the same. + 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); + move_table.emplace(def_method, &new_method); ++out; } virtuals->SetLength(new_method_count); @@ -4910,7 +5354,8 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, self->EndAssertNoThreadSuspension(old_cause); const size_t old_vtable_count = vtable->GetLength(); - const size_t new_vtable_count = old_vtable_count + miranda_methods.size(); + const size_t new_vtable_count = + old_vtable_count + miranda_methods.size() + default_methods.size(); miranda_methods.clear(); vtable.Assign(down_cast<mirror::PointerArray*>(vtable->CopyOf(self, new_vtable_count))); if (UNLIKELY(vtable.Get() == nullptr)) { @@ -4927,15 +5372,29 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, ++vtable_pos; } CHECK_EQ(vtable_pos, new_vtable_count); - // Update old vtable methods. + // Update old vtable methods. We use the default_translations map to figure out what each vtable + // entry should be updated to, if they need to be at all. for (size_t i = 0; i < old_vtable_count; ++i) { - auto* m = vtable->GetElementPtrSize<ArtMethod*>(i, image_pointer_size_); - DCHECK(m != nullptr) << PrettyClass(klass.Get()); - auto it = move_table.find(m); + ArtMethod* translated_method = vtable->GetElementPtrSize<ArtMethod*>(i, image_pointer_size_); + // Try and find what we need to change this method to. + 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); + found_translation = true; + } + DCHECK(translated_method != nullptr); + auto it = move_table.find(translated_method); if (it != move_table.end()) { - auto* new_m = it->second; - DCHECK(new_m != nullptr) << PrettyClass(klass.Get()); - vtable->SetElementPtrSize(i, new_m, image_pointer_size_); + auto* new_method = it->second; + DCHECK(new_method != nullptr); + vtable->SetElementPtrSize(i, new_method, image_pointer_size_); + } else { + // If it was not going to be updated we wouldn't have put it into the default_translations + // map. + CHECK(!found_translation) << "We were asked to update this vtable entry. Must not fail."; } } @@ -4966,7 +5425,11 @@ bool ClassLinker::LinkInterfaceMethods(Thread* self, auto* resolved_methods = klass->GetDexCache()->GetResolvedMethods(); for (size_t i = 0, count = klass->GetDexCache()->NumResolvedMethods(); i < count; ++i) { auto* m = mirror::DexCache::GetElementPtrSize(resolved_methods, i, image_pointer_size_); - CHECK(move_table.find(m) == move_table.end()) << PrettyMethod(m); + // We don't remove default methods from the move table since we need them to update the + // vtable. Therefore just skip them for this check. + if (!m->IsDefault()) { + CHECK(move_table.find(m) == move_table.end()) << PrettyMethod(m); + } } } // Put some random garbage in old virtuals to help find stale pointers. diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 76cb0a6fb6..93161f7bb7 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -18,6 +18,7 @@ #define ART_RUNTIME_CLASS_LINKER_H_ #include <string> +#include <unordered_map> #include <utility> #include <vector> @@ -648,6 +649,12 @@ class ClassLinker { bool can_init_parents) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!dex_lock_); + bool InitializeDefaultInterfaceRecursive(Thread* self, + Handle<mirror::Class> klass, + bool can_run_clinit, + bool can_init_parents) + REQUIRES(!dex_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); bool WaitForInitializeClass(Handle<mirror::Class> klass, Thread* self, ObjectLock<mirror::Class>& lock); @@ -687,12 +694,65 @@ class ClassLinker { ArtMethod** out_imt) SHARED_REQUIRES(Locks::mutator_lock_); - bool LinkVirtualMethods(Thread* self, Handle<mirror::Class> klass) - SHARED_REQUIRES(Locks::mutator_lock_); - + // Links the virtual methods for the given class and records any default methods that will need to + // be updated later. + // + // Arguments: + // * self - The current thread. + // * klass - class, whose vtable will be filled in. + // * default_translations - Vtable index to new method map. + // Any vtable entries that need to be updated with new default methods + // are stored into the default_translations map. The default_translations + // map is keyed on the vtable index that needs to be updated. We use this + // map because if we override a default method with another default + // method we need to update the vtable to point to the new method. + // Unfortunately since we copy the ArtMethod* we cannot just do a simple + // 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) + SHARED_REQUIRES(Locks::mutator_lock_); + + // Sets up the interface lookup table (IFTable) in the correct order to allow searching for + // default methods. + bool SetupInterfaceLookupTable(Thread* self, + Handle<mirror::Class> klass, + Handle<mirror::ObjectArray<mirror::Class>> interfaces) + SHARED_REQUIRES(Locks::mutator_lock_); + + // Find the default method implementation for 'interface_method' in 'klass', if one exists. + // + // Arguments: + // * self - The current thread. + // * 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 + 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, - Handle<mirror::ObjectArray<mirror::Class>> interfaces, + const std::unordered_map<size_t, ArtMethod*>& default_translations, ArtMethod** out_imt) SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc index 09416cc5c4..a5f9d09900 100644 --- a/runtime/dex_file_verifier.cc +++ b/runtime/dex_file_verifier.cc @@ -23,7 +23,9 @@ #include "base/stringprintf.h" #include "dex_file-inl.h" +#include "experimental_flags.h" #include "leb128.h" +#include "runtime.h" #include "safe_map.h" #include "utf-inl.h" #include "utils.h" @@ -2530,7 +2532,14 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, } // Only the static initializer may have code in an interface. - if (((class_access_flags & kAccInterface) != 0) && !is_clinit_by_name) { + // TODO We should have some way determine whether to allow this experimental flag without the + // runtime being started. + // We assume experimental flags are enabled when running without a runtime to enable tools like + // dexdump to handle dex files with these features. + if (((class_access_flags & kAccInterface) != 0) + && !is_clinit_by_name + && Runtime::Current() != nullptr + && !Runtime::Current()->AreExperimentalFlagsEnabled(ExperimentalFlags::kDefaultMethods)) { *error_msg = StringPrintf("Non-clinit interface method %" PRIu32 " should not have code", method_index); return false; diff --git a/runtime/experimental_flags.h b/runtime/experimental_flags.h new file mode 100644 index 0000000000..2e674e95c6 --- /dev/null +++ b/runtime/experimental_flags.h @@ -0,0 +1,88 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_EXPERIMENTAL_FLAGS_H_ +#define ART_RUNTIME_EXPERIMENTAL_FLAGS_H_ + +#include <ostream> + +namespace art { + +// Possible experimental features that might be enabled. +struct ExperimentalFlags { + // The actual flag values. + enum { + kNone = 0x0000, + kLambdas = 0x0001, + kDefaultMethods = 0x0002, + }; + + constexpr ExperimentalFlags() : value_(0x0000) {} + constexpr ExperimentalFlags(decltype(kNone) t) : value_(static_cast<uint32_t>(t)) {} + + constexpr operator decltype(kNone)() const { + return static_cast<decltype(kNone)>(value_); + } + + constexpr explicit operator bool() const { + return value_ != kNone; + } + + constexpr ExperimentalFlags operator|(const decltype(kNone)& b) const { + return static_cast<decltype(kNone)>(value_ | static_cast<uint32_t>(b)); + } + constexpr ExperimentalFlags operator|(const ExperimentalFlags& b) const { + return static_cast<decltype(kNone)>(value_ | b.value_); + } + + constexpr ExperimentalFlags operator&(const ExperimentalFlags& b) const { + return static_cast<decltype(kNone)>(value_ & b.value_); + } + constexpr ExperimentalFlags operator&(const decltype(kNone)& b) const { + return static_cast<decltype(kNone)>(value_ & static_cast<uint32_t>(b)); + } + + constexpr bool operator==(const ExperimentalFlags& b) const { + return value_ == b.value_; + } + + private: + uint32_t value_; +}; + +inline std::ostream& operator<<(std::ostream& stream, const ExperimentalFlags& e) { + bool started = false; + if (e & ExperimentalFlags::kLambdas) { + stream << (started ? "|" : "") << "kLambdas"; + started = true; + } + if (e & ExperimentalFlags::kDefaultMethods) { + stream << (started ? "|" : "") << "kDefaultMethods"; + started = true; + } + if (!started) { + stream << "kNone"; + } + return stream; +} + +inline std::ostream& operator<<(std::ostream& stream, const decltype(ExperimentalFlags::kNone)& e) { + return stream << ExperimentalFlags(e); +} + +} // namespace art + +#endif // ART_RUNTIME_EXPERIMENTAL_FLAGS_H_ diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index b010504e7d..7c0594a8bb 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -458,10 +458,11 @@ void ArtInterpreterToInterpreterBridge(Thread* self, const DexFile::CodeItem* co } self->PushShadowFrame(shadow_frame); + ArtMethod* method = shadow_frame->GetMethod(); // Ensure static methods are initialized. - const bool is_static = shadow_frame->GetMethod()->IsStatic(); + const bool is_static = method->IsStatic(); if (is_static) { - mirror::Class* declaring_class = shadow_frame->GetMethod()->GetDeclaringClass(); + mirror::Class* declaring_class = method->GetDeclaringClass(); if (UNLIKELY(!declaring_class->IsInitialized())) { StackHandleScope<1> hs(self); HandleWrapper<Class> h_declaring_class(hs.NewHandleWrapper(&declaring_class)); diff --git a/runtime/interpreter/interpreter_goto_table_impl.cc b/runtime/interpreter/interpreter_goto_table_impl.cc index 4265b502c6..9766299ae0 100644 --- a/runtime/interpreter/interpreter_goto_table_impl.cc +++ b/runtime/interpreter/interpreter_goto_table_impl.cc @@ -19,6 +19,7 @@ #include "base/stl_util.h" // MakeUnique +#include "experimental_flags.h" #include "interpreter_common.h" #include "safe_math.h" @@ -83,7 +84,7 @@ namespace interpreter { #define HANDLE_EXPERIMENTAL_INSTRUCTION_START(opcode) \ HANDLE_INSTRUCTION_START(opcode); \ DCHECK(inst->IsExperimental()); \ - if (Runtime::Current()->AreExperimentalLambdasEnabled()) { + if (Runtime::Current()->AreExperimentalFlagsEnabled(ExperimentalFlags::kLambdas)) { #define HANDLE_EXPERIMENTAL_INSTRUCTION_END() \ } else { \ UnexpectedOpcode(inst, shadow_frame); \ diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 76d4bb0fc1..bf95a0e46f 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -15,6 +15,7 @@ */ #include "base/stl_util.h" // MakeUnique +#include "experimental_flags.h" #include "interpreter_common.h" #include "safe_math.h" @@ -67,7 +68,7 @@ namespace interpreter { static bool IsExperimentalInstructionEnabled(const Instruction *inst) { DCHECK(inst->IsExperimental()); - return Runtime::Current()->AreExperimentalLambdasEnabled(); + return Runtime::Current()->AreExperimentalFlagsEnabled(ExperimentalFlags::kLambdas); } template<bool do_access_check, bool transaction_active> diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 93f2aea38e..a528c3b890 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -392,7 +392,8 @@ inline ArtMethod* Class::FindVirtualMethodForInterface(ArtMethod* method, size_t } inline ArtMethod* Class::FindVirtualMethodForVirtual(ArtMethod* method, size_t pointer_size) { - DCHECK(!method->GetDeclaringClass()->IsInterface() || method->IsMiranda()); + // Only miranda or default methods may come from interfaces and be used as a virtual. + DCHECK(!method->GetDeclaringClass()->IsInterface() || method->IsDefault() || method->IsMiranda()); // The argument method may from a super class. // Use the index to a potentially overridden one for this instance's class. return GetVTableEntry(method->GetMethodIndex(), pointer_size); diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 2668b3db9c..8219d69b6e 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -30,6 +30,7 @@ #include "primitive.h" #include "read_barrier_option.h" #include "stride_iterator.h" +#include "thread.h" #include "utils.h" #ifndef IMT_SIZE @@ -229,6 +230,18 @@ class MANAGED Class FINAL : public Object { return (GetAccessFlags() & kAccClassIsFinalizable) != 0; } + ALWAYS_INLINE void SetRecursivelyInitialized() SHARED_REQUIRES(Locks::mutator_lock_) { + DCHECK_EQ(GetLockOwnerThreadId(), Thread::Current()->GetThreadId()); + uint32_t flags = GetField32(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_)); + SetAccessFlags(flags | kAccRecursivelyInitialized); + } + + ALWAYS_INLINE void SetHasDefaultMethods() SHARED_REQUIRES(Locks::mutator_lock_) { + DCHECK_EQ(GetLockOwnerThreadId(), Thread::Current()->GetThreadId()); + uint32_t flags = GetField32(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_)); + SetAccessFlags(flags | kAccHasDefaultMethod); + } + ALWAYS_INLINE void SetFinalizable() SHARED_REQUIRES(Locks::mutator_lock_) { uint32_t flags = GetField32(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_)); SetAccessFlags(flags | kAccClassIsFinalizable); @@ -860,6 +873,14 @@ class MANAGED Class FINAL : public Object { ArtMethod* FindClassInitializer(size_t pointer_size) SHARED_REQUIRES(Locks::mutator_lock_); + bool HasDefaultMethods() SHARED_REQUIRES(Locks::mutator_lock_) { + return (GetAccessFlags() & kAccHasDefaultMethod) != 0; + } + + bool HasBeenRecursivelyInitialized() SHARED_REQUIRES(Locks::mutator_lock_) { + return (GetAccessFlags() & kAccRecursivelyInitialized) != 0; + } + ALWAYS_INLINE int32_t GetIfTableCount() SHARED_REQUIRES(Locks::mutator_lock_); ALWAYS_INLINE IfTable* GetIfTable() SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/modifiers.h b/runtime/modifiers.h index f7ab10be9d..116cbe9254 100644 --- a/runtime/modifiers.h +++ b/runtime/modifiers.h @@ -49,8 +49,13 @@ static constexpr uint32_t kAccPreverified = 0x00080000; // class (runt // method (dex only) 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) // Special runtime-only flags. +// Interface and all its super-interfaces with default methods have been recursively initialized. +static constexpr uint32_t kAccRecursivelyInitialized = 0x20000000; +// Interface declares some default method. +static constexpr uint32_t kAccHasDefaultMethod = 0x40000000; // class/ancestor overrides finalize() static constexpr uint32_t kAccClassIsFinalizable = 0x80000000; diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 50e2053c73..ae16c7f373 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -269,10 +269,10 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define("-Xfingerprint:_") .WithType<std::string>() .IntoKey(M::Fingerprint) - .Define({"-Xexperimental-lambdas", "-Xnoexperimental-lambdas"}) - .WithType<bool>() - .WithValues({true, false}) - .IntoKey(M::ExperimentalLambdas) + .Define("-Xexperimental:_") + .WithType<ExperimentalFlags>() + .AppendValues() + .IntoKey(M::Experimental) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", @@ -557,7 +557,14 @@ bool ParsedOptions::Parse(const RuntimeOptions& options, bool ignore_unrecognize args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize)); } - if (args.GetOrDefault(M::ExperimentalLambdas)) { + if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kDefaultMethods) { + LOG(WARNING) << "Default method support has been enabled. The verifier will be less strict " + << "in some cases. All existing invoke opcodes have an unstable updated " + << "specification and are nearly guaranteed to change over time. Do not attempt " + << "to write shipping code against the invoke opcodes with this flag."; + } + + if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kLambdas) { LOG(WARNING) << "Experimental lambdas have been enabled. All lambda opcodes have " << "an unstable specification and are nearly guaranteed to change over time. " << "Do not attempt to write shipping code against these opcodes."; @@ -682,8 +689,8 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -X[no]image-dex2oat (Whether to create and use a boot image)\n"); UsageMessage(stream, " -Xno-dex-file-fallback " "(Don't fall back to dex files without oat files)\n"); - UsageMessage(stream, " -X[no]experimental-lambdas\n" - " (Enable new experimental dalvik opcodes, off by default)\n"); + UsageMessage(stream, " -Xexperimental:{lambdas,default-methods} " + "(Enable new experimental dalvik opcodes and semantics, off by default)\n"); UsageMessage(stream, "\n"); UsageMessage(stream, "The following previously supported Dalvik options are ignored:\n"); diff --git a/runtime/runtime.cc b/runtime/runtime.cc index b623ba1aec..cd09bee4e6 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -64,6 +64,7 @@ #include "debugger.h" #include "elf_file.h" #include "entrypoints/runtime_asm_entrypoints.h" +#include "experimental_flags.h" #include "fault_handler.h" #include "gc/accounting/card_table-inl.h" #include "gc/heap.h" @@ -199,7 +200,7 @@ Runtime::Runtime() no_sig_chain_(false), is_native_bridge_loaded_(false), zygote_max_failed_boots_(0), - experimental_lambdas_(false) { + experimental_flags_(ExperimentalFlags::kNone) { CheckAsmSupportOffsetsAndSizes(); std::fill(callee_save_methods_, callee_save_methods_ + arraysize(callee_save_methods_), 0u); } @@ -884,7 +885,7 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) } zygote_max_failed_boots_ = runtime_options.GetOrDefault(Opt::ZygoteMaxFailedBoots); - experimental_lambdas_ = runtime_options.GetOrDefault(Opt::ExperimentalLambdas); + experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental); XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption); ATRACE_BEGIN("CreateHeap"); diff --git a/runtime/runtime.h b/runtime/runtime.h index fd486f9b35..458f08a316 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -28,6 +28,7 @@ #include "arch/instruction_set.h" #include "base/macros.h" +#include "experimental_flags.h" #include "gc_root.h" #include "instrumentation.h" #include "jobject_comparator.h" @@ -532,8 +533,8 @@ class Runtime { return zygote_max_failed_boots_; } - bool AreExperimentalLambdasEnabled() const { - return experimental_lambdas_; + bool AreExperimentalFlagsEnabled(ExperimentalFlags flags) { + return (experimental_flags_ & flags) != ExperimentalFlags::kNone; } lambda::BoxTable* GetLambdaBoxTable() const { @@ -769,7 +770,7 @@ class Runtime { // eventually publish them as public-usable opcodes, but they aren't ready yet. // // Experimental opcodes should not be used by other production code. - bool experimental_lambdas_; + ExperimentalFlags experimental_flags_; MethodRefToStringInitRegMap method_ref_string_init_reg_map_; diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index d88e84b602..7b5bc1ad9a 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -114,7 +114,7 @@ RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 10) RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) RUNTIME_OPTIONS_KEY (std::string, CpuAbiList) RUNTIME_OPTIONS_KEY (std::string, Fingerprint) -RUNTIME_OPTIONS_KEY (bool, ExperimentalLambdas, false) // -X[no]experimental-lambdas +RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{, lambdas, default-methods} // Not parse-able from command line, but can be provided explicitly. // (Do not add anything here that is defined in ParsedOptions::MakeParser) diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index eed3e22a72..4051a1cbe6 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -30,6 +30,7 @@ #include "dex_instruction-inl.h" #include "dex_instruction_utils.h" #include "dex_instruction_visitor.h" +#include "experimental_flags.h" #include "gc/accounting/card_table-inl.h" #include "indenter.h" #include "intern_table.h" @@ -560,6 +561,7 @@ SafeMap<uint32_t, std::set<uint32_t>>& MethodVerifier::FindStringInitMap() { bool MethodVerifier::Verify() { // Some older code doesn't correctly mark constructors as such. Test for this case by looking at // the name. + Runtime* runtime = Runtime::Current(); const DexFile::MethodId& method_id = dex_file_->GetMethodId(dex_method_idx_); const char* method_name = dex_file_->StringDataByIdx(method_id.name_idx_); bool instance_constructor_by_name = strcmp("<init>", method_name) == 0; @@ -628,9 +630,13 @@ bool MethodVerifier::Verify() { } } if ((class_def_->GetJavaAccessFlags() & kAccInterface) != 0) { - // Interface methods must be public and abstract. - if ((method_access_flags_ & (kAccPublic | kAccAbstract)) != (kAccPublic | kAccAbstract)) { - Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interface methods must be public and abstract"; + // Interface methods must be public and abstract (if default methods are disabled). + bool default_methods_supported = + runtime->AreExperimentalFlagsEnabled(ExperimentalFlags::kDefaultMethods); + uint32_t kRequired = kAccPublic | (default_methods_supported ? 0 : kAccAbstract); + if ((method_access_flags_ & kRequired) != kRequired) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interface methods must be public" + << (default_methods_supported ? "" : " and abstract"); return false; } // In addition to the above, interface methods must not be protected. @@ -657,10 +663,22 @@ bool MethodVerifier::Verify() { return false; } - // Only the static initializer may have code in an interface. if ((class_def_->GetJavaAccessFlags() & kAccInterface) != 0) { - // Interfaces may have static initializers for their fields. - if (!IsConstructor() || !IsStatic()) { + // Interfaces may always have static initializers for their fields. If we are running with + // default methods enabled we also allow other public, static, non-final methods to have code. + // Otherwise that is the only type of method allowed. + if (runtime->AreExperimentalFlagsEnabled(ExperimentalFlags::kDefaultMethods)) { + if (IsInstanceConstructor()) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-static constructor"; + return false; + } else if (method_access_flags_ & kAccFinal) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have final methods"; + return false; + } else if (!(method_access_flags_ & kAccPublic)) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-public members"; + return false; + } + } else if (!IsConstructor() || !IsStatic()) { Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interface methods must be abstract"; return false; } @@ -682,6 +700,7 @@ bool MethodVerifier::Verify() { << " regs=" << code_item_->registers_size_; return false; } + // Allocate and initialize an array to hold instruction data. insn_flags_.reset(new InstructionFlags[code_item_->insns_size_in_code_units_]()); // Run through the instructions and see if the width checks out. @@ -693,8 +712,8 @@ bool MethodVerifier::Verify() { // Perform code-flow analysis and return. result = result && VerifyCodeFlow(); // Compute information for compiler. - if (result && Runtime::Current()->IsCompiler()) { - result = Runtime::Current()->GetCompilerCallbacks()->MethodVerified(this); + if (result && runtime->IsCompiler()) { + result = runtime->GetCompilerCallbacks()->MethodVerified(this); } return result; } diff --git a/test/955-lambda-smali/run b/test/955-lambda-smali/run index 2aeca8c8fc..b7546801b9 100755 --- a/test/955-lambda-smali/run +++ b/test/955-lambda-smali/run @@ -15,4 +15,4 @@ # limitations under the License. # Ensure that the lambda experimental opcodes are turned on for dalvikvm and dex2oat -${RUN} "$@" --runtime-option -Xexperimental-lambdas -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental-lambdas +${RUN} "$@" --runtime-option -Xexperimental:lambdas -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:lambdas diff --git a/test/960-default-smali/build b/test/960-default-smali/build new file mode 100755 index 0000000000..c7866878e9 --- /dev/null +++ b/test/960-default-smali/build @@ -0,0 +1,33 @@ +#!/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 + +# Generate the smali Main.smali file or fail +./util-src/generate_smali.py ./smali + +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 + ${JAVAC} -implicit:none -d classes $(find src -name '*.java') +fi + +# Build the smali files and make a dex +${SMALI} -JXmx256m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') +zip "$TEST_NAME.jar" classes.dex diff --git a/test/960-default-smali/expected.txt b/test/960-default-smali/expected.txt new file mode 100644 index 0000000000..7671eed5de --- /dev/null +++ b/test/960-default-smali/expected.txt @@ -0,0 +1,84 @@ +Testing for type A +A-virtual A.SayHi()='Hi ' +A-interface Greeter.SayHi()='Hi ' +A-virtual A.SayHiTwice()='Hi Hi ' +A-interface Greeter.SayHiTwice()='Hi Hi ' +End testing for type A +Testing for type B +B-virtual B.SayHi()='Hello ' +B-interface Greeter.SayHi()='Hello ' +B-interface Greeter2.SayHi()='Hello ' +B-virtual B.SayHiTwice()='I say Hello Hello ' +B-interface Greeter.SayHiTwice()='I say Hello Hello ' +B-interface Greeter2.SayHiTwice()='I say Hello Hello ' +End testing for type B +Testing for type C +C-virtual A.SayHi()='Hi ' +C-virtual C.SayHi()='Hi ' +C-interface Greeter.SayHi()='Hi ' +C-virtual A.SayHiTwice()='You don't control me' +C-virtual C.SayHiTwice()='You don't control me' +C-interface Greeter.SayHiTwice()='You don't control me' +End testing for type C +Testing for type D +D-virtual D.GetName()='Alex ' +D-interface Greeter3.GetName()='Alex ' +D-virtual D.SayHi()='Hello Alex ' +D-interface Greeter.SayHi()='Hello Alex ' +D-interface Greeter3.SayHi()='Hello Alex ' +D-virtual D.SayHiTwice()='Hello Alex Hello Alex ' +D-interface Greeter.SayHiTwice()='Hello Alex Hello Alex ' +D-interface Greeter3.SayHiTwice()='Hello Alex Hello Alex ' +End testing for type D +Testing for type E +E-virtual A.SayHi()='Hi2 ' +E-virtual E.SayHi()='Hi2 ' +E-interface Greeter.SayHi()='Hi2 ' +E-interface Greeter2.SayHi()='Hi2 ' +E-virtual A.SayHiTwice()='I say Hi2 Hi2 ' +E-virtual E.SayHiTwice()='I say Hi2 Hi2 ' +E-interface Greeter.SayHiTwice()='I say Hi2 Hi2 ' +E-interface Greeter2.SayHiTwice()='I say Hi2 Hi2 ' +End testing for type E +Testing for type F +F-interface Attendant.GetPlace()='android' +F-virtual F.GetPlace()='android' +F-virtual A.SayHi()='Hi ' +F-interface Attendant.SayHi()='Hi ' +F-virtual F.SayHi()='Hi ' +F-interface Greeter.SayHi()='Hi ' +F-virtual A.SayHiTwice()='We can override both interfaces' +F-interface Attendant.SayHiTwice()='We can override both interfaces' +F-virtual F.SayHiTwice()='We can override both interfaces' +F-interface Greeter.SayHiTwice()='We can override both interfaces' +End testing for type F +Testing for type G +G-interface Attendant.GetPlace()='android' +G-virtual G.GetPlace()='android' +G-interface Attendant.SayHi()='welcome to android' +G-virtual G.SayHi()='welcome to android' +G-interface Attendant.SayHiTwice()='welcome to androidwelcome to android' +G-virtual G.SayHiTwice()='welcome to androidwelcome to android' +End testing for type G +Testing for type H +H-interface Extension.SayHi()='welcome ' +H-virtual H.SayHi()='welcome ' +End testing for type H +Testing for type I +I-virtual A.SayHi()='Hi ' +I-interface Greeter.SayHi()='Hi ' +I-interface Greeter2.SayHi()='Hi ' +I-virtual I.SayHi()='Hi ' +I-virtual A.SayHiTwice()='I say Hi Hi ' +I-interface Greeter.SayHiTwice()='I say Hi Hi ' +I-interface Greeter2.SayHiTwice()='I say Hi Hi ' +I-virtual I.SayHiTwice()='I say Hi Hi ' +End testing for type I +Testing for type J +J-virtual A.SayHi()='Hi ' +J-interface Greeter.SayHi()='Hi ' +J-virtual J.SayHi()='Hi ' +J-virtual A.SayHiTwice()='Hi Hi ' +J-interface Greeter.SayHiTwice()='Hi Hi ' +J-virtual J.SayHiTwice()='Hi Hi ' +End testing for type J diff --git a/test/960-default-smali/info.txt b/test/960-default-smali/info.txt new file mode 100644 index 0000000000..eb596e2c9f --- /dev/null +++ b/test/960-default-smali/info.txt @@ -0,0 +1,19 @@ +Smali-based tests for experimental interface default methods. + +Obviously needs to run under ART or a Java 8 Language runtime and compiler. + +When run a Main.smali file will be generated by the util-src/generate_smali.py +script. If we run with --jvm we will use the tools/extract-embedded-java script to +turn the smali into equivalent Java using the embedded Java code. + +When updating be sure to write the equivalent Java code in comments of the smali +files. + +Care should be taken when updating the generate_smali.py script. It must always +return equivalent output when run multiple times. + +To update the test files do the following steps: + <Add new classes/interfaces> + <Add these classes/interfaces to ./smali/classes.xml> + JAVA_HOME="/path/to/java-8-jdk" ../run-test --use-java-home --update --jvm --host 956-default-smali + git add ./smali/classes.xml ./expected.txt diff --git a/test/960-default-smali/run b/test/960-default-smali/run new file mode 100755 index 0000000000..e378b061d9 --- /dev/null +++ b/test/960-default-smali/run @@ -0,0 +1,21 @@ +#!/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. + +if echo $@ | grep -q -- "--jvm"; then + ${RUN} "$@" +else + ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods +fi diff --git a/test/960-default-smali/smali/A.smali b/test/960-default-smali/smali/A.smali new file mode 100644 index 0000000000..e755612fbe --- /dev/null +++ b/test/960-default-smali/smali/A.smali @@ -0,0 +1,38 @@ +# /* +# * 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. +# */ + +.class public LA; +.super Ljava/lang/Object; +.implements LGreeter; + +# class A implements Greeter { +# public String SayHi() { +# return "Hi "; +# } +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public SayHi()Ljava/lang/String; + .registers 1 + + const-string v0, "Hi " + return-object v0 +.end method diff --git a/test/960-default-smali/smali/Attendant.smali b/test/960-default-smali/smali/Attendant.smali new file mode 100644 index 0000000000..ab63aeefcb --- /dev/null +++ b/test/960-default-smali/smali/Attendant.smali @@ -0,0 +1,53 @@ +# /* +# * 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. +# */ + +.class public abstract interface LAttendant; +.super Ljava/lang/Object; + +# public interface Attendant { +# public default String SayHi() { +# return "welcome to " + GetPlace(); +# } +# public default String SayHiTwice() { +# return SayHi() + SayHi(); +# } +# +# public String GetPlace(); +# } + +.method public SayHi()Ljava/lang/String; + .locals 2 + const-string v0, "welcome to " + invoke-interface {p0}, LAttendant;->GetPlace()Ljava/lang/String; + move-result-object v1 + invoke-virtual {v0, v1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + return-object v0 +.end method + +.method public SayHiTwice()Ljava/lang/String; + .locals 2 + invoke-interface {p0}, LAttendant;->SayHi()Ljava/lang/String; + move-result-object v0 + invoke-interface {p0}, LAttendant;->SayHi()Ljava/lang/String; + move-result-object v1 + invoke-virtual {v0, v1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + return-object v0 +.end method + +.method public abstract GetPlace()Ljava/lang/String; +.end method diff --git a/test/960-default-smali/smali/B.smali b/test/960-default-smali/smali/B.smali new file mode 100644 index 0000000000..d847dd12ff --- /dev/null +++ b/test/960-default-smali/smali/B.smali @@ -0,0 +1,38 @@ +# /* +# * 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. +# */ + +.class public LB; +.super Ljava/lang/Object; +.implements LGreeter2; + +# class B implements Greeter2 { +# public String SayHi() { +# return "Hello "; +# } +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public SayHi()Ljava/lang/String; + .registers 1 + + const-string v0, "Hello " + return-object v0 +.end method diff --git a/test/960-default-smali/smali/C.smali b/test/960-default-smali/smali/C.smali new file mode 100644 index 0000000000..08a8508be1 --- /dev/null +++ b/test/960-default-smali/smali/C.smali @@ -0,0 +1,37 @@ +# /* +# * 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. +# */ + +.class public LC; +.super LA; + +# class C extends A { +# public String SayHiTwice() { +# return "You don't control me"; +# } +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, LA;-><init>()V + return-void +.end method + +.method public SayHiTwice()Ljava/lang/String; + .registers 1 + + const-string v0, "You don't control me" + return-object v0 +.end method diff --git a/test/960-default-smali/smali/D.smali b/test/960-default-smali/smali/D.smali new file mode 100644 index 0000000000..32f3b7ec8b --- /dev/null +++ b/test/960-default-smali/smali/D.smali @@ -0,0 +1,38 @@ +# /* +# * 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. +# */ + +.class public LD; +.super Ljava/lang/Object; +.implements LGreeter3; + +# class D implements Greeter3 { +# public String GetName() { +# return "Alex "; +# } +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public GetName()Ljava/lang/String; + .registers 1 + + const-string v0, "Alex " + return-object v0 +.end method diff --git a/test/960-default-smali/smali/E.smali b/test/960-default-smali/smali/E.smali new file mode 100644 index 0000000000..bae6250414 --- /dev/null +++ b/test/960-default-smali/smali/E.smali @@ -0,0 +1,38 @@ +# /* +# * 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. +# */ + +.class public LE; +.super LA; +.implements LGreeter2; + +# class E extends A implements Greeter2 { +# public String SayHi() { +# return "Hi2 "; +# } +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, LA;-><init>()V + return-void +.end method + +.method public SayHi()Ljava/lang/String; + .registers 1 + + const-string v0, "Hi2 " + return-object v0 +.end method diff --git a/test/960-default-smali/smali/Extension.smali b/test/960-default-smali/smali/Extension.smali new file mode 100644 index 0000000000..60ffa26ec6 --- /dev/null +++ b/test/960-default-smali/smali/Extension.smali @@ -0,0 +1,30 @@ +# /* +# * 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. +# */ + +.class public abstract interface LExtension; +.super Ljava/lang/Object; + +# public interface Extension { +# public default String SayHi() { +# return "welcome "; +# } +# } + +.method public SayHi()Ljava/lang/String; + .locals 1 + const-string v0, "welcome " + return-object v0 +.end method diff --git a/test/960-default-smali/smali/F.smali b/test/960-default-smali/smali/F.smali new file mode 100644 index 0000000000..3eaa089e1f --- /dev/null +++ b/test/960-default-smali/smali/F.smali @@ -0,0 +1,47 @@ +# /* +# * 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. +# */ + +.class public LF; +.super LA; +.implements LAttendant; + +# class F extends A implements Attendant { +# public String GetPlace() { +# return "android"; +# } +# public String SayHiTwice() { +# return "We can override both interfaces"; +# } +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public SayHiTwice()Ljava/lang/String; + .registers 1 + + const-string v0, "We can override both interfaces" + return-object v0 +.end method + +.method public GetPlace()Ljava/lang/String; + .registers 1 + const-string v0, "android" + return-object v0 +.end method diff --git a/test/960-default-smali/smali/G.smali b/test/960-default-smali/smali/G.smali new file mode 100644 index 0000000000..446f2a4c64 --- /dev/null +++ b/test/960-default-smali/smali/G.smali @@ -0,0 +1,37 @@ +# /* +# * 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. +# */ + +.class public LG; +.super Ljava/lang/Object; +.implements LAttendant; + +# class G implements Attendant { +# public String GetPlace() { +# return "android"; +# } +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public GetPlace()Ljava/lang/String; + .registers 1 + const-string v0, "android" + return-object v0 +.end method diff --git a/test/960-default-smali/smali/Greeter.smali b/test/960-default-smali/smali/Greeter.smali new file mode 100644 index 0000000000..28530ffc6f --- /dev/null +++ b/test/960-default-smali/smali/Greeter.smali @@ -0,0 +1,40 @@ +# /* +# * 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. +# */ + +.class public abstract interface LGreeter; +.super Ljava/lang/Object; + +# public interface Greeter { +# public String SayHi(); +# +# public default String SayHiTwice() { +# return SayHi() + SayHi(); +# } +# } + +.method public abstract SayHi()Ljava/lang/String; +.end method + +.method public SayHiTwice()Ljava/lang/String; + .locals 2 + invoke-interface {p0}, LGreeter;->SayHi()Ljava/lang/String; + move-result-object v0 + invoke-interface {p0}, LGreeter;->SayHi()Ljava/lang/String; + move-result-object v1 + invoke-virtual {v0, v1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + return-object v0 +.end method diff --git a/test/960-default-smali/smali/Greeter2.smali b/test/960-default-smali/smali/Greeter2.smali new file mode 100644 index 0000000000..ace1798bab --- /dev/null +++ b/test/960-default-smali/smali/Greeter2.smali @@ -0,0 +1,39 @@ +# /* +# * 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. +# */ + +.class public abstract interface LGreeter2; +.super Ljava/lang/Object; +.implements LGreeter; + +# public interface Greeter2 extends Greeter { +# public default String SayHiTwice() { +# return "I say " + SayHi() + SayHi(); +# } +# } + +.method public SayHiTwice()Ljava/lang/String; + .locals 3 + const-string v0, "I say " + invoke-interface {p0}, LGreeter;->SayHi()Ljava/lang/String; + move-result-object v1 + invoke-virtual {v0, v1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + invoke-interface {p0}, LGreeter;->SayHi()Ljava/lang/String; + move-result-object v1 + invoke-virtual {v0, v1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + return-object v0 +.end method diff --git a/test/960-default-smali/smali/Greeter3.smali b/test/960-default-smali/smali/Greeter3.smali new file mode 100644 index 0000000000..31fc2e79ff --- /dev/null +++ b/test/960-default-smali/smali/Greeter3.smali @@ -0,0 +1,40 @@ +# /* +# * 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. +# */ + +.class public abstract interface LGreeter3; +.super Ljava/lang/Object; +.implements LGreeter; + +# public interface Greeter3 extends Greeter { +# public String GetName(); +# +# public default String SayHi() { +# return "Hello " + GetName(); +# } +# } + +.method public abstract GetName()Ljava/lang/String; +.end method + +.method public SayHi()Ljava/lang/String; + .locals 2 + const-string v0, "Hello " + invoke-interface {p0}, LGreeter3;->GetName()Ljava/lang/String; + move-result-object v1 + invoke-virtual {v0, v1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + return-object v0 +.end method diff --git a/test/960-default-smali/smali/H.smali b/test/960-default-smali/smali/H.smali new file mode 100644 index 0000000000..82065ea49d --- /dev/null +++ b/test/960-default-smali/smali/H.smali @@ -0,0 +1,28 @@ +# /* +# * 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. +# */ + +.class public LH; +.super Ljava/lang/Object; +.implements LExtension; + +# class H implements Extension { +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method diff --git a/test/960-default-smali/smali/I.smali b/test/960-default-smali/smali/I.smali new file mode 100644 index 0000000000..72fb58afe4 --- /dev/null +++ b/test/960-default-smali/smali/I.smali @@ -0,0 +1,28 @@ +# /* +# * 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. +# */ + +.class public LI; +.super LA; +.implements LGreeter2; + +# class I extends A implements Greeter2 { +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method diff --git a/test/960-default-smali/smali/J.smali b/test/960-default-smali/smali/J.smali new file mode 100644 index 0000000000..93f3d6231c --- /dev/null +++ b/test/960-default-smali/smali/J.smali @@ -0,0 +1,29 @@ +# /* +# * 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. +# */ + +.class public LJ; +.super LA; + +# class J extends A { +# } + + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, LA;-><init>()V + return-void +.end method + diff --git a/test/960-default-smali/smali/classes.xml b/test/960-default-smali/smali/classes.xml new file mode 100644 index 0000000000..0aa41f7fb6 --- /dev/null +++ b/test/960-default-smali/smali/classes.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<data> + <classes> + <class name="A" super="java/lang/Object"> + <implements> + <item>Greeter</item> + </implements> + <methods> </methods> + </class> + + <class name="B" super="java/lang/Object"> + <implements> + <item>Greeter2</item> + </implements> + <methods> </methods> + </class> + + <class name="C" super="A"> + <implements> </implements> + <methods> </methods> + </class> + + <class name="D" super="java/lang/Object"> + <implements> + <item>Greeter3</item> + </implements> + <methods> </methods> + </class> + + <class name="E" super="A"> + <implements> + <item>Greeter2</item> + </implements> + <methods> </methods> + </class> + + <class name="F" super="A"> + <implements> + <item>Attendant</item> + </implements> + <methods> </methods> + </class> + + <class name="G" super="java/lang/Object"> + <implements> + <item>Attendant</item> + </implements> + <methods> </methods> + </class> + + <class name="H" super="java/lang/Object"> + <implements> + <item>Extension</item> + </implements> + <methods> </methods> + </class> + + <class name="I" super="A"> + <implements> + <item>Greeter2</item> + </implements> + <methods> </methods> + </class> + + <class name="J" super="A"> + <implements> </implements> + <methods> </methods> + </class> + </classes> + + <interfaces> + <interface name="Extension" super="java/lang/Object"> + <implements> </implements> + <methods> + <method type="default">SayHi</method> + </methods> + </interface> + + <interface name="Greeter" super="java/lang/Object"> + <implements> </implements> + <methods> + <method type="abstract">SayHi</method> + <method type="default">SayHiTwice</method> + </methods> + </interface> + + <interface name="Greeter2" super="java/lang/Object"> + <implements> + <item>Greeter</item> + </implements> + <methods> </methods> + </interface> + + <interface name="Greeter3" super="java/lang/Object"> + <implements> + <item>Greeter</item> + </implements> + <methods> + <method type="abstract">GetName</method> + </methods> + </interface> + + <interface name="Attendant" super="java/lang/Object"> + <implements> </implements> + <methods> + <method type="default">SayHi</method> + <method type="default">SayHiTwice</method> + <method type="abstract">GetPlace</method> + </methods> + </interface> + </interfaces> +</data> diff --git a/test/960-default-smali/util-src/generate_smali.py b/test/960-default-smali/util-src/generate_smali.py new file mode 100755 index 0000000000..b2bf1f0761 --- /dev/null +++ b/test/960-default-smali/util-src/generate_smali.py @@ -0,0 +1,376 @@ +#!/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 Main file for test 960 +""" + +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 +import testgen.mixins as mixins + +from collections import namedtuple +import itertools +import functools +import xml.etree.ElementTree as ET + +class MainClass(mixins.DumpMixin, mixins.Named, mixins.SmaliFileMixin): + """ + A mainclass and main method for this test. + """ + + 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_groups} + +{test_funcs} + +{main_func} + +# }} +""" + + MAIN_FUNCTION_TEMPLATE = """ +# public static void main(String[] args) {{ +.method public static main([Ljava/lang/String;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + + {test_group_invoke} + + return-void +.end method +# }} +""" + + TEST_GROUP_INVOKE_TEMPLATE = """ +# {test_name}(); + invoke-static {{}}, {test_name}()V +""" + + def __init__(self): + """ + Initialize this MainClass + """ + self.tests = set() + self.global_funcs = set() + + def add_instance(self, it): + """ + Add an instance test for the given class + """ + self.tests.add(it) + + def add_func(self, f): + """ + Add a function to the class + """ + self.global_funcs.add(f) + + def get_name(self): + """ + Get the name of this class + """ + return "Main" + + def __str__(self): + """ + Print this class + """ + all_tests = sorted(self.tests) + test_invoke = "" + test_groups = "" + for t in all_tests: + test_groups += str(t) + for t in sorted(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) + + funcs = "" + for f in self.global_funcs: + funcs += str(f) + return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright('smali'), + test_groups=test_groups, + main_func=main_func, test_funcs=funcs) + + +class InstanceTest(mixins.Named, mixins.NameComparableMixin): + """ + A method that runs tests for a particular concrete type, It calls the test + cases for running it in all possible ways. + """ + + INSTANCE_TEST_TEMPLATE = """ +# public static void {test_name}() {{ +# System.out.println("Testing for type {ty}"); +# String s = "{ty}"; +# {ty} v = new {ty}(); +.method public static {test_name}()V + .locals 3 + sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Testing for type {ty}" + invoke-virtual {{v2,v0}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "{ty}" + new-instance v1, L{ty}; + invoke-direct {{v1}}, L{ty};-><init>()V + + {invokes} + + const-string v0, "End testing for type {ty}" + invoke-virtual {{v2,v0}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void +.end method +# System.out.println("End testing for type {ty}"); +# }} +""" + + TEST_INVOKE_TEMPLATE = """ +# {fname}(s, v); + invoke-static {{v0, v1}}, {fname}(Ljava/lang/String;L{farg};)V +""" + + def __init__(self, main, ty): + """ + Initialize this test group for the given type + """ + self.ty = ty + self.main = main + self.funcs = set() + self.main.add_instance(self) + + def get_name(self): + """ + Get the name of this test group + """ + return "TEST_NAME_"+self.ty + + def add_func(self, f): + """ + Add a test function to this test group + """ + self.main.add_func(f) + self.funcs.add(f) + + def __str__(self): + """ + Returns the smali code for this function + """ + func_invokes = "" + for f in sorted(self.funcs, key=lambda a: (a.func, a.farg)): + func_invokes += self.TEST_INVOKE_TEMPLATE.format(fname=f.get_name(), + farg=f.farg) + + return self.INSTANCE_TEST_TEMPLATE.format(test_name=self.get_name(), ty=self.ty, + invokes=func_invokes) + +class Func(mixins.Named, mixins.NameComparableMixin): + """ + A single test case that attempts to invoke a function on receiver of a given type. + """ + + TEST_FUNCTION_TEMPLATE = """ +# public static void {fname}(String s, {farg} v) {{ +# try {{ +# System.out.printf("%s-{invoke_type:<9} {farg:>9}.{callfunc}()='%s'\\n", s, v.{callfunc}()); +# return; +# }} catch (Error e) {{ +# System.out.printf("%s-{invoke_type} on {farg}: {callfunc}() threw exception!\\n", s); +# e.printStackTrace(System.out); +# }} +# }} +.method public static {fname}(Ljava/lang/String;L{farg};)V + .locals 7 + :call_{fname}_try_start + const/4 v0, 2 + new-array v1,v0, [Ljava/lang/Object; + const/4 v0, 0 + aput-object p0,v1,v0 + + sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v3, "%s-{invoke_type:<9} {farg:>9}.{callfunc}()='%s'\\n" + + invoke-{invoke_type} {{p1}}, L{farg};->{callfunc}()Ljava/lang/String; + move-result-object v4 + const/4 v0, 1 + aput-object v4, v1, v0 + + invoke-virtual {{v2,v3,v1}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; + return-void + :call_{fname}_try_end + .catch Ljava/lang/Error; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start + :error_{fname}_start + move-exception v3 + const/4 v0, 1 + new-array v1,v0, [Ljava/lang/Object; + const/4 v0, 0 + aput-object p0, v1, v0 + sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v4, "%s-{invoke_type} on {farg}: {callfunc}() threw exception!\\n" + invoke-virtual {{v2,v4,v1}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; + invoke-virtual {{v3,v2}}, Ljava/lang/Error;->printStackTrace(Ljava/io/PrintStream;)V + return-void +.end method +""" + + def __init__(self, func, farg, invoke): + """ + Initialize this test function for the given invoke type and argument + """ + self.func = func + self.farg = farg + self.invoke = invoke + + def get_name(self): + """ + Get the name of this test + """ + return "Test_Func_{}_{}_{}".format(self.func, self.farg, self.invoke) + + def __str__(self): + """ + Get the smali code for this test function + """ + return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(), + farg=self.farg, + invoke_type=self.invoke, + callfunc=self.func) + +def flatten_classes(classes, c): + """ + Iterate over all the classes 'c' can be used as + """ + while c: + yield c + c = classes.get(c.super_class) + +def flatten_class_methods(classes, c): + """ + Iterate over all the methods 'c' can call + """ + for c1 in flatten_classes(classes, c): + yield from c1.methods + +def flatten_interfaces(dat, c): + """ + Iterate over all the interfaces 'c' transitively implements + """ + def get_ifaces(cl): + for i2 in cl.implements: + yield dat.interfaces[i2] + yield from get_ifaces(dat.interfaces[i2]) + + for cl in flatten_classes(dat.classes, c): + yield from get_ifaces(cl) + +def flatten_interface_methods(dat, i): + """ + Iterate over all the interface methods 'c' can call + """ + yield from i.methods + for i2 in flatten_interfaces(dat, i): + yield from i2.methods + +def make_main_class(dat): + """ + Creates a Main.smali file that runs all the tests + """ + m = MainClass() + for c in dat.classes.values(): + i = InstanceTest(m, c.name) + for clazz in flatten_classes(dat.classes, c): + for meth in flatten_class_methods(dat.classes, clazz): + i.add_func(Func(meth, clazz.name, 'virtual')) + for iface in flatten_interfaces(dat, clazz): + for meth in flatten_interface_methods(dat, iface): + i.add_func(Func(meth, clazz.name, 'virtual')) + i.add_func(Func(meth, iface.name, 'interface')) + return m + +class TestData(namedtuple("TestData", ['classes', 'interfaces'])): + """ + A class representing the classes.xml document. + """ + pass + +class Clazz(namedtuple("Clazz", ["name", "methods", "super_class", "implements"])): + """ + A class representing a class element in the classes.xml document. + """ + pass + +class IFace(namedtuple("IFace", ["name", "methods", "super_class", "implements"])): + """ + A class representing an interface element in the classes.xml document. + """ + pass + +def parse_xml(xml): + """ + Parse the xml description of this test. + """ + classes = dict() + ifaces = dict() + root = ET.fromstring(xml) + for iface in root.find("interfaces"): + name = iface.attrib['name'] + implements = [a.text for a in iface.find("implements")] + methods = [a.text for a in iface.find("methods")] + ifaces[name] = IFace(name = name, + super_class = iface.attrib['super'], + methods = methods, + implements = implements) + for clazz in root.find('classes'): + name = clazz.attrib['name'] + implements = [a.text for a in clazz.find("implements")] + methods = [a.text for a in clazz.find("methods")] + classes[name] = Clazz(name = name, + super_class = clazz.attrib['super'], + methods = methods, + implements = implements) + return TestData(classes, ifaces) + +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) + class_data = parse_xml((smali_dir / "classes.xml").open().read()) + make_main_class(class_data).dump(smali_dir) + +if __name__ == '__main__': + main(sys.argv) diff --git a/test/961-default-iface-resolution-generated/build b/test/961-default-iface-resolution-generated/build new file mode 100755 index 0000000000..707c17e1cf --- /dev/null +++ b/test/961-default-iface-resolution-generated/build @@ -0,0 +1,47 @@ +#!/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 + +mkdir -p ./smali + +# 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 + +# Generate the smali files and expected.txt or fail +./util-src/generate_smali.py ./smali ./expected.txt + +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 + ${JAVAC} -implicit:none -d classes $(find src -name '*.java') +fi + +# Build the smali files and make a dex +${SMALI} -JXmx512m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') +zip $TEST_NAME.jar classes.dex + +# Reset the ulimit back to its initial value +restore_ulimit diff --git a/test/961-default-iface-resolution-generated/expected.txt b/test/961-default-iface-resolution-generated/expected.txt new file mode 100644 index 0000000000..1ddd65d177 --- /dev/null +++ b/test/961-default-iface-resolution-generated/expected.txt @@ -0,0 +1 @@ +This file is generated by util-src/generate_smali.py do not directly modify! diff --git a/test/961-default-iface-resolution-generated/info.txt b/test/961-default-iface-resolution-generated/info.txt new file mode 100644 index 0000000000..2cd2cc75b7 --- /dev/null +++ b/test/961-default-iface-resolution-generated/info.txt @@ -0,0 +1,17 @@ +Smali-based tests for experimental interface default methods. + +This tests that interface method resolution order is correct. + +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 +$(ANDROID_BUILD_TOP)/art/tools/extract-embedded-java script to turn the smali +into equivalent Java using the embedded Java 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/961-default-iface-resolution-generated/run b/test/961-default-iface-resolution-generated/run new file mode 100755 index 0000000000..e378b061d9 --- /dev/null +++ b/test/961-default-iface-resolution-generated/run @@ -0,0 +1,21 @@ +#!/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. + +if echo $@ | grep -q -- "--jvm"; then + ${RUN} "$@" +else + ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods +fi diff --git a/test/961-default-iface-resolution-generated/util-src/generate_smali.py b/test/961-default-iface-resolution-generated/util-src/generate_smali.py new file mode 100755 index 0000000000..921a096dd3 --- /dev/null +++ b/test/961-default-iface-resolution-generated/util-src/generate_smali.py @@ -0,0 +1,466 @@ +#!/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 961. +""" + +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 functools import total_ordering +import itertools +import string + +# The max depth the type tree can have. Includes the class object in the tree. +# Increasing this increases the number of generated files significantly. This +# value was chosen as it is fairly quick to run and very comprehensive, checking +# every possible interface tree up to 5 layers deep. +MAX_IFACE_DEPTH = 5 + +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_groups} + +{main_func} + +# }} +""" + + MAIN_FUNCTION_TEMPLATE = """ +# public static void main(String[] args) {{ +.method public static main([Ljava/lang/String;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + + {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_groups = "" + for t in all_tests: + test_groups += 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_groups = test_groups, + 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}() {{ +# try {{ +# {farg} v = new {farg}(); +# System.out.printf("%s calls default method on %s\\n", +# v.CalledClassName(), +# v.CalledInterfaceName()); +# return; +# }} catch (Error e) {{ +# e.printStackTrace(System.out); +# return; +# }} +# }} +.method public static {fname}()V + .locals 7 + :call_{fname}_try_start + new-instance v6, L{farg}; + invoke-direct {{v6}}, L{farg};-><init>()V + + const/4 v0, 2 + new-array v1,v0, [Ljava/lang/Object; + const/4 v0, 0 + invoke-virtual {{v6}}, L{farg};->CalledClassName()Ljava/lang/String; + move-result-object v4 + aput-object v4,v1,v0 + + sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v3, "%s calls default method on %s\\n" + + invoke-virtual {{v6}}, L{farg};->CalledInterfaceName()Ljava/lang/String; + move-result-object v4 + const/4 v0, 1 + aput-object v4, v1, v0 + + invoke-virtual {{v2,v3,v1}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; + return-void + :call_{fname}_try_end + .catch Ljava/lang/Error; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start + :error_{fname}_start + move-exception v3 + sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {{v3,v2}}, Ljava/lang/Error;->printStackTrace(Ljava/io/PrintStream;)V + return-void +.end method +""" + + 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. + """ + return "{tree} calls default method on {iface_tree}".format( + tree = self.farg.get_tree(), iface_tree = self.farg.get_called().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(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} {{ +# public String CalledClassName() {{ +# return "{tree}"; +# }} +# }} + +.method public constructor <init>()V + .registers 1 + invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public CalledClassName()Ljava/lang/String; + .locals 1 + const-string v0, "{tree}" + return-object v0 +.end method +""" + + 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): + """ + Get the interface whose default method would be called when calling the + CalledInterfaceName function. + """ + all_ifaces = set(iface for iface in self if iface.default) + for i in all_ifaces: + if all(map(lambda j: i not in j.get_super_types(), all_ifaces)): + return i + raise Exception("UNREACHABLE! Unable to find default method!") + + 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) + +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} {{ +# public String CalledClassName(); +.method public abstract CalledClassName()Ljava/lang/String; +.end method + +{funcs} + +# }} +""" + + DEFAULT_FUNC_TEMPLATE = """ +# public default String CalledInterfaceName() {{ +# return "{tree}"; +# }} +.method public CalledInterfaceName()Ljava/lang/String; + .locals 1 + const-string v0, "{tree}" + return-object v0 +.end method +""" + + IMPLEMENTS_TEMPLATE = """ +.implements L{iface_name}; +""" + + def __init__(self, ifaces, default): + """ + Initialize interface with the given super-interfaces + """ + self.ifaces = sorted(ifaces) + self.default = default + end = "_DEFAULT" if default else "" + self.class_name = "INTERFACE_"+gensym()+end + + def get_super_types(self): + """ + Returns a set of all the supertypes of this interface + """ + return set(i2 for i2 in self) + + 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.default: + funcs = self.DEFAULT_FUNC_TEMPLATE.format(ifaces = j_ifaces, + tree = self.get_tree(), + class_name = self.class_name) + 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_interface_trees(): + """ + Return all legal interface trees + """ + def dump_supers(s): + """ + Does depth first traversal of all the interfaces in the list. + """ + for i in s: + yield i + yield from i + + def create_interface_trees_inner(num, allow_default): + for split in SUBTREES[num]: + ifaces = [] + for sub in split: + if sub == 1: + ifaces.append([TestInterface([], allow_default)]) + if allow_default: + ifaces[-1].append(TestInterface([], False)) + else: + ifaces.append(list(create_interface_trees_inner(sub, allow_default))) + for supers in itertools.product(*ifaces): + all_supers = sorted(set(dump_supers(supers)) - set(supers)) + for i in range(len(all_supers) + 1): + for combo in itertools.combinations(all_supers, i): + yield TestInterface(list(combo) + list(supers), allow_default) + if allow_default: + for i in range(len(split)): + ifaces = [] + for sub, cs in zip(split, itertools.count()): + if sub == 1: + ifaces.append([TestInterface([], i == cs)]) + else: + ifaces.append(list(create_interface_trees_inner(sub, i == cs))) + for supers in itertools.product(*ifaces): + all_supers = sorted(set(dump_supers(supers)) - set(supers)) + for i in range(len(all_supers) + 1): + for combo in itertools.combinations(all_supers, i): + yield TestInterface(list(combo) + list(supers), False) + + for num in range(1, MAX_IFACE_DEPTH): + yield from create_interface_trees_inner(num, True) + +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 tree in create_interface_trees(): + classes.add(tree) + for i in tree: + classes.add(i) + test_class = TestClass(tree) + mc.add_test(test_class) + classes.add(test_class) + 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/962-iface-static/build b/test/962-iface-static/build new file mode 100755 index 0000000000..5ad82f70d1 --- /dev/null +++ b/test/962-iface-static/build @@ -0,0 +1,30 @@ +#!/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 + +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 + ${JAVAC} -implicit:none -d classes $(find src -name '*.java') +fi + +# Build the smali files and make a dex +${SMALI} -JXmx512m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') +zip $TEST_NAME.jar classes.dex diff --git a/test/962-iface-static/expected.txt b/test/962-iface-static/expected.txt new file mode 100644 index 0000000000..6d98ea1571 --- /dev/null +++ b/test/962-iface-static/expected.txt @@ -0,0 +1,3 @@ +init +constructor +Hello diff --git a/test/962-iface-static/info.txt b/test/962-iface-static/info.txt new file mode 100644 index 0000000000..d4732e533d --- /dev/null +++ b/test/962-iface-static/info.txt @@ -0,0 +1,4 @@ +Smali-based tests for experimental interface static methods. + +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/962-iface-static/run b/test/962-iface-static/run new file mode 100755 index 0000000000..e713708c18 --- /dev/null +++ b/test/962-iface-static/run @@ -0,0 +1,21 @@ +#!/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. + +if echo $@ | grep -q -- "--jvm"; then + ${RUN} "$@" +else + ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods +fi diff --git a/test/962-iface-static/smali/Displayer.smali b/test/962-iface-static/smali/Displayer.smali new file mode 100644 index 0000000000..06bec16432 --- /dev/null +++ b/test/962-iface-static/smali/Displayer.smali @@ -0,0 +1,45 @@ +# /* +# * 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 class Displayer { +# static { +# System.out.println("init"); +# } +# +# public Displayer() { +# System.out.println("constructor"); +# } +# } + +.class public LDisplayer; +.super Ljava/lang/Object; + +.method public static <clinit>()V + .locals 3 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "init" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void +.end method + +.method public constructor <init>()V + .locals 2 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "constructor" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void +.end method diff --git a/test/962-iface-static/smali/Main.smali b/test/962-iface-static/smali/Main.smali new file mode 100644 index 0000000000..72fa5e0e6e --- /dev/null +++ b/test/962-iface-static/smali/Main.smali @@ -0,0 +1,40 @@ +# /* +# * 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 { +# public static void main(String[] args) { +# System.out.println(iface.SayHi()); +# } +# } +.class public LMain; +.super Ljava/lang/Object; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + + invoke-static {}, Liface;->SayHi()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/962-iface-static/smali/iface.smali b/test/962-iface-static/smali/iface.smali new file mode 100644 index 0000000000..441aae669e --- /dev/null +++ b/test/962-iface-static/smali/iface.smali @@ -0,0 +1,43 @@ +# /* +# * 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 static final Displayer f = new Displayer(); +# +# public static String SayHi() { +# return "Hello"; +# } +# } + +.class public abstract interface Liface; +.super Ljava/lang/Object; + +.field public final static f:LDisplayer; + +.method public static <clinit>()V + .locals 3 + new-instance v1, LDisplayer; + invoke-direct {v1}, LDisplayer;-><init>()V + sput-object v1, Liface;->f:LDisplayer; + return-void +.end method + +.method public static SayHi()Ljava/lang/String; + .locals 1 + const-string v0, "Hello" + return-object v0 +.end method + diff --git a/test/963-default-range-smali/build b/test/963-default-range-smali/build new file mode 100755 index 0000000000..5ad82f70d1 --- /dev/null +++ b/test/963-default-range-smali/build @@ -0,0 +1,30 @@ +#!/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 + +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 + ${JAVAC} -implicit:none -d classes $(find src -name '*.java') +fi + +# Build the smali files and make a dex +${SMALI} -JXmx512m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') +zip $TEST_NAME.jar classes.dex diff --git a/test/963-default-range-smali/expected.txt b/test/963-default-range-smali/expected.txt new file mode 100644 index 0000000000..af17d2f873 --- /dev/null +++ b/test/963-default-range-smali/expected.txt @@ -0,0 +1,2 @@ +Hello +Hello diff --git a/test/963-default-range-smali/info.txt b/test/963-default-range-smali/info.txt new file mode 100644 index 0000000000..d4732e533d --- /dev/null +++ b/test/963-default-range-smali/info.txt @@ -0,0 +1,4 @@ +Smali-based tests for experimental interface static methods. + +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/963-default-range-smali/run b/test/963-default-range-smali/run new file mode 100755 index 0000000000..e713708c18 --- /dev/null +++ b/test/963-default-range-smali/run @@ -0,0 +1,21 @@ +#!/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. + +if echo $@ | grep -q -- "--jvm"; then + ${RUN} "$@" +else + ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods +fi diff --git a/test/963-default-range-smali/smali/A.smali b/test/963-default-range-smali/smali/A.smali new file mode 100644 index 0000000000..b3d91dd76b --- /dev/null +++ b/test/963-default-range-smali/smali/A.smali @@ -0,0 +1,29 @@ +# /* +# * 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. +# */ + +.class public LA; +.super Ljava/lang/Object; +.implements Liface; + +# class A implements iface { +# } + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + diff --git a/test/963-default-range-smali/smali/Main.smali b/test/963-default-range-smali/smali/Main.smali new file mode 100644 index 0000000000..400fba72d9 --- /dev/null +++ b/test/963-default-range-smali/smali/Main.smali @@ -0,0 +1,77 @@ +# /* +# * 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 { +# public static void main(String[] args) { +# A a = new A(); +# System.out.println(a.SayHi("a string 0", +# "a string 1", +# "a string 2", +# "a string 3", +# "a string 4", +# "a string 5", +# "a string 6", +# "a string 7", +# "a string 8", +# "a string 9")); +# iface b = (iface)a; +# System.out.println(b.SayHi("a string 0", +# "a string 1", +# "a string 2", +# "a string 3", +# "a string 4", +# "a string 5", +# "a string 6", +# "a string 7", +# "a string 8", +# "a string 9")); +# } +# } +.class public LMain; +.super Ljava/lang/Object; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 15 + sget-object v12, Ljava/lang/System;->out:Ljava/io/PrintStream; + + new-instance v1, LA; + invoke-direct {v1}, LA;-><init>()V + const-string v2, "a string 0" + const-string v3, "a string 1" + const-string v4, "a string 2" + const-string v5, "a string 3" + const-string v6, "a string 4" + const-string v7, "a string 5" + const-string v8, "a string 6" + const-string v9, "a string 7" + const-string v10, "a string 8" + const-string v11, "a string 9" + invoke-virtual/range {v1 .. v11}, LA;->SayHi(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + invoke-virtual {v12,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface/range {v1 .. v11}, Liface;->SayHi(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + invoke-virtual {v12,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method diff --git a/test/963-default-range-smali/smali/iface.smali b/test/963-default-range-smali/smali/iface.smali new file mode 100644 index 0000000000..c2c3ce69a7 --- /dev/null +++ b/test/963-default-range-smali/smali/iface.smali @@ -0,0 +1,40 @@ +# /* +# * 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(String n1, +# String n2, +# String n3, +# String n4, +# String n5, +# String n6, +# String n7, +# String n8, +# String n9, +# String n0) { +# return "Hello"; +# } +# } + +.class public abstract interface Liface; +.super Ljava/lang/Object; + +.method public SayHi(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + .locals 1 + const-string v0, "Hello" + return-object v0 +.end method + diff --git a/test/964-default-iface-init-generated/build b/test/964-default-iface-init-generated/build new file mode 100755 index 0000000000..deef803813 --- /dev/null +++ b/test/964-default-iface-init-generated/build @@ -0,0 +1,45 @@ +#!/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 + +# Generate the smali files and expected.txt or fail +./util-src/generate_smali.py ./smali ./expected.txt + +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 + ${JAVAC} -implicit:none -d classes $(find src -name '*.java') +fi + +# Build the smali files and make a dex +${SMALI} -JXmx512m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') +zip $TEST_NAME.jar classes.dex + +# Reset the ulimit back to its initial value +restore_ulimit diff --git a/test/964-default-iface-init-generated/expected.txt b/test/964-default-iface-init-generated/expected.txt new file mode 100644 index 0000000000..1ddd65d177 --- /dev/null +++ b/test/964-default-iface-init-generated/expected.txt @@ -0,0 +1 @@ +This file is generated by util-src/generate_smali.py do not directly modify! diff --git a/test/964-default-iface-init-generated/info.txt b/test/964-default-iface-init-generated/info.txt new file mode 100644 index 0000000000..5805a86854 --- /dev/null +++ b/test/964-default-iface-init-generated/info.txt @@ -0,0 +1,17 @@ +Smali-based tests for interface initialization. + +This tests that interface initialization order is correct. + +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 +$(ANDROID_BUILD_TOP)/art/tools/extract-embedded-java script to turn the smali +into equivalent Java using the embedded Java 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/964-default-iface-init-generated/run b/test/964-default-iface-init-generated/run new file mode 100755 index 0000000000..e378b061d9 --- /dev/null +++ b/test/964-default-iface-init-generated/run @@ -0,0 +1,21 @@ +#!/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. + +if echo $@ | grep -q -- "--jvm"; then + ${RUN} "$@" +else + ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods +fi diff --git a/test/964-default-iface-init-generated/smali/Displayer.smali b/test/964-default-iface-init-generated/smali/Displayer.smali new file mode 100644 index 0000000000..91280a8a42 --- /dev/null +++ b/test/964-default-iface-init-generated/smali/Displayer.smali @@ -0,0 +1,45 @@ +# /* +# * 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. +# */ +# +# // This class is b/c java does not allow static {} blocks in interfaces. +# public class Displayer { +# public Displayer(String type) { +# System.out.println("initialization of " + type); +# } +# public void touch() { +# return; +# } +# } + +.class public LDisplayer; +.super Ljava/lang/Object; + +.method public constructor <init>(Ljava/lang/String;)V + .locals 2 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + const-string v0, "initialization of " + invoke-virtual {v0, p1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void +.end method + +.method public touch()V + .locals 0 + return-void +.end method + diff --git a/test/964-default-iface-init-generated/util-src/generate_smali.py b/test/964-default-iface-init-generated/util-src/generate_smali.py new file mode 100755 index 0000000000..be2d3ba563 --- /dev/null +++ b/test/964-default-iface-init-generated/util-src/generate_smali.py @@ -0,0 +1,531 @@ +#!/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 964. +""" + +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 functools import total_ordering +import itertools +import string + +# The max depth the 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_groups} + +{main_func} + +# }} +""" + + MAIN_FUNCTION_TEMPLATE = """ +# public static void main(String[] args) {{ +.method public static main([Ljava/lang/String;)V + .locals 2 + + {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 add_test(self, ty): + """ + Add a test for the concrete type 'ty' + """ + self.tests.add(Func(ty)) + + 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 get_name(self): + """ + Gets the name of this class + """ + return "Main" + + def __str__(self): + """ + Print the smali code for this test. + """ + all_tests = sorted(self.tests) + test_invoke = "" + test_groups = "" + for t in all_tests: + test_groups += 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_groups = test_groups, + 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}() {{ +# try {{ +# System.out.println("About to initialize {tree}"); +# {farg} v = new {farg}(); +# System.out.println("Initialized {tree}"); +# v.touchAll(); +# System.out.println("All of {tree} hierarchy initialized"); +# return; +# }} catch (Error e) {{ +# e.printStackTrace(System.out); +# return; +# }} +# }} +.method public static {fname}()V + .locals 7 + :call_{fname}_try_start + sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v3, "About to initialize {tree}" + invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + new-instance v6, L{farg}; + invoke-direct {{v6}}, L{farg};-><init>()V + + const-string v3, "Initialized {tree}" + invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {{v6}}, L{farg};->touchAll()V + + const-string v3, "All of {tree} hierarchy initialized" + invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void + :call_{fname}_try_end + .catch Ljava/lang/Error; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start + :error_{fname}_start + move-exception v3 + sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {{v3,v2}}, Ljava/lang/Error;->printStackTrace(Ljava/io/PrintStream;)V + return-void +.end method +""" + + OUTPUT_FORMAT = """ +About to initialize {tree} +{initialize_output} +Initialized {tree} +{touch_output} +All of {tree} hierarchy initialized +""".strip() + + def __init__(self, farg): + """ + Initialize a test function for the given argument + """ + self.farg = farg + + def __str__(self): + """ + Print the smali code for this test function. + """ + return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(), + farg=self.farg.get_name(), + tree = self.farg.get_tree()) + + def get_name(self): + """ + Gets the name of this test function + """ + return "TEST_FUNC_{}".format(self.farg.get_name()) + + def get_expected(self): + """ + Get the expected output of this function. + """ + return self.OUTPUT_FORMAT.format( + tree = self.farg.get_tree(), + initialize_output = self.farg.get_initialize_output().strip(), + touch_output = self.farg.get_touch_output().strip()) + +class TestClass(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin): + """ + A class that will be instantiated to test interface initialization order. + """ + + TEST_CLASS_TEMPLATE = """{copyright} + +.class public L{class_name}; +.super Ljava/lang/Object; +{implements_spec} + +# public class {class_name} implements {ifaces} {{ +# +# public {class_name}() {{ +# }} +.method public constructor <init>()V + .locals 2 + invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V + return-void +.end method + +# public void marker() {{ +# return; +# }} +.method public marker()V + .locals 0 + return-void +.end method + +# public void touchAll() {{ +.method public touchAll()V + .locals 2 + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + {touch_calls} + return-void +.end method +# }} +# }} +""" + + IMPLEMENTS_TEMPLATE = """ +.implements L{iface_name}; +""" + + TOUCH_CALL_TEMPLATE = """ +# System.out.println("{class_name} touching {iface_name}"); +# {iface_name}.field.touch(); + const-string v1, "{class_name} touching {iface_name}" + invoke-virtual {{v0, v1}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + sget-object v1, L{iface_name};->field:LDisplayer; + invoke-virtual {{v1}}, LDisplayer;->touch()V +""" + + TOUCH_OUTPUT_TEMPLATE = """ +{class_name} touching {iface_name} +{touch_output} +""".strip() + + def __init__(self, ifaces): + """ + Initialize this test class which implements the given interfaces + """ + self.ifaces = ifaces + self.class_name = "CLASS_"+gensym() + + def get_name(self): + """ + Gets the name of this interface + """ + return self.class_name + + def get_tree(self): + """ + Print out a representation of the type tree of this class + """ + return "[{fname} {iftree}]".format(fname = self.get_name(), iftree = print_tree(self.ifaces)) + + def get_initialize_output(self): + return "\n".join(map(lambda i: i.get_initialize_output().strip(), dump_tree(self.ifaces))) + + def get_touch_output(self): + return "\n".join(map(lambda a: self.TOUCH_OUTPUT_TEMPLATE.format( + class_name = self.class_name, + iface_name = a.get_name(), + touch_output = a.get_touch_output()).strip(), + self.get_all_interfaces())) + + def get_all_interfaces(self): + """ + Returns a set of all interfaces this class transitively implements + """ + return sorted(set(dump_tree(self.ifaces))) + + def __str__(self): + """ + Print the smali code for this class. + """ + s_ifaces = '\n'.join(map(lambda a: self.IMPLEMENTS_TEMPLATE.format(iface_name = a.get_name()), + self.ifaces)) + j_ifaces = ', '.join(map(lambda a: a.get_name(), self.ifaces)) + touches = '\n'.join(map(lambda a: self.TOUCH_CALL_TEMPLATE.format(class_name = self.class_name, + iface_name = a.get_name()), + self.get_all_interfaces())) + return self.TEST_CLASS_TEMPLATE.format(copyright = get_copyright('smali'), + implements_spec = s_ifaces, + ifaces = j_ifaces, + class_name = self.class_name, + touch_calls = touches) + +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} {{ +# public static final Displayer field = new Displayer("{tree}"); +.field public final static field:LDisplayer; + +.method public static constructor <clinit>()V + .locals 3 + const-string v2, "{tree}" + new-instance v1, LDisplayer; + invoke-direct {{v1, v2}}, LDisplayer;-><init>(Ljava/lang/String;)V + sput-object v1, L{class_name};->field:LDisplayer; + return-void +.end method + +# public void marker(); +.method public abstract marker()V +.end method + +{funcs} + +# }} +""" + + DEFAULT_FUNC_TEMPLATE = """ +# public default void {class_name}_DEFAULT_FUNC() {{ +# return; +# }} +.method public {class_name}_DEFAULT_FUNC()V + .locals 0 + return-void +.end method +""" + IMPLEMENTS_TEMPLATE = """ +.implements L{iface_name}; +""" + + OUTPUT_TEMPLATE = "initialization of {tree}" + + def __init__(self, ifaces, default): + """ + Initialize interface with the given super-interfaces + """ + self.ifaces = ifaces + self.default = default + end = "_DEFAULT" if default else "" + self.class_name = "INTERFACE_"+gensym()+end + self.cloned = False + self.initialized = False + + def clone(self): + """ + Clones this interface, returning a new one with the same structure but + different name. + """ + return TestInterface(tuple(map(lambda a: a.clone(), self.ifaces)), self.default) + + def get_name(self): + """ + Gets the name of this interface + """ + return self.class_name + + 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 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 get_initialize_output(self): + """ + Returns the expected output upon the class that implements this interface being initialized. + """ + if self.default and not self.initialized: + self.initialized = True + return self.OUTPUT_TEMPLATE.format(tree = self.get_tree()) + else: + return "" + + def get_touch_output(self): + """ + Returns the expected output upon this interface being touched. + """ + if not self.default and not self.initialized: + self.initialized = True + return self.OUTPUT_TEMPLATE.format(tree = self.get_tree()) + else: + return "" + + def __str__(self): + """ + Print the smali code for this interface. + """ + s_ifaces = '\n'.join(map(lambda a: self.IMPLEMENTS_TEMPLATE.format(iface_name = a.get_name()), + self.ifaces)) + j_ifaces = ', '.join(map(lambda a: a.get_name(), self.ifaces)) + if self.default: + funcs = self.DEFAULT_FUNC_TEMPLATE.format(class_name = self.class_name) + 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 dump_tree(ifaces): + """ + Yields all the interfaces transitively implemented by the set in + reverse-depth-first order + """ + for i in ifaces: + yield from dump_tree(i.ifaces) + yield i + +def print_tree(ifaces): + """ + Prints the tree for the given ifaces. + """ + return " ".join(i.get_tree() for i in ifaces) + +def clone_all(l): + return tuple(a.clone() for a in l) + +# Cached output of subtree_sizes for speed of access. +SUBTREES = [set(tuple(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 split in SUBTREES[num]: + ifaces = [] + for sub in split: + ifaces.append(list(create_interface_trees(sub))) + for supers in itertools.product(*ifaces): + yield TestClass(clone_all(supers)) + for i in range(len(set(dump_tree(supers)) - set(supers))): + ns = clone_all(supers) + selected = sorted(set(dump_tree(ns)) - set(ns))[i] + yield TestClass(tuple([selected] + list(ns))) + +def create_interface_trees(num): + """ + Yield all the interface trees up to 'num' depth. + """ + if num == 0: + yield TestInterface(tuple(), False) + yield TestInterface(tuple(), True) + return + for split in SUBTREES[num]: + ifaces = [] + for sub in split: + ifaces.append(list(create_interface_trees(sub))) + for supers in itertools.product(*ifaces): + yield TestInterface(clone_all(supers), False) + yield TestInterface(clone_all(supers), True) + # TODO Should add on some from higher up the tree. + +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 dump_tree(clazz.ifaces): + 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/etc/default-build b/test/etc/default-build index c281bca3f5..c92402b529 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -26,6 +26,8 @@ while true; do option="$1" DX_FLAGS="${DX_FLAGS} $option" shift + elif [ "x$1" = "x--jvm" ]; then + shift elif expr "x$1" : "x--" >/dev/null 2>&1; then echo "unknown $0 option: $1" 1>&2 exit 1 diff --git a/test/run-all-tests b/test/run-all-tests index 13490c46e4..76283b7a8d 100755 --- a/test/run-all-tests +++ b/test/run-all-tests @@ -41,6 +41,9 @@ while true; do if [ "x$1" = "x--host" ]; then run_args="${run_args} --host" shift + elif [ "x$1" = "x--use-java-home" ]; then + run_args="${run_args} --use-java-home" + shift elif [ "x$1" = "x--jvm" ]; then run_args="${run_args} --jvm" shift @@ -133,7 +136,7 @@ if [ "$usage" = "yes" ]; then echo " --debug --dev --host --interpreter --jit --jvm --no-optimize" echo " --no-verify -O --update --valgrind --zygote --64 --relocate" echo " --prebuild --always-clean --gcstress --gcverify --trace" - echo " --no-patchoat --no-dex2oat" + echo " --no-patchoat --no-dex2oat --use-java-home" echo " Specific Runtime Options:" echo " --seq Run tests one-by-one, avoiding failures caused by busy CPU" ) 1>&2 diff --git a/test/run-test b/test/run-test index 2892ce9f18..1b71f33209 100755 --- a/test/run-test +++ b/test/run-test @@ -40,7 +40,6 @@ else tmp_dir="${TMPDIR}/$USER/${test_dir}" fi checker="${progdir}/../tools/checker/checker.py" - export JAVA="java" export JAVAC="javac -g" export RUN="${progdir}/etc/run-test-jar" @@ -155,6 +154,15 @@ while true; do DEX_LOCATION=$tmp_dir run_args="${run_args} --host" shift + elif [ "x$1" = "x--use-java-home" ]; then + if [ -n "${JAVA_HOME}" ]; then + export JAVA="${JAVA_HOME}/bin/java" + export JAVAC="${JAVA_HOME}/bin/javac -g" + else + echo "Passed --use-java-home without JAVA_HOME variable set!" + usage="yes" + fi + shift elif [ "x$1" = "x--jvm" ]; then target_mode="no" runtime="jvm" @@ -162,6 +170,7 @@ while true; do NEED_DEX="false" USE_JACK="false" run_args="${run_args} --jvm" + build_args="${build_args} --jvm" shift elif [ "x$1" = "x-O" ]; then lib="libart.so" @@ -560,6 +569,9 @@ if [ "$usage" = "yes" ]; then echo " --invoke-with Pass --invoke-with option to runtime." echo " --dalvik Use Dalvik (off by default)." echo " --jvm Use a host-local RI virtual machine." + echo " --use-java-home Use the JAVA_HOME environment variable" + echo " to find the java compiler and runtime" + echo " (if applicable) to run the test with." echo " --output-path [path] Location where to store the build" \ "files." echo " --64 Run the test in 64-bit mode" diff --git a/test/utils/python/testgen/mixins.py b/test/utils/python/testgen/mixins.py new file mode 100644 index 0000000000..085e51def2 --- /dev/null +++ b/test/utils/python/testgen/mixins.py @@ -0,0 +1,135 @@ +#!/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. + +""" +Common mixins and abstract base classes (ABCs) useful for writing test generators in python +""" + +import abc +import collections.abc +import functools + +class Named(metaclass=abc.ABCMeta): + """ + An abc that defines a get_name method. + """ + + @abc.abstractmethod + def get_name(self): + """ + Returns a unique name to use as the identity for implementing comparisons. + """ + pass + +class FileLike(metaclass=abc.ABCMeta): + """ + An abc that defines get_file_name and get_file_extension methods. + """ + + @abc.abstractmethod + def get_file_name(self): + """Returns the filename this object represents""" + pass + + @abc.abstractmethod + def get_file_extension(self): + """Returns the file extension of the file this object represents""" + pass + +@functools.lru_cache(maxsize=None) +def get_file_extension_mixin(ext): + """ + Gets a mixin that defines get_file_name(self) in terms of get_name(self) with the + given file extension. + """ + + class FExt(object): + """ + A mixin defining get_file_name(self) in terms of get_name(self) + """ + + def get_file_name(self): + return self.get_name() + ext + + def get_file_extension(self): + return ext + + # Register the ABCs + Named.register(FExt) + FileLike.register(FExt) + + return FExt + +class SmaliFileMixin(get_file_extension_mixin(".smali")): + """ + A mixin that defines that the file this class belongs to is get_name() + ".smali". + """ + pass + +class NameComparableMixin(object): + """ + A mixin that defines the object comparison and related functionality in terms + of a get_name(self) function. + """ + + def __lt__(self, other): + return self.get_name() < other.get_name() + + def __gt__(self, other): + return self.get_name() > other.get_name() + + def __eq__(self, other): + return self.get_name() == other.get_name() + + def __le__(self, other): + return self.get_name() <= other.get_name() + + def __ge__(self, other): + return self.get_name() >= other.get_name() + + def __ne__(self, other): + return self.get_name() != other.get_name() + + def __hash__(self): + return hash(self.get_name()) + +Named.register(NameComparableMixin) +collections.abc.Hashable.register(NameComparableMixin) + +class DumpMixin(metaclass=abc.ABCMeta): + """ + A mixin to add support for dumping the string representation of an object to a + file. Requires the get_file_name(self) method be defined. + """ + + @abc.abstractmethod + def __str__(self): + """ + Returns the data to be printed to a file by dump. + """ + pass + + def dump(self, directory): + """ + Dump this object to a file in the given directory + """ + out_file = directory / self.get_file_name() + if out_file.exists(): + out_file.unlink() + with out_file.open('w') as out: + print(str(self), file=out) + +FileLike.register(DumpMixin) diff --git a/test/utils/python/testgen/utils.py b/test/utils/python/testgen/utils.py new file mode 100644 index 0000000000..769ad16ebe --- /dev/null +++ b/test/utils/python/testgen/utils.py @@ -0,0 +1,80 @@ +#!/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. + +""" +Common functions useful for writing test generators in python +""" + +import itertools +import os +import string +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) + +# An iterator which yields strings made from lowercase letters. First yields +# all 1 length strings, then all 2 and so on. It does this alphabetically. +NAME_GEN = itertools.chain.from_iterable( + map(lambda n: itertools.product(string.ascii_lowercase, repeat=n), + itertools.count(1))) + +def gensym(): + """ + Returns a new, globally unique, identifier name that is a valid Java symbol + on each call. + """ + return ''.join(next(NAME_GEN)) + +def filter_blanks(s): + """ + Takes a string returns the same string sans empty lines + """ + return "\n".join(a for a in s.split("\n") if a.strip() != "") + +def get_copyright(filetype = "java"): + """ + Returns the standard copyright header for the given filetype + """ + if filetype == "smali": + return "\n".join(map(lambda a: "# " + a, get_copyright("java").split("\n"))) + else: + fname = filetype + ".txt" + with (Path(BUILD_TOP)/"development"/"docs"/"copyright-templates"/fname).open() as template: + return "".join(template.readlines()) + +def subtree_sizes(n): + """ + A generator that yields a tuple containing a possible arrangement of subtree + nodes for a tree with a total of 'n' leaf nodes. + """ + if n == 0: + return + elif n == 1: + yield (0,) + elif n == 2: + yield (1, 1) + else: + for prevt in subtree_sizes(n - 1): + prev = list(prevt) + yield tuple([1] + prev) + for i in range(len(prev)): + prev[i] += 1 + yield tuple(prev) + prev[i] -= 1 + diff --git a/tools/extract-embedded-java b/tools/extract-embedded-java new file mode 100755 index 0000000000..e966552af0 --- /dev/null +++ b/tools/extract-embedded-java @@ -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. + +if [ "$#" -ne "2" ]; then + echo "Usage: ./extract_embedded_java.sh smali_dir java_dir" + exit 1 +fi + +# Check the input and output are directories +[[ -d "$1" ]] || exit 1 +[[ -d "$2" ]] || exit 1 + +# For every file which has the file extension smali, set $f to be the name without +# .smali and then: +for f in `find "$1" -type f -name "*.smali" | xargs -n 1 -P 0 -i basename -s .smali \{\}`; do + # remove all lines except those starting with '# ', remove the '#' then print + # it to a file ${name}.java. Do this concurrently. + grep "^# " "$1/${f}.smali" | sed "s:# ::" > "${2}/${f}.java" & +done + +# wait for all the files to be written +wait |