summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alex Light <allight@google.com> 2015-10-13 21:02:28 +0000
committer Gerrit Code Review <noreply-gerritcodereview@google.com> 2015-10-13 21:02:28 +0000
commit85506c2ff34e930b4589568f78ee30f60d49d456 (patch)
treec7e4b30a54046ed90a212e298f871a5df257c648
parent7598f145d4fd4219c14c6e2c8a03b34ce7943ffe (diff)
parenteb7c144a6aff7da673ba53d501c46f00311d4d7f (diff)
Merge "Add initial default method support to Art"
-rw-r--r--.gitignore1
-rw-r--r--cmdline/cmdline_parser_test.cc31
-rw-r--r--cmdline/cmdline_types.h18
-rw-r--r--compiler/dex/quick/quick_compiler.cc4
-rw-r--r--runtime/art_method-inl.h3
-rw-r--r--runtime/art_method.h10
-rw-r--r--runtime/class_linker.cc677
-rw-r--r--runtime/class_linker.h68
-rw-r--r--runtime/dex_file_verifier.cc11
-rw-r--r--runtime/experimental_flags.h88
-rw-r--r--runtime/interpreter/interpreter.cc5
-rw-r--r--runtime/interpreter/interpreter_goto_table_impl.cc3
-rw-r--r--runtime/interpreter/interpreter_switch_impl.cc3
-rw-r--r--runtime/mirror/class-inl.h3
-rw-r--r--runtime/mirror/class.h21
-rw-r--r--runtime/modifiers.h5
-rw-r--r--runtime/parsed_options.cc21
-rw-r--r--runtime/runtime.cc5
-rw-r--r--runtime/runtime.h7
-rw-r--r--runtime/runtime_options.def2
-rw-r--r--runtime/verifier/method_verifier.cc35
-rwxr-xr-xtest/955-lambda-smali/run2
-rwxr-xr-xtest/960-default-smali/build33
-rw-r--r--test/960-default-smali/expected.txt84
-rw-r--r--test/960-default-smali/info.txt19
-rwxr-xr-xtest/960-default-smali/run21
-rw-r--r--test/960-default-smali/smali/A.smali38
-rw-r--r--test/960-default-smali/smali/Attendant.smali53
-rw-r--r--test/960-default-smali/smali/B.smali38
-rw-r--r--test/960-default-smali/smali/C.smali37
-rw-r--r--test/960-default-smali/smali/D.smali38
-rw-r--r--test/960-default-smali/smali/E.smali38
-rw-r--r--test/960-default-smali/smali/Extension.smali30
-rw-r--r--test/960-default-smali/smali/F.smali47
-rw-r--r--test/960-default-smali/smali/G.smali37
-rw-r--r--test/960-default-smali/smali/Greeter.smali40
-rw-r--r--test/960-default-smali/smali/Greeter2.smali39
-rw-r--r--test/960-default-smali/smali/Greeter3.smali40
-rw-r--r--test/960-default-smali/smali/H.smali28
-rw-r--r--test/960-default-smali/smali/I.smali28
-rw-r--r--test/960-default-smali/smali/J.smali29
-rw-r--r--test/960-default-smali/smali/classes.xml127
-rwxr-xr-xtest/960-default-smali/util-src/generate_smali.py376
-rwxr-xr-xtest/961-default-iface-resolution-generated/build47
-rw-r--r--test/961-default-iface-resolution-generated/expected.txt1
-rw-r--r--test/961-default-iface-resolution-generated/info.txt17
-rwxr-xr-xtest/961-default-iface-resolution-generated/run21
-rwxr-xr-xtest/961-default-iface-resolution-generated/util-src/generate_smali.py466
-rwxr-xr-xtest/962-iface-static/build30
-rw-r--r--test/962-iface-static/expected.txt3
-rw-r--r--test/962-iface-static/info.txt4
-rwxr-xr-xtest/962-iface-static/run21
-rw-r--r--test/962-iface-static/smali/Displayer.smali45
-rw-r--r--test/962-iface-static/smali/Main.smali40
-rw-r--r--test/962-iface-static/smali/iface.smali43
-rwxr-xr-xtest/963-default-range-smali/build30
-rw-r--r--test/963-default-range-smali/expected.txt2
-rw-r--r--test/963-default-range-smali/info.txt4
-rwxr-xr-xtest/963-default-range-smali/run21
-rw-r--r--test/963-default-range-smali/smali/A.smali29
-rw-r--r--test/963-default-range-smali/smali/Main.smali77
-rw-r--r--test/963-default-range-smali/smali/iface.smali40
-rwxr-xr-xtest/964-default-iface-init-generated/build45
-rw-r--r--test/964-default-iface-init-generated/expected.txt1
-rw-r--r--test/964-default-iface-init-generated/info.txt17
-rwxr-xr-xtest/964-default-iface-init-generated/run21
-rw-r--r--test/964-default-iface-init-generated/smali/Displayer.smali45
-rwxr-xr-xtest/964-default-iface-init-generated/util-src/generate_smali.py531
-rwxr-xr-xtest/etc/default-build2
-rwxr-xr-xtest/run-all-tests5
-rwxr-xr-xtest/run-test14
-rw-r--r--test/utils/python/testgen/mixins.py135
-rw-r--r--test/utils/python/testgen/utils.py80
-rwxr-xr-xtools/extract-embedded-java35
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*/&current_method,
+ /*out*/&icce_message)) {
+ // There was a conflict with default method implementations.
+ self->EndAssertNoThreadSuspension(old_cause);
+ // TODO This should actually be thrown when we attempt to invoke this method.
+ ThrowIncompatibleClassChangeError(klass.Get(), "%s", icce_message.c_str());
+ return false;
+ } else if (current_method != nullptr) {
+ if (found_default_impl &&
+ current_method->GetDeclaringClass() == default_impl->GetDeclaringClass()) {
+ // 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