Merge "Move Class.newInstance to native"
diff --git a/compiler/dex/bb_optimizations.cc b/compiler/dex/bb_optimizations.cc
index 11a7e44..f351d99 100644
--- a/compiler/dex/bb_optimizations.cc
+++ b/compiler/dex/bb_optimizations.cc
@@ -17,6 +17,7 @@
#include "bb_optimizations.h"
#include "dataflow_iterator.h"
#include "dataflow_iterator-inl.h"
+#include "global_value_numbering.h"
namespace art {
@@ -79,4 +80,14 @@
return false;
}
+bool GlobalValueNumberingCleanupPass::Gate(const PassDataHolder* data) const {
+ DCHECK(data != nullptr);
+ CompilationUnit* c_unit = down_cast<const PassMEDataHolder*>(data)->c_unit;
+ DCHECK(c_unit != nullptr);
+ // Do not do cleanup if GVN skipped this.
+ // TODO: Proper dependencies between passes?
+ return !GlobalValueNumbering::Skip(c_unit);
+}
+
+
} // namespace art
diff --git a/compiler/dex/bb_optimizations.h b/compiler/dex/bb_optimizations.h
index eb87c29..b948afd 100644
--- a/compiler/dex/bb_optimizations.h
+++ b/compiler/dex/bb_optimizations.h
@@ -284,6 +284,9 @@
: PassME("GVNCleanup", kNoNodes, "") {
}
+ // Depends on GlobalValueNumbering, so implemented in cc file.
+ bool Gate(const PassDataHolder* data) const OVERRIDE;
+
void Start(PassDataHolder* data) const OVERRIDE {
DCHECK(data != nullptr);
CompilationUnit* c_unit = down_cast<const PassMEDataHolder*>(data)->c_unit;
diff --git a/compiler/dex/mir_optimization.cc b/compiler/dex/mir_optimization.cc
index 0d5da32..3482602 100644
--- a/compiler/dex/mir_optimization.cc
+++ b/compiler/dex/mir_optimization.cc
@@ -1355,8 +1355,13 @@
temp_scoped_alloc_.reset();
}
+static void DisableGVNDependentOptimizations(CompilationUnit* cu) {
+ cu->disable_opt |= (1u << kGvnDeadCodeElimination);
+}
+
bool MIRGraph::ApplyGlobalValueNumberingGate() {
if (GlobalValueNumbering::Skip(cu_)) {
+ DisableGVNDependentOptimizations(cu_);
return false;
}
@@ -1407,7 +1412,7 @@
cu_->disable_opt |= (1u << kLocalValueNumbering);
} else {
LOG(WARNING) << "GVN failed for " << PrettyMethod(cu_->method_idx, *cu_->dex_file);
- cu_->disable_opt |= (1u << kGvnDeadCodeElimination);
+ DisableGVNDependentOptimizations(cu_);
}
}
diff --git a/compiler/dex/quick/quick_compiler.cc b/compiler/dex/quick/quick_compiler.cc
index 73cfe92..39eb117 100644
--- a/compiler/dex/quick/quick_compiler.cc
+++ b/compiler/dex/quick/quick_compiler.cc
@@ -575,7 +575,7 @@
// (1 << kNullCheckElimination) |
// (1 << kClassInitCheckElimination) |
// (1 << kGlobalValueNumbering) |
- // (1 << kGvnDeadCodeElimination) |
+ (1 << kGvnDeadCodeElimination) |
// (1 << kLocalValueNumbering) |
// (1 << kPromoteRegs) |
// (1 << kTrackLiveTemps) |
diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc
index 6511120..3645f19 100644
--- a/compiler/optimizing/bounds_check_elimination.cc
+++ b/compiler/optimizing/bounds_check_elimination.cc
@@ -1064,7 +1064,7 @@
};
void BoundsCheckElimination::Run() {
- if (!graph_->HasArrayAccesses()) {
+ if (!graph_->HasBoundsChecks()) {
return;
}
diff --git a/compiler/optimizing/bounds_check_elimination_test.cc b/compiler/optimizing/bounds_check_elimination_test.cc
index 75cf1cf..97be778 100644
--- a/compiler/optimizing/bounds_check_elimination_test.cc
+++ b/compiler/optimizing/bounds_check_elimination_test.cc
@@ -43,7 +43,7 @@
ArenaAllocator allocator(&pool);
HGraph* graph = new (&allocator) HGraph(&allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
graph->AddBlock(entry);
@@ -148,7 +148,7 @@
ArenaAllocator allocator(&pool);
HGraph* graph = new (&allocator) HGraph(&allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
graph->AddBlock(entry);
@@ -220,7 +220,7 @@
ArenaAllocator allocator(&pool);
HGraph* graph = new (&allocator) HGraph(&allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
graph->AddBlock(entry);
@@ -292,7 +292,7 @@
ArenaAllocator allocator(&pool);
HGraph* graph = new (&allocator) HGraph(&allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
graph->AddBlock(entry);
@@ -365,7 +365,7 @@
int increment,
IfCondition cond = kCondGE) {
HGraph* graph = new (allocator) HGraph(allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (allocator) HBasicBlock(graph);
graph->AddBlock(entry);
@@ -502,7 +502,7 @@
int increment = -1,
IfCondition cond = kCondLE) {
HGraph* graph = new (allocator) HGraph(allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (allocator) HBasicBlock(graph);
graph->AddBlock(entry);
@@ -633,7 +633,7 @@
int increment,
IfCondition cond) {
HGraph* graph = new (allocator) HGraph(allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (allocator) HBasicBlock(graph);
graph->AddBlock(entry);
@@ -744,7 +744,7 @@
int initial,
IfCondition cond = kCondGE) {
HGraph* graph = new (allocator) HGraph(allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (allocator) HBasicBlock(graph);
graph->AddBlock(entry);
@@ -869,7 +869,7 @@
ArenaAllocator allocator(&pool);
HGraph* graph = new (&allocator) HGraph(&allocator);
- graph->SetHasArrayAccesses(true);
+ graph->SetHasBoundsChecks(true);
HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
graph->AddBlock(entry);
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc
index ebd8243..96e08fd 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -963,7 +963,7 @@
current_block_->AddInstruction(new (arena_) HArrayGet(object, index, anticipated_type));
UpdateLocal(source_or_dest_reg, current_block_->GetLastInstruction());
}
- graph_->SetHasArrayAccesses(true);
+ graph_->SetHasBoundsChecks(true);
}
void HGraphBuilder::BuildFilledNewArray(uint32_t dex_pc,
@@ -1065,7 +1065,7 @@
default:
LOG(FATAL) << "Unknown element width for " << payload->element_width;
}
- graph_->SetHasArrayAccesses(true);
+ graph_->SetHasBoundsChecks(true);
}
void HGraphBuilder::BuildFillWideArrayData(HInstruction* object,
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index f0c14f6..04999be 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -3819,7 +3819,7 @@
LocationSummary* locations =
new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall);
locations->SetInAt(0, Location::RegisterOrConstant(instruction->InputAt(0)));
- locations->SetInAt(1, Location::RequiresRegister());
+ locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
if (instruction->HasUses()) {
locations->SetOut(Location::SameAsFirstInput());
}
@@ -3831,16 +3831,38 @@
Location length_loc = locations->InAt(1);
SlowPathCodeX86* slow_path =
new (GetGraph()->GetArena()) BoundsCheckSlowPathX86(instruction, index_loc, length_loc);
- codegen_->AddSlowPath(slow_path);
- Register length = length_loc.AsRegister<Register>();
- if (index_loc.IsConstant()) {
- int32_t value = CodeGenerator::GetInt32ValueOf(index_loc.GetConstant());
- __ cmpl(length, Immediate(value));
+ if (length_loc.IsConstant()) {
+ int32_t length = CodeGenerator::GetInt32ValueOf(length_loc.GetConstant());
+ if (index_loc.IsConstant()) {
+ // BCE will remove the bounds check if we are guarenteed to pass.
+ int32_t index = CodeGenerator::GetInt32ValueOf(index_loc.GetConstant());
+ if (index < 0 || index >= length) {
+ codegen_->AddSlowPath(slow_path);
+ __ jmp(slow_path->GetEntryLabel());
+ } else {
+ // Some optimization after BCE may have generated this, and we should not
+ // generate a bounds check if it is a valid range.
+ }
+ return;
+ }
+
+ // We have to reverse the jump condition because the length is the constant.
+ Register index_reg = index_loc.AsRegister<Register>();
+ __ cmpl(index_reg, Immediate(length));
+ codegen_->AddSlowPath(slow_path);
+ __ j(kAboveEqual, slow_path->GetEntryLabel());
} else {
- __ cmpl(length, index_loc.AsRegister<Register>());
+ Register length = length_loc.AsRegister<Register>();
+ if (index_loc.IsConstant()) {
+ int32_t value = CodeGenerator::GetInt32ValueOf(index_loc.GetConstant());
+ __ cmpl(length, Immediate(value));
+ } else {
+ __ cmpl(length, index_loc.AsRegister<Register>());
+ }
+ codegen_->AddSlowPath(slow_path);
+ __ j(kBelowEqual, slow_path->GetEntryLabel());
}
- __ j(kBelowEqual, slow_path->GetEntryLabel());
}
void LocationsBuilderX86::VisitTemporary(HTemporary* temp) {
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 642900f..5ce9329 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -3760,7 +3760,7 @@
LocationSummary* locations =
new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall);
locations->SetInAt(0, Location::RegisterOrConstant(instruction->InputAt(0)));
- locations->SetInAt(1, Location::RequiresRegister());
+ locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
if (instruction->HasUses()) {
locations->SetOut(Location::SameAsFirstInput());
}
@@ -3772,16 +3772,38 @@
Location length_loc = locations->InAt(1);
SlowPathCodeX86_64* slow_path =
new (GetGraph()->GetArena()) BoundsCheckSlowPathX86_64(instruction, index_loc, length_loc);
- codegen_->AddSlowPath(slow_path);
- CpuRegister length = length_loc.AsRegister<CpuRegister>();
- if (index_loc.IsConstant()) {
- int32_t value = CodeGenerator::GetInt32ValueOf(index_loc.GetConstant());
- __ cmpl(length, Immediate(value));
+ if (length_loc.IsConstant()) {
+ int32_t length = CodeGenerator::GetInt32ValueOf(length_loc.GetConstant());
+ if (index_loc.IsConstant()) {
+ // BCE will remove the bounds check if we are guarenteed to pass.
+ int32_t index = CodeGenerator::GetInt32ValueOf(index_loc.GetConstant());
+ if (index < 0 || index >= length) {
+ codegen_->AddSlowPath(slow_path);
+ __ jmp(slow_path->GetEntryLabel());
+ } else {
+ // Some optimization after BCE may have generated this, and we should not
+ // generate a bounds check if it is a valid range.
+ }
+ return;
+ }
+
+ // We have to reverse the jump condition because the length is the constant.
+ CpuRegister index_reg = index_loc.AsRegister<CpuRegister>();
+ __ cmpl(index_reg, Immediate(length));
+ codegen_->AddSlowPath(slow_path);
+ __ j(kAboveEqual, slow_path->GetEntryLabel());
} else {
- __ cmpl(length, index_loc.AsRegister<CpuRegister>());
+ CpuRegister length = length_loc.AsRegister<CpuRegister>();
+ if (index_loc.IsConstant()) {
+ int32_t value = CodeGenerator::GetInt32ValueOf(index_loc.GetConstant());
+ __ cmpl(length, Immediate(value));
+ } else {
+ __ cmpl(length, index_loc.AsRegister<CpuRegister>());
+ }
+ codegen_->AddSlowPath(slow_path);
+ __ j(kBelowEqual, slow_path->GetEntryLabel());
}
- __ j(kBelowEqual, slow_path->GetEntryLabel());
}
void CodeGeneratorX86_64::MarkGCCard(CpuRegister temp,
diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc
index 8906764..dc3124b 100644
--- a/compiler/optimizing/graph_checker.cc
+++ b/compiler/optimizing/graph_checker.cc
@@ -121,6 +121,18 @@
}
}
+void GraphChecker::VisitBoundsCheck(HBoundsCheck* check) {
+ if (!GetGraph()->HasBoundsChecks()) {
+ AddError(StringPrintf("Instruction %s:%d is a HBoundsCheck, "
+ "but HasBoundsChecks() returns false",
+ check->DebugName(),
+ check->GetId()));
+ }
+
+ // Perform the instruction base checks too.
+ VisitInstruction(check);
+}
+
void GraphChecker::VisitInstruction(HInstruction* instruction) {
if (seen_ids_.IsBitSet(instruction->GetId())) {
AddError(StringPrintf("Instruction id %d is duplicate in graph.",
diff --git a/compiler/optimizing/graph_checker.h b/compiler/optimizing/graph_checker.h
index 45e8804..b4314da 100644
--- a/compiler/optimizing/graph_checker.h
+++ b/compiler/optimizing/graph_checker.h
@@ -45,6 +45,9 @@
// Perform control-flow graph checks on instruction.
void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE;
+ // Check that the HasBoundsChecks() flag is set for bounds checks.
+ void VisitBoundsCheck(HBoundsCheck* check) OVERRIDE;
+
// Was the last visit of the graph valid?
bool IsValid() const {
return errors_.empty();
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index 37b5753..ada32db 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -268,8 +268,8 @@
callee_graph->InlineInto(graph_, invoke_instruction);
- if (callee_graph->HasArrayAccesses()) {
- graph_->SetHasArrayAccesses(true);
+ if (callee_graph->HasBoundsChecks()) {
+ graph_->SetHasBoundsChecks(true);
}
return true;
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 946091f..938d6fc 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -127,7 +127,7 @@
number_of_vregs_(0),
number_of_in_vregs_(0),
temporaries_vreg_slots_(0),
- has_array_accesses_(false),
+ has_bounds_checks_(false),
debuggable_(debuggable),
current_instruction_id_(start_instruction_id),
cached_null_constant_(nullptr),
@@ -230,12 +230,12 @@
return linear_order_;
}
- bool HasArrayAccesses() const {
- return has_array_accesses_;
+ bool HasBoundsChecks() const {
+ return has_bounds_checks_;
}
- void SetHasArrayAccesses(bool value) {
- has_array_accesses_ = value;
+ void SetHasBoundsChecks(bool value) {
+ has_bounds_checks_ = value;
}
bool IsDebuggable() const { return debuggable_; }
@@ -295,8 +295,8 @@
// Number of vreg size slots that the temporaries use (used in baseline compiler).
size_t temporaries_vreg_slots_;
- // Has array accesses. We can totally skip BCE if it's false.
- bool has_array_accesses_;
+ // Has bounds checks. We can totally skip BCE if it's false.
+ bool has_bounds_checks_;
// Indicates whether the graph should be compiled in a way that
// ensures full debuggability. If false, we can apply more
diff --git a/compiler/utils/assembler_test.h b/compiler/utils/assembler_test.h
index 3fe1a31..a339633 100644
--- a/compiler/utils/assembler_test.h
+++ b/compiler/utils/assembler_test.h
@@ -19,6 +19,7 @@
#include "assembler.h"
+#include "assembler_test_base.h"
#include "common_runtime_test.h" // For ScratchFile
#include <cstdio>
@@ -29,19 +30,11 @@
namespace art {
-// If you want to take a look at the differences between the ART assembler and GCC, set this flag
-// to true. The disassembled files will then remain in the tmp directory.
-static constexpr bool kKeepDisassembledFiles = false;
-
// Helper for a constexpr string length.
constexpr size_t ConstexprStrLen(char const* str, size_t count = 0) {
return ('\0' == str[0]) ? count : ConstexprStrLen(str+1, count+1);
}
-// Use a glocal static variable to keep the same name for all test data. Else we'll just spam the
-// temp directory.
-static std::string tmpnam_;
-
enum class RegisterView { // private
kUsePrimaryName,
kUseSecondaryName,
@@ -59,12 +52,12 @@
typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler);
void DriverFn(TestFn f, std::string test_name) {
- Driver(f(this, assembler_.get()), test_name);
+ DriverWrapper(f(this, assembler_.get()), test_name);
}
// This driver assumes the assembler has already been called.
void DriverStr(std::string assembly_string, std::string test_name) {
- Driver(assembly_string, test_name);
+ DriverWrapper(assembly_string, test_name);
}
std::string RepeatR(void (Ass::*f)(Reg), std::string fmt) {
@@ -212,28 +205,7 @@
// This is intended to be run as a test.
bool CheckTools() {
- if (!FileExists(FindTool(GetAssemblerCmdName()))) {
- return false;
- }
- LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
-
- if (!FileExists(FindTool(GetObjdumpCmdName()))) {
- return false;
- }
- LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
-
- // Disassembly is optional.
- std::string disassembler = GetDisassembleCommand();
- if (disassembler.length() != 0) {
- if (!FileExists(FindTool(GetDisassembleCmdName()))) {
- return false;
- }
- LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
- } else {
- LOG(INFO) << "No disassembler given.";
- }
-
- return true;
+ return test_helper_->CheckTools();
}
// The following functions are public so that TestFn can use them...
@@ -272,17 +244,21 @@
void SetUp() OVERRIDE {
assembler_.reset(new Ass());
-
- // Fake a runtime test for ScratchFile
- CommonRuntimeTest::SetUpAndroidData(android_data_);
+ test_helper_.reset(
+ new AssemblerTestInfrastructure(GetArchitectureString(),
+ GetAssemblerCmdName(),
+ GetAssemblerParameters(),
+ GetObjdumpCmdName(),
+ GetObjdumpParameters(),
+ GetDisassembleCmdName(),
+ GetDisassembleParameters(),
+ GetAssemblyHeader()));
SetUpHelpers();
}
void TearDown() OVERRIDE {
- // We leave temporaries in case this failed so we can debug issues.
- CommonRuntimeTest::TearDownAndroidData(android_data_, false);
- tmpnam_ = "";
+ test_helper_.reset(); // Clean up the helper.
}
// Override this to set up any architecture-specific things, e.g., register vectors.
@@ -301,23 +277,6 @@
return "";
}
- // Return the host assembler command for this test.
- virtual std::string GetAssemblerCommand() {
- // Already resolved it once?
- if (resolved_assembler_cmd_.length() != 0) {
- return resolved_assembler_cmd_;
- }
-
- std::string line = FindTool(GetAssemblerCmdName());
- if (line.length() == 0) {
- return line;
- }
-
- resolved_assembler_cmd_ = line + GetAssemblerParameters();
-
- return resolved_assembler_cmd_;
- }
-
// Get the name of the objdump, e.g., "objdump" by default.
virtual std::string GetObjdumpCmdName() {
return "objdump";
@@ -328,23 +287,6 @@
return " -h";
}
- // Return the host objdump command for this test.
- virtual std::string GetObjdumpCommand() {
- // Already resolved it once?
- if (resolved_objdump_cmd_.length() != 0) {
- return resolved_objdump_cmd_;
- }
-
- std::string line = FindTool(GetObjdumpCmdName());
- if (line.length() == 0) {
- return line;
- }
-
- resolved_objdump_cmd_ = line + GetObjdumpParameters();
-
- return resolved_objdump_cmd_;
- }
-
// Get the name of the objdump, e.g., "objdump" by default.
virtual std::string GetDisassembleCmdName() {
return "objdump";
@@ -354,23 +296,6 @@
// such to objdump, so it's architecture-specific and there is no default.
virtual std::string GetDisassembleParameters() = 0;
- // Return the host disassembler command for this test.
- virtual std::string GetDisassembleCommand() {
- // Already resolved it once?
- if (resolved_disassemble_cmd_.length() != 0) {
- return resolved_disassemble_cmd_;
- }
-
- std::string line = FindTool(GetDisassembleCmdName());
- if (line.length() == 0) {
- return line;
- }
-
- resolved_disassemble_cmd_ = line + GetDisassembleParameters();
-
- return resolved_disassemble_cmd_;
- }
-
// Create a couple of immediate values up to the number of bytes given.
virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes, bool as_uint = false) {
std::vector<int64_t> res;
@@ -618,395 +543,18 @@
return str;
}
- // Driver() assembles and compares the results. If the results are not equal and we have a
- // disassembler, disassemble both and check whether they have the same mnemonics (in which case
- // we just warn).
- void Driver(std::string assembly_text, std::string test_name) {
- EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
-
- NativeAssemblerResult res;
- Compile(assembly_text, &res, test_name);
-
- EXPECT_TRUE(res.ok) << res.error_msg;
- if (!res.ok) {
- // No way of continuing.
- return;
- }
-
+ void DriverWrapper(std::string assembly_text, std::string test_name) {
size_t cs = assembler_->CodeSize();
std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs));
MemoryRegion code(&(*data)[0], data->size());
assembler_->FinalizeInstructions(code);
-
- if (*data == *res.code) {
- Clean(&res);
- } else {
- if (DisassembleBinaries(*data, *res.code, test_name)) {
- if (data->size() > res.code->size()) {
- // Fail this test with a fancy colored warning being printed.
- EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
- "is equal: this implies sub-optimal encoding! Our code size=" << data->size() <<
- ", gcc size=" << res.code->size();
- } else {
- // Otherwise just print an info message and clean up.
- LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
- "same.";
- Clean(&res);
- }
- } else {
- // This will output the assembly.
- EXPECT_EQ(*res.code, *data) << "Outputs (and disassembly) not identical.";
- }
- }
- }
-
- // Structure to store intermediates and results.
- struct NativeAssemblerResult {
- bool ok;
- std::string error_msg;
- std::string base_name;
- std::unique_ptr<std::vector<uint8_t>> code;
- uintptr_t length;
- };
-
- // Compile the assembly file from_file to a binary file to_file. Returns true on success.
- bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
- bool have_assembler = FileExists(FindTool(GetAssemblerCmdName()));
- EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
- if (!have_assembler) {
- return false;
- }
-
- std::vector<std::string> args;
-
- // Encaspulate the whole command line in a single string passed to
- // the shell, so that GetAssemblerCommand() may contain arguments
- // in addition to the program name.
- args.push_back(GetAssemblerCommand());
- args.push_back("-o");
- args.push_back(to_file);
- args.push_back(from_file);
- std::string cmd = Join(args, ' ');
-
- args.clear();
- args.push_back("/bin/sh");
- args.push_back("-c");
- args.push_back(cmd);
-
- bool success = Exec(args, error_msg);
- if (!success) {
- LOG(INFO) << "Assembler command line:";
- for (std::string arg : args) {
- LOG(INFO) << arg;
- }
- }
- return success;
- }
-
- // Runs objdump -h on the binary file and extracts the first line with .text.
- // Returns "" on failure.
- std::string Objdump(std::string file) {
- bool have_objdump = FileExists(FindTool(GetObjdumpCmdName()));
- EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
- if (!have_objdump) {
- return "";
- }
-
- std::string error_msg;
- std::vector<std::string> args;
-
- // Encaspulate the whole command line in a single string passed to
- // the shell, so that GetObjdumpCommand() may contain arguments
- // in addition to the program name.
- args.push_back(GetObjdumpCommand());
- args.push_back(file);
- args.push_back(">");
- args.push_back(file+".dump");
- std::string cmd = Join(args, ' ');
-
- args.clear();
- args.push_back("/bin/sh");
- args.push_back("-c");
- args.push_back(cmd);
-
- if (!Exec(args, &error_msg)) {
- EXPECT_TRUE(false) << error_msg;
- }
-
- std::ifstream dump(file+".dump");
-
- std::string line;
- bool found = false;
- while (std::getline(dump, line)) {
- if (line.find(".text") != line.npos) {
- found = true;
- break;
- }
- }
-
- dump.close();
-
- if (found) {
- return line;
- } else {
- return "";
- }
- }
-
- // Disassemble both binaries and compare the text.
- bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as,
- std::string test_name) {
- std::string disassembler = GetDisassembleCommand();
- if (disassembler.length() == 0) {
- LOG(WARNING) << "No dissassembler command.";
- return false;
- }
-
- std::string data_name = WriteToFile(data, test_name + ".ass");
- std::string error_msg;
- if (!DisassembleBinary(data_name, &error_msg)) {
- LOG(INFO) << "Error disassembling: " << error_msg;
- std::remove(data_name.c_str());
- return false;
- }
-
- std::string as_name = WriteToFile(as, test_name + ".gcc");
- if (!DisassembleBinary(as_name, &error_msg)) {
- LOG(INFO) << "Error disassembling: " << error_msg;
- std::remove(data_name.c_str());
- std::remove((data_name + ".dis").c_str());
- std::remove(as_name.c_str());
- return false;
- }
-
- bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
-
- if (!kKeepDisassembledFiles) {
- std::remove(data_name.c_str());
- std::remove(as_name.c_str());
- std::remove((data_name + ".dis").c_str());
- std::remove((as_name + ".dis").c_str());
- }
-
- return result;
- }
-
- bool DisassembleBinary(std::string file, std::string* error_msg) {
- std::vector<std::string> args;
-
- // Encaspulate the whole command line in a single string passed to
- // the shell, so that GetDisassembleCommand() may contain arguments
- // in addition to the program name.
- args.push_back(GetDisassembleCommand());
- args.push_back(file);
- args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
- args.push_back(">");
- args.push_back(file+".dis");
- std::string cmd = Join(args, ' ');
-
- args.clear();
- args.push_back("/bin/sh");
- args.push_back("-c");
- args.push_back(cmd);
-
- return Exec(args, error_msg);
- }
-
- std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) {
- std::string file_name = GetTmpnam() + std::string("---") + test_name;
- const char* data = reinterpret_cast<char*>(buffer.data());
- std::ofstream s_out(file_name + ".o");
- s_out.write(data, buffer.size());
- s_out.close();
- return file_name + ".o";
- }
-
- bool CompareFiles(std::string f1, std::string f2) {
- std::ifstream f1_in(f1);
- std::ifstream f2_in(f2);
-
- bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
- std::istreambuf_iterator<char>(),
- std::istreambuf_iterator<char>(f2_in));
-
- f1_in.close();
- f2_in.close();
-
- return result;
- }
-
- // Compile the given assembly code and extract the binary, if possible. Put result into res.
- bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
- res->ok = false;
- res->code.reset(nullptr);
-
- res->base_name = GetTmpnam() + std::string("---") + test_name;
-
- // TODO: Lots of error checking.
-
- std::ofstream s_out(res->base_name + ".S");
- const char* header = GetAssemblyHeader();
- if (header != nullptr) {
- s_out << header;
- }
- s_out << assembly_code;
- s_out.close();
-
- if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
- &res->error_msg)) {
- res->error_msg = "Could not compile.";
- return false;
- }
-
- std::string odump = Objdump(res->base_name + ".o");
- if (odump.length() == 0) {
- res->error_msg = "Objdump failed.";
- return false;
- }
-
- std::istringstream iss(odump);
- std::istream_iterator<std::string> start(iss);
- std::istream_iterator<std::string> end;
- std::vector<std::string> tokens(start, end);
-
- if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
- res->error_msg = "Objdump output not recognized: too few tokens.";
- return false;
- }
-
- if (tokens[1] != ".text") {
- res->error_msg = "Objdump output not recognized: .text not second token.";
- return false;
- }
-
- std::string lengthToken = "0x" + tokens[2];
- std::istringstream(lengthToken) >> std::hex >> res->length;
-
- std::string offsetToken = "0x" + tokens[5];
- uintptr_t offset;
- std::istringstream(offsetToken) >> std::hex >> offset;
-
- std::ifstream obj(res->base_name + ".o");
- obj.seekg(offset);
- res->code.reset(new std::vector<uint8_t>(res->length));
- obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
- obj.close();
-
- res->ok = true;
- return true;
- }
-
- // Remove temporary files.
- void Clean(const NativeAssemblerResult* res) {
- std::remove((res->base_name + ".S").c_str());
- std::remove((res->base_name + ".o").c_str());
- std::remove((res->base_name + ".o.dump").c_str());
- }
-
- // Check whether file exists. Is used for commands, so strips off any parameters: anything after
- // the first space. We skip to the last slash for this, so it should work with directories with
- // spaces.
- static bool FileExists(std::string file) {
- if (file.length() == 0) {
- return false;
- }
-
- // Need to strip any options.
- size_t last_slash = file.find_last_of('/');
- if (last_slash == std::string::npos) {
- // No slash, start looking at the start.
- last_slash = 0;
- }
- size_t space_index = file.find(' ', last_slash);
-
- if (space_index == std::string::npos) {
- std::ifstream infile(file.c_str());
- return infile.good();
- } else {
- std::string copy = file.substr(0, space_index - 1);
-
- struct stat buf;
- return stat(copy.c_str(), &buf) == 0;
- }
- }
-
- static std::string GetGCCRootPath() {
- return "prebuilts/gcc/linux-x86";
- }
-
- static std::string GetRootPath() {
- // 1) Check ANDROID_BUILD_TOP
- char* build_top = getenv("ANDROID_BUILD_TOP");
- if (build_top != nullptr) {
- return std::string(build_top) + "/";
- }
-
- // 2) Do cwd
- char temp[1024];
- return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
- }
-
- std::string FindTool(std::string tool_name) {
- // Find the current tool. Wild-card pattern is "arch-string*tool-name".
- std::string gcc_path = GetRootPath() + GetGCCRootPath();
- std::vector<std::string> args;
- args.push_back("find");
- args.push_back(gcc_path);
- args.push_back("-name");
- args.push_back(GetArchitectureString() + "*" + tool_name);
- args.push_back("|");
- args.push_back("sort");
- args.push_back("|");
- args.push_back("tail");
- args.push_back("-n");
- args.push_back("1");
- std::string tmp_file = GetTmpnam();
- args.push_back(">");
- args.push_back(tmp_file);
- std::string sh_args = Join(args, ' ');
-
- args.clear();
- args.push_back("/bin/sh");
- args.push_back("-c");
- args.push_back(sh_args);
-
- std::string error_msg;
- if (!Exec(args, &error_msg)) {
- EXPECT_TRUE(false) << error_msg;
- return "";
- }
-
- std::ifstream in(tmp_file.c_str());
- std::string line;
- if (!std::getline(in, line)) {
- in.close();
- std::remove(tmp_file.c_str());
- return "";
- }
- in.close();
- std::remove(tmp_file.c_str());
- return line;
- }
-
- // Use a consistent tmpnam, so store it.
- std::string GetTmpnam() {
- if (tmpnam_.length() == 0) {
- ScratchFile tmp;
- tmpnam_ = tmp.GetFilename() + "asm";
- }
- return tmpnam_;
+ test_helper_->Driver(*data, assembly_text, test_name);
}
static constexpr size_t kWarnManyCombinationsThreshold = 500;
- static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
std::unique_ptr<Ass> assembler_;
-
- std::string resolved_assembler_cmd_;
- std::string resolved_objdump_cmd_;
- std::string resolved_disassemble_cmd_;
-
- std::string android_data_;
+ std::unique_ptr<AssemblerTestInfrastructure> test_helper_;
DISALLOW_COPY_AND_ASSIGN(AssemblerTest);
};
diff --git a/compiler/utils/assembler_test_base.h b/compiler/utils/assembler_test_base.h
new file mode 100644
index 0000000..3341151
--- /dev/null
+++ b/compiler/utils/assembler_test_base.h
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2014 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_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_
+#define ART_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_
+
+#include "common_runtime_test.h" // For ScratchFile
+
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <iterator>
+#include <sys/stat.h>
+
+namespace art {
+
+// If you want to take a look at the differences between the ART assembler and GCC, set this flag
+// to true. The disassembled files will then remain in the tmp directory.
+static constexpr bool kKeepDisassembledFiles = false;
+
+// Use a glocal static variable to keep the same name for all test data. Else we'll just spam the
+// temp directory.
+static std::string tmpnam_;
+
+// We put this into a class as gtests are self-contained, so this helper needs to be in an h-file.
+class AssemblerTestInfrastructure {
+ public:
+ AssemblerTestInfrastructure(std::string architecture,
+ std::string as,
+ std::string as_params,
+ std::string objdump,
+ std::string objdump_params,
+ std::string disasm,
+ std::string disasm_params,
+ const char* asm_header) :
+ architecture_string_(architecture),
+ asm_header_(asm_header),
+ assembler_cmd_name_(as),
+ assembler_parameters_(as_params),
+ objdump_cmd_name_(objdump),
+ objdump_parameters_(objdump_params),
+ disassembler_cmd_name_(disasm),
+ disassembler_parameters_(disasm_params) {
+ // Fake a runtime test for ScratchFile
+ CommonRuntimeTest::SetUpAndroidData(android_data_);
+ }
+
+ virtual ~AssemblerTestInfrastructure() {
+ // We leave temporaries in case this failed so we can debug issues.
+ CommonRuntimeTest::TearDownAndroidData(android_data_, false);
+ tmpnam_ = "";
+ }
+
+ // This is intended to be run as a test.
+ bool CheckTools() {
+ if (!FileExists(FindTool(assembler_cmd_name_))) {
+ return false;
+ }
+ LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand();
+
+ if (!FileExists(FindTool(objdump_cmd_name_))) {
+ return false;
+ }
+ LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand();
+
+ // Disassembly is optional.
+ std::string disassembler = GetDisassembleCommand();
+ if (disassembler.length() != 0) {
+ if (!FileExists(FindTool(disassembler_cmd_name_))) {
+ return false;
+ }
+ LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand();
+ } else {
+ LOG(INFO) << "No disassembler given.";
+ }
+
+ return true;
+ }
+
+ // Driver() assembles and compares the results. If the results are not equal and we have a
+ // disassembler, disassemble both and check whether they have the same mnemonics (in which case
+ // we just warn).
+ void Driver(const std::vector<uint8_t>& data, std::string assembly_text, std::string test_name) {
+ EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly";
+
+ NativeAssemblerResult res;
+ Compile(assembly_text, &res, test_name);
+
+ EXPECT_TRUE(res.ok) << res.error_msg;
+ if (!res.ok) {
+ // No way of continuing.
+ return;
+ }
+
+ if (data == *res.code) {
+ Clean(&res);
+ } else {
+ if (DisassembleBinaries(data, *res.code, test_name)) {
+ if (data.size() > res.code->size()) {
+ // Fail this test with a fancy colored warning being printed.
+ EXPECT_TRUE(false) << "Assembly code is not identical, but disassembly of machine code "
+ "is equal: this implies sub-optimal encoding! Our code size=" << data.size() <<
+ ", gcc size=" << res.code->size();
+ } else {
+ // Otherwise just print an info message and clean up.
+ LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the "
+ "same.";
+ Clean(&res);
+ }
+ } else {
+ // This will output the assembly.
+ EXPECT_EQ(*res.code, data) << "Outputs (and disassembly) not identical.";
+ }
+ }
+ }
+
+ protected:
+ // Return the host assembler command for this test.
+ virtual std::string GetAssemblerCommand() {
+ // Already resolved it once?
+ if (resolved_assembler_cmd_.length() != 0) {
+ return resolved_assembler_cmd_;
+ }
+
+ std::string line = FindTool(assembler_cmd_name_);
+ if (line.length() == 0) {
+ return line;
+ }
+
+ resolved_assembler_cmd_ = line + assembler_parameters_;
+
+ return resolved_assembler_cmd_;
+ }
+
+ // Return the host objdump command for this test.
+ virtual std::string GetObjdumpCommand() {
+ // Already resolved it once?
+ if (resolved_objdump_cmd_.length() != 0) {
+ return resolved_objdump_cmd_;
+ }
+
+ std::string line = FindTool(objdump_cmd_name_);
+ if (line.length() == 0) {
+ return line;
+ }
+
+ resolved_objdump_cmd_ = line + objdump_parameters_;
+
+ return resolved_objdump_cmd_;
+ }
+
+ // Return the host disassembler command for this test.
+ virtual std::string GetDisassembleCommand() {
+ // Already resolved it once?
+ if (resolved_disassemble_cmd_.length() != 0) {
+ return resolved_disassemble_cmd_;
+ }
+
+ std::string line = FindTool(disassembler_cmd_name_);
+ if (line.length() == 0) {
+ return line;
+ }
+
+ resolved_disassemble_cmd_ = line + disassembler_parameters_;
+
+ return resolved_disassemble_cmd_;
+ }
+
+ private:
+ // Structure to store intermediates and results.
+ struct NativeAssemblerResult {
+ bool ok;
+ std::string error_msg;
+ std::string base_name;
+ std::unique_ptr<std::vector<uint8_t>> code;
+ uintptr_t length;
+ };
+
+ // Compile the assembly file from_file to a binary file to_file. Returns true on success.
+ bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) {
+ bool have_assembler = FileExists(FindTool(assembler_cmd_name_));
+ EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand();
+ if (!have_assembler) {
+ return false;
+ }
+
+ std::vector<std::string> args;
+
+ // Encaspulate the whole command line in a single string passed to
+ // the shell, so that GetAssemblerCommand() may contain arguments
+ // in addition to the program name.
+ args.push_back(GetAssemblerCommand());
+ args.push_back("-o");
+ args.push_back(to_file);
+ args.push_back(from_file);
+ std::string cmd = Join(args, ' ');
+
+ args.clear();
+ args.push_back("/bin/sh");
+ args.push_back("-c");
+ args.push_back(cmd);
+
+ bool success = Exec(args, error_msg);
+ if (!success) {
+ LOG(INFO) << "Assembler command line:";
+ for (std::string arg : args) {
+ LOG(INFO) << arg;
+ }
+ }
+ return success;
+ }
+
+ // Runs objdump -h on the binary file and extracts the first line with .text.
+ // Returns "" on failure.
+ std::string Objdump(std::string file) {
+ bool have_objdump = FileExists(FindTool(objdump_cmd_name_));
+ EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand();
+ if (!have_objdump) {
+ return "";
+ }
+
+ std::string error_msg;
+ std::vector<std::string> args;
+
+ // Encaspulate the whole command line in a single string passed to
+ // the shell, so that GetObjdumpCommand() may contain arguments
+ // in addition to the program name.
+ args.push_back(GetObjdumpCommand());
+ args.push_back(file);
+ args.push_back(">");
+ args.push_back(file+".dump");
+ std::string cmd = Join(args, ' ');
+
+ args.clear();
+ args.push_back("/bin/sh");
+ args.push_back("-c");
+ args.push_back(cmd);
+
+ if (!Exec(args, &error_msg)) {
+ EXPECT_TRUE(false) << error_msg;
+ }
+
+ std::ifstream dump(file+".dump");
+
+ std::string line;
+ bool found = false;
+ while (std::getline(dump, line)) {
+ if (line.find(".text") != line.npos) {
+ found = true;
+ break;
+ }
+ }
+
+ dump.close();
+
+ if (found) {
+ return line;
+ } else {
+ return "";
+ }
+ }
+
+ // Disassemble both binaries and compare the text.
+ bool DisassembleBinaries(const std::vector<uint8_t>& data, const std::vector<uint8_t>& as,
+ std::string test_name) {
+ std::string disassembler = GetDisassembleCommand();
+ if (disassembler.length() == 0) {
+ LOG(WARNING) << "No dissassembler command.";
+ return false;
+ }
+
+ std::string data_name = WriteToFile(data, test_name + ".ass");
+ std::string error_msg;
+ if (!DisassembleBinary(data_name, &error_msg)) {
+ LOG(INFO) << "Error disassembling: " << error_msg;
+ std::remove(data_name.c_str());
+ return false;
+ }
+
+ std::string as_name = WriteToFile(as, test_name + ".gcc");
+ if (!DisassembleBinary(as_name, &error_msg)) {
+ LOG(INFO) << "Error disassembling: " << error_msg;
+ std::remove(data_name.c_str());
+ std::remove((data_name + ".dis").c_str());
+ std::remove(as_name.c_str());
+ return false;
+ }
+
+ bool result = CompareFiles(data_name + ".dis", as_name + ".dis");
+
+ if (!kKeepDisassembledFiles) {
+ std::remove(data_name.c_str());
+ std::remove(as_name.c_str());
+ std::remove((data_name + ".dis").c_str());
+ std::remove((as_name + ".dis").c_str());
+ }
+
+ return result;
+ }
+
+ bool DisassembleBinary(std::string file, std::string* error_msg) {
+ std::vector<std::string> args;
+
+ // Encaspulate the whole command line in a single string passed to
+ // the shell, so that GetDisassembleCommand() may contain arguments
+ // in addition to the program name.
+ args.push_back(GetDisassembleCommand());
+ args.push_back(file);
+ args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'");
+ args.push_back(">");
+ args.push_back(file+".dis");
+ std::string cmd = Join(args, ' ');
+
+ args.clear();
+ args.push_back("/bin/sh");
+ args.push_back("-c");
+ args.push_back(cmd);
+
+ return Exec(args, error_msg);
+ }
+
+ std::string WriteToFile(const std::vector<uint8_t>& buffer, std::string test_name) {
+ std::string file_name = GetTmpnam() + std::string("---") + test_name;
+ const char* data = reinterpret_cast<const char*>(buffer.data());
+ std::ofstream s_out(file_name + ".o");
+ s_out.write(data, buffer.size());
+ s_out.close();
+ return file_name + ".o";
+ }
+
+ bool CompareFiles(std::string f1, std::string f2) {
+ std::ifstream f1_in(f1);
+ std::ifstream f2_in(f2);
+
+ bool result = std::equal(std::istreambuf_iterator<char>(f1_in),
+ std::istreambuf_iterator<char>(),
+ std::istreambuf_iterator<char>(f2_in));
+
+ f1_in.close();
+ f2_in.close();
+
+ return result;
+ }
+
+ // Compile the given assembly code and extract the binary, if possible. Put result into res.
+ bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) {
+ res->ok = false;
+ res->code.reset(nullptr);
+
+ res->base_name = GetTmpnam() + std::string("---") + test_name;
+
+ // TODO: Lots of error checking.
+
+ std::ofstream s_out(res->base_name + ".S");
+ if (asm_header_ != nullptr) {
+ s_out << asm_header_;
+ }
+ s_out << assembly_code;
+ s_out.close();
+
+ if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(),
+ &res->error_msg)) {
+ res->error_msg = "Could not compile.";
+ return false;
+ }
+
+ std::string odump = Objdump(res->base_name + ".o");
+ if (odump.length() == 0) {
+ res->error_msg = "Objdump failed.";
+ return false;
+ }
+
+ std::istringstream iss(odump);
+ std::istream_iterator<std::string> start(iss);
+ std::istream_iterator<std::string> end;
+ std::vector<std::string> tokens(start, end);
+
+ if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) {
+ res->error_msg = "Objdump output not recognized: too few tokens.";
+ return false;
+ }
+
+ if (tokens[1] != ".text") {
+ res->error_msg = "Objdump output not recognized: .text not second token.";
+ return false;
+ }
+
+ std::string lengthToken = "0x" + tokens[2];
+ std::istringstream(lengthToken) >> std::hex >> res->length;
+
+ std::string offsetToken = "0x" + tokens[5];
+ uintptr_t offset;
+ std::istringstream(offsetToken) >> std::hex >> offset;
+
+ std::ifstream obj(res->base_name + ".o");
+ obj.seekg(offset);
+ res->code.reset(new std::vector<uint8_t>(res->length));
+ obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length);
+ obj.close();
+
+ res->ok = true;
+ return true;
+ }
+
+ // Remove temporary files.
+ void Clean(const NativeAssemblerResult* res) {
+ std::remove((res->base_name + ".S").c_str());
+ std::remove((res->base_name + ".o").c_str());
+ std::remove((res->base_name + ".o.dump").c_str());
+ }
+
+ // Check whether file exists. Is used for commands, so strips off any parameters: anything after
+ // the first space. We skip to the last slash for this, so it should work with directories with
+ // spaces.
+ static bool FileExists(std::string file) {
+ if (file.length() == 0) {
+ return false;
+ }
+
+ // Need to strip any options.
+ size_t last_slash = file.find_last_of('/');
+ if (last_slash == std::string::npos) {
+ // No slash, start looking at the start.
+ last_slash = 0;
+ }
+ size_t space_index = file.find(' ', last_slash);
+
+ if (space_index == std::string::npos) {
+ std::ifstream infile(file.c_str());
+ return infile.good();
+ } else {
+ std::string copy = file.substr(0, space_index - 1);
+
+ struct stat buf;
+ return stat(copy.c_str(), &buf) == 0;
+ }
+ }
+
+ static std::string GetGCCRootPath() {
+ return "prebuilts/gcc/linux-x86";
+ }
+
+ static std::string GetRootPath() {
+ // 1) Check ANDROID_BUILD_TOP
+ char* build_top = getenv("ANDROID_BUILD_TOP");
+ if (build_top != nullptr) {
+ return std::string(build_top) + "/";
+ }
+
+ // 2) Do cwd
+ char temp[1024];
+ return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string("");
+ }
+
+ std::string FindTool(std::string tool_name) {
+ // Find the current tool. Wild-card pattern is "arch-string*tool-name".
+ std::string gcc_path = GetRootPath() + GetGCCRootPath();
+ std::vector<std::string> args;
+ args.push_back("find");
+ args.push_back(gcc_path);
+ args.push_back("-name");
+ args.push_back(architecture_string_ + "*" + tool_name);
+ args.push_back("|");
+ args.push_back("sort");
+ args.push_back("|");
+ args.push_back("tail");
+ args.push_back("-n");
+ args.push_back("1");
+ std::string tmp_file = GetTmpnam();
+ args.push_back(">");
+ args.push_back(tmp_file);
+ std::string sh_args = Join(args, ' ');
+
+ args.clear();
+ args.push_back("/bin/sh");
+ args.push_back("-c");
+ args.push_back(sh_args);
+
+ std::string error_msg;
+ if (!Exec(args, &error_msg)) {
+ EXPECT_TRUE(false) << error_msg;
+ return "";
+ }
+
+ std::ifstream in(tmp_file.c_str());
+ std::string line;
+ if (!std::getline(in, line)) {
+ in.close();
+ std::remove(tmp_file.c_str());
+ return "";
+ }
+ in.close();
+ std::remove(tmp_file.c_str());
+ return line;
+ }
+
+ // Use a consistent tmpnam, so store it.
+ std::string GetTmpnam() {
+ if (tmpnam_.length() == 0) {
+ ScratchFile tmp;
+ tmpnam_ = tmp.GetFilename() + "asm";
+ }
+ return tmpnam_;
+ }
+
+ static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6;
+
+ std::string architecture_string_;
+ const char* asm_header_;
+
+ std::string assembler_cmd_name_;
+ std::string assembler_parameters_;
+
+ std::string objdump_cmd_name_;
+ std::string objdump_parameters_;
+
+ std::string disassembler_cmd_name_;
+ std::string disassembler_parameters_;
+
+ std::string resolved_assembler_cmd_;
+ std::string resolved_objdump_cmd_;
+ std::string resolved_disassemble_cmd_;
+
+ std::string android_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(AssemblerTestInfrastructure);
+};
+
+} // namespace art
+
+#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_BASE_H_
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index dc8bf2a..8a0c315 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -31,6 +31,7 @@
#include "base/scoped_flock.h"
#include "base/stl_util.h"
#include "base/unix_file/fd_file.h"
+#include "base/value_object.h"
#include "class_linker-inl.h"
#include "compiler_callbacks.h"
#include "debugger.h"
@@ -81,6 +82,10 @@
static constexpr bool kSanityCheckObjects = kIsDebugBuild;
+// Do a simple class redefinition check in OpenDexFilesFromOat. This is a conservative check to
+// avoid problems with compile-time class-path != runtime class-path.
+static constexpr bool kCheckForDexCollisions = true;
+
static void ThrowNoClassDefFoundError(const char* fmt, ...)
__attribute__((__format__(__printf__, 1, 2)))
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
@@ -712,6 +717,186 @@
return *oat_file;
}
+class DexFileAndClassPair : ValueObject {
+ public:
+ DexFileAndClassPair(const DexFile* dex_file, size_t current_class_index, bool from_loaded_oat)
+ : cached_descriptor_(GetClassDescriptor(dex_file, current_class_index)),
+ dex_file_(dex_file),
+ current_class_index_(current_class_index),
+ from_loaded_oat_(from_loaded_oat) {}
+
+ DexFileAndClassPair(const DexFileAndClassPair&) = default;
+
+ DexFileAndClassPair& operator=(const DexFileAndClassPair& rhs) {
+ cached_descriptor_ = rhs.cached_descriptor_;
+ dex_file_ = rhs.dex_file_;
+ current_class_index_ = rhs.current_class_index_;
+ from_loaded_oat_ = rhs.from_loaded_oat_;
+ return *this;
+ }
+
+ const char* GetCachedDescriptor() const {
+ return cached_descriptor_;
+ }
+
+ bool operator<(const DexFileAndClassPair& rhs) const {
+ const char* lhsDescriptor = cached_descriptor_;
+ const char* rhsDescriptor = rhs.cached_descriptor_;
+ int cmp = strcmp(lhsDescriptor, rhsDescriptor);
+ if (cmp != 0) {
+ return cmp > 0;
+ }
+ return dex_file_ < rhs.dex_file_;
+ }
+
+ bool DexFileHasMoreClasses() const {
+ return current_class_index_ + 1 < dex_file_->NumClassDefs();
+ }
+
+ DexFileAndClassPair GetNext() const {
+ return DexFileAndClassPair(dex_file_, current_class_index_ + 1, from_loaded_oat_);
+ }
+
+ size_t GetCurrentClassIndex() const {
+ return current_class_index_;
+ }
+
+ bool FromLoadedOat() const {
+ return from_loaded_oat_;
+ }
+
+ const DexFile* GetDexFile() const {
+ return dex_file_;
+ }
+
+ private:
+ static const char* GetClassDescriptor(const DexFile* dex_file, size_t index) {
+ const DexFile::ClassDef& class_def = dex_file->GetClassDef(static_cast<uint16_t>(index));
+ return dex_file->StringByTypeIdx(class_def.class_idx_);
+ }
+
+ const char* cached_descriptor_;
+ const DexFile* dex_file_;
+ size_t current_class_index_;
+ bool from_loaded_oat_; // We only need to compare mismatches between what we load now
+ // and what was loaded before. Any old duplicates must have been
+ // OK, and any new "internal" duplicates are as well (they must
+ // be from multidex, which resolves correctly).
+};
+
+static void AddDexFilesFromOat(const OatFile* oat_file, bool already_loaded,
+ std::priority_queue<DexFileAndClassPair>* heap) {
+ const std::vector<const OatDexFile*>& oat_dex_files = oat_file->GetOatDexFiles();
+ for (const OatDexFile* oat_dex_file : oat_dex_files) {
+ std::string error;
+ std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error);
+ if (dex_file.get() == nullptr) {
+ LOG(WARNING) << "Could not create dex file from oat file: " << error;
+ } else {
+ if (dex_file->NumClassDefs() > 0U) {
+ heap->emplace(dex_file.release(), 0U, already_loaded);
+ }
+ }
+ }
+}
+
+static void AddNext(const DexFileAndClassPair& original,
+ std::priority_queue<DexFileAndClassPair>* heap) {
+ if (original.DexFileHasMoreClasses()) {
+ heap->push(original.GetNext());
+ } else {
+ // Need to delete the dex file.
+ delete original.GetDexFile();
+ }
+}
+
+static void FreeDexFilesInHeap(std::priority_queue<DexFileAndClassPair>* heap) {
+ while (!heap->empty()) {
+ delete heap->top().GetDexFile();
+ heap->pop();
+ }
+}
+
+// Check for class-def collisions in dex files.
+//
+// This works by maintaining a heap with one class from each dex file, sorted by the class
+// descriptor. Then a dex-file/class pair is continually removed from the heap and compared
+// against the following top element. If the descriptor is the same, it is now checked whether
+// the two elements agree on whether their dex file was from an already-loaded oat-file or the
+// new oat file. Any disagreement indicates a collision.
+bool ClassLinker::HasCollisions(const OatFile* oat_file, std::string* error_msg) {
+ if (!kCheckForDexCollisions) {
+ return false;
+ }
+
+ // Dex files are registered late - once a class is actually being loaded. We have to compare
+ // against the open oat files.
+ ReaderMutexLock mu(Thread::Current(), dex_lock_);
+
+ std::priority_queue<DexFileAndClassPair> heap;
+
+ // Add dex files from already loaded oat files, but skip boot.
+ {
+ // To grab the boot oat, look at the dex files in the boot classpath.
+ const OatFile* boot_oat = nullptr;
+ if (!boot_class_path_.empty()) {
+ const DexFile* boot_dex_file = boot_class_path_[0];
+ // Is it from an oat file?
+ if (boot_dex_file->GetOatDexFile() != nullptr) {
+ boot_oat = boot_dex_file->GetOatDexFile()->GetOatFile();
+ }
+ }
+
+ for (const OatFile* loaded_oat_file : oat_files_) {
+ if (loaded_oat_file == boot_oat) {
+ continue;
+ }
+ AddDexFilesFromOat(loaded_oat_file, true, &heap);
+ }
+ }
+
+ if (heap.empty()) {
+ // No other oat files, return early.
+ return false;
+ }
+
+ // Add dex files from the oat file to check.
+ AddDexFilesFromOat(oat_file, false, &heap);
+
+ // Now drain the heap.
+ while (!heap.empty()) {
+ DexFileAndClassPair compare_pop = heap.top();
+ heap.pop();
+
+ // Compare against the following elements.
+ while (!heap.empty()) {
+ DexFileAndClassPair top = heap.top();
+
+ if (strcmp(compare_pop.GetCachedDescriptor(), top.GetCachedDescriptor()) == 0) {
+ // Same descriptor. Check whether it's crossing old-oat-files to new-oat-files.
+ if (compare_pop.FromLoadedOat() != top.FromLoadedOat()) {
+ *error_msg =
+ StringPrintf("Found duplicated class when checking oat files: '%s' in %s and %s",
+ compare_pop.GetCachedDescriptor(),
+ compare_pop.GetDexFile()->GetLocation().c_str(),
+ top.GetDexFile()->GetLocation().c_str());
+ FreeDexFilesInHeap(&heap);
+ return true;
+ }
+ // Pop it.
+ heap.pop();
+ AddNext(top, &heap);
+ } else {
+ // Something else. Done here.
+ break;
+ }
+ }
+ AddNext(compare_pop, &heap);
+ }
+
+ return false;
+}
+
std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat(
const char* dex_location, const char* oat_location,
std::vector<std::string>* error_msgs) {
@@ -757,8 +942,20 @@
// Get the oat file on disk.
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
if (oat_file.get() != nullptr) {
- source_oat_file = oat_file.release();
- RegisterOatFile(source_oat_file);
+ // Take the file only if it has no collisions.
+ if (!HasCollisions(oat_file.get(), &error_msg)) {
+ source_oat_file = oat_file.release();
+ RegisterOatFile(source_oat_file);
+ } else {
+ if (Runtime::Current()->IsDexFileFallbackEnabled()) {
+ LOG(WARNING) << "Found duplicate classes, falling back to interpreter mode for "
+ << dex_location;
+ } else {
+ LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to "
+ " load classes for " << dex_location;
+ }
+ LOG(WARNING) << error_msg;
+ }
}
}
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 1bd9f0a..57989b2 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -663,6 +663,9 @@
// a recreation with a custom string.
void ThrowEarlierClassFailure(mirror::Class* c) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ // Check for duplicate class definitions of the given oat file against all open oat files.
+ bool HasCollisions(const OatFile* oat_file, std::string* error_msg) LOCKS_EXCLUDED(dex_lock_);
+
std::vector<const DexFile*> boot_class_path_;
std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc
index 2603975..a66c38e 100644
--- a/runtime/dex_file_verifier.cc
+++ b/runtime/dex_file_verifier.cc
@@ -944,7 +944,7 @@
uint32_t type_idx = DecodeUnsignedLeb128(&ptr_);
if (type_idx != 0) {
type_idx--;
- if (!CheckIndex(type_idx, header_->string_ids_size_, "DBG_START_LOCAL type_idx")) {
+ if (!CheckIndex(type_idx, header_->type_ids_size_, "DBG_START_LOCAL type_idx")) {
return false;
}
}
@@ -975,7 +975,7 @@
uint32_t type_idx = DecodeUnsignedLeb128(&ptr_);
if (type_idx != 0) {
type_idx--;
- if (!CheckIndex(type_idx, header_->string_ids_size_, "DBG_START_LOCAL_EXTENDED type_idx")) {
+ if (!CheckIndex(type_idx, header_->type_ids_size_, "DBG_START_LOCAL_EXTENDED type_idx")) {
return false;
}
}
diff --git a/runtime/dex_file_verifier_test.cc b/runtime/dex_file_verifier_test.cc
index 95a47cc..9f1ffec 100644
--- a/runtime/dex_file_verifier_test.cc
+++ b/runtime/dex_file_verifier_test.cc
@@ -200,11 +200,11 @@
return dex_file;
}
-static bool ModifyAndLoad(const char* location, size_t offset, uint8_t new_val,
- std::string* error_msg) {
+static bool ModifyAndLoad(const char* dex_file_content, const char* location, size_t offset,
+ uint8_t new_val, std::string* error_msg) {
// Decode base64.
size_t length;
- std::unique_ptr<uint8_t[]> dex_bytes(DecodeBase64(kGoodTestDex, &length));
+ std::unique_ptr<uint8_t[]> dex_bytes(DecodeBase64(dex_file_content, &length));
CHECK(dex_bytes.get() != nullptr);
// Make modifications.
@@ -221,7 +221,7 @@
// Class error.
ScratchFile tmp;
std::string error_msg;
- bool success = !ModifyAndLoad(tmp.GetFilename().c_str(), 220, 0xFFU, &error_msg);
+ bool success = !ModifyAndLoad(kGoodTestDex, tmp.GetFilename().c_str(), 220, 0xFFU, &error_msg);
ASSERT_TRUE(success);
ASSERT_NE(error_msg.find("inter_method_id_item class_idx"), std::string::npos) << error_msg;
}
@@ -230,7 +230,7 @@
// Proto error.
ScratchFile tmp;
std::string error_msg;
- bool success = !ModifyAndLoad(tmp.GetFilename().c_str(), 222, 0xFFU, &error_msg);
+ bool success = !ModifyAndLoad(kGoodTestDex, tmp.GetFilename().c_str(), 222, 0xFFU, &error_msg);
ASSERT_TRUE(success);
ASSERT_NE(error_msg.find("inter_method_id_item proto_idx"), std::string::npos) << error_msg;
}
@@ -239,10 +239,81 @@
// Name error.
ScratchFile tmp;
std::string error_msg;
- bool success = !ModifyAndLoad(tmp.GetFilename().c_str(), 224, 0xFFU, &error_msg);
+ bool success = !ModifyAndLoad(kGoodTestDex, tmp.GetFilename().c_str(), 224, 0xFFU, &error_msg);
ASSERT_TRUE(success);
ASSERT_NE(error_msg.find("inter_method_id_item name_idx"), std::string::npos) << error_msg;
}
}
+// Generated from:
+//
+// .class public LTest;
+// .super Ljava/lang/Object;
+// .source "Test.java"
+//
+// .method public constructor <init>()V
+// .registers 1
+//
+// .prologue
+// .line 1
+// invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+//
+// return-void
+// .end method
+//
+// .method public static main()V
+// .registers 2
+//
+// const-string v0, "a"
+// const-string v0, "b"
+// const-string v0, "c"
+// const-string v0, "d"
+// const-string v0, "e"
+// const-string v0, "f"
+// const-string v0, "g"
+// const-string v0, "h"
+// const-string v0, "i"
+// const-string v0, "j"
+// const-string v0, "k"
+//
+// .local v1, "local_var":Ljava/lang/String;
+// const-string v1, "test"
+// .end method
+
+static const char kDebugInfoTestDex[] =
+ "ZGV4CjAzNQCHRkHix2eIMQgvLD/0VGrlllZLo0Rb6VyUAgAAcAAAAHhWNBIAAAAAAAAAAAwCAAAU"
+ "AAAAcAAAAAQAAADAAAAAAQAAANAAAAAAAAAAAAAAAAMAAADcAAAAAQAAAPQAAACAAQAAFAEAABQB"
+ "AAAcAQAAJAEAADgBAABMAQAAVwEAAFoBAABdAQAAYAEAAGMBAABmAQAAaQEAAGwBAABvAQAAcgEA"
+ "AHUBAAB4AQAAewEAAIYBAACMAQAAAQAAAAIAAAADAAAABQAAAAUAAAADAAAAAAAAAAAAAAAAAAAA"
+ "AAAAABIAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAEAAAAAAAAAPwBAAAAAAAABjxpbml0PgAG"
+ "TFRlc3Q7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAJVGVzdC5qYXZh"
+ "AAFWAAFhAAFiAAFjAAFkAAFlAAFmAAFnAAFoAAFpAAFqAAFrAAlsb2NhbF92YXIABG1haW4ABHRl"
+ "c3QAAAABAAcOAAAAARYDARIDAAAAAQABAAEAAACUAQAABAAAAHAQAgAAAA4AAgAAAAAAAACZAQAA"
+ "GAAAABoABgAaAAcAGgAIABoACQAaAAoAGgALABoADAAaAA0AGgAOABoADwAaABAAGgETAAAAAgAA"
+ "gYAEpAMBCbwDAAALAAAAAAAAAAEAAAAAAAAAAQAAABQAAABwAAAAAgAAAAQAAADAAAAAAwAAAAEA"
+ "AADQAAAABQAAAAMAAADcAAAABgAAAAEAAAD0AAAAAiAAABQAAAAUAQAAAyAAAAIAAACUAQAAASAA"
+ "AAIAAACkAQAAACAAAAEAAAD8AQAAABAAAAEAAAAMAgAA";
+
+TEST_F(DexFileVerifierTest, DebugInfoTypeIdxTest) {
+ {
+ // The input dex file should be good before modification.
+ ScratchFile tmp;
+ std::string error_msg;
+ std::unique_ptr<const DexFile> raw(OpenDexFileBase64(kDebugInfoTestDex,
+ tmp.GetFilename().c_str(),
+ &error_msg));
+ ASSERT_TRUE(raw.get() != nullptr) << error_msg;
+ }
+
+ {
+ // Modify the debug information entry.
+ ScratchFile tmp;
+ std::string error_msg;
+ bool success = !ModifyAndLoad(kDebugInfoTestDex, tmp.GetFilename().c_str(), 416, 0x14U,
+ &error_msg);
+ ASSERT_TRUE(success);
+ ASSERT_NE(error_msg.find("DBG_START_LOCAL type_idx"), std::string::npos) << error_msg;
+ }
+}
+
} // namespace art
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index af01a02..8524348 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -65,6 +65,7 @@
DEBUG_ENABLE_SAFEMODE = 1 << 3,
DEBUG_ENABLE_JNI_LOGGING = 1 << 4,
DEBUG_ENABLE_JIT = 1 << 5,
+ DEBUG_GENERATE_CFI = 1 << 6,
};
Runtime* const runtime = Runtime::Current();
@@ -111,6 +112,12 @@
}
runtime->GetJITOptions()->SetUseJIT(use_jit);
+ const bool generate_cfi = (debug_flags & DEBUG_GENERATE_CFI) != 0;
+ if (generate_cfi) {
+ runtime->AddCompilerOption("--include-cfi");
+ debug_flags &= ~DEBUG_GENERATE_CFI;
+ }
+
// This is for backwards compatibility with Dalvik.
debug_flags &= ~DEBUG_ENABLE_ASSERT;
diff --git a/test/138-duplicate-classes-check/build b/test/138-duplicate-classes-check/build
new file mode 100755
index 0000000..7ddc81d
--- /dev/null
+++ b/test/138-duplicate-classes-check/build
@@ -0,0 +1,31 @@
+#!/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.
+
+# Stop if something fails.
+set -e
+
+mkdir classes
+${JAVAC} -d classes `find src -name '*.java'`
+
+mkdir classes-ex
+${JAVAC} -d classes-ex `find src-ex -name '*.java'`
+
+if [ ${NEED_DEX} = "true" ]; then
+ ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex --dump-width=1000 classes
+ zip $TEST_NAME.jar classes.dex
+ ${DX} -JXmx256m --debug --dex --dump-to=classes-ex.lst --output=classes.dex --dump-width=1000 classes-ex
+ zip ${TEST_NAME}-ex.jar classes.dex
+fi
diff --git a/test/138-duplicate-classes-check/expected.txt b/test/138-duplicate-classes-check/expected.txt
new file mode 100644
index 0000000..b2f7f08
--- /dev/null
+++ b/test/138-duplicate-classes-check/expected.txt
@@ -0,0 +1,2 @@
+10
+10
diff --git a/test/138-duplicate-classes-check/info.txt b/test/138-duplicate-classes-check/info.txt
new file mode 100644
index 0000000..22a66a2
--- /dev/null
+++ b/test/138-duplicate-classes-check/info.txt
@@ -0,0 +1 @@
+Check whether a duplicate class is detected.
diff --git a/test/138-duplicate-classes-check/src-ex/A.java b/test/138-duplicate-classes-check/src-ex/A.java
new file mode 100644
index 0000000..8e52cb3
--- /dev/null
+++ b/test/138-duplicate-classes-check/src-ex/A.java
@@ -0,0 +1,23 @@
+/*
+ * 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 A {
+ public volatile int i;
+
+ public A() {
+ i = 10;
+ }
+}
diff --git a/test/138-duplicate-classes-check/src-ex/TestEx.java b/test/138-duplicate-classes-check/src-ex/TestEx.java
new file mode 100644
index 0000000..87558fa
--- /dev/null
+++ b/test/138-duplicate-classes-check/src-ex/TestEx.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class TestEx {
+ public static void test() {
+ System.out.println(new A().i);
+ }
+}
diff --git a/test/138-duplicate-classes-check/src/A.java b/test/138-duplicate-classes-check/src/A.java
new file mode 100644
index 0000000..e1773e5
--- /dev/null
+++ b/test/138-duplicate-classes-check/src/A.java
@@ -0,0 +1,28 @@
+/*
+ * 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 A {
+ // Object fields add padding in the Foo class object layout. Therefore the field 'i' should
+ // be at a different offset compared to the A class from the ex DEX file.
+ public final Object anObject = null;
+ public final Object anotherObject = null;
+ // Use volatile to defeat inlining of the constructor + load-elimination.
+ public volatile int i;
+
+ public A() {
+ i = 10;
+ }
+}
diff --git a/test/138-duplicate-classes-check/src/FancyLoader.java b/test/138-duplicate-classes-check/src/FancyLoader.java
new file mode 100644
index 0000000..03ec948
--- /dev/null
+++ b/test/138-duplicate-classes-check/src/FancyLoader.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A class loader with atypical behavior: we try to load a private
+ * class implementation before asking the system or boot loader. This
+ * is used to create multiple classes with identical names in a single VM.
+ *
+ * If DexFile is available, we use that; if not, we assume we're not in
+ * Dalvik and instantiate the class with defineClass().
+ *
+ * The location of the DEX files and class data is dependent upon the
+ * test framework.
+ */
+public class FancyLoader extends ClassLoader {
+ /* this is where the "alternate" .class files live */
+ static final String CLASS_PATH = "classes-ex/";
+
+ /* this is the "alternate" DEX/Jar file */
+ static final String DEX_FILE = System.getenv("DEX_LOCATION") +
+ "/138-duplicate-classes-check-ex.jar";
+
+ /* on Dalvik, this is a DexFile; otherwise, it's null */
+ private Class mDexClass;
+
+ private Object mDexFile;
+
+ /**
+ * Construct FancyLoader, grabbing a reference to the DexFile class
+ * if we're running under Dalvik.
+ */
+ public FancyLoader(ClassLoader parent) {
+ super(parent);
+
+ try {
+ mDexClass = parent.loadClass("dalvik.system.DexFile");
+ } catch (ClassNotFoundException cnfe) {
+ // ignore -- not running Dalvik
+ }
+ }
+
+ /**
+ * Finds the class with the specified binary name.
+ *
+ * We search for a file in CLASS_PATH or pull an entry from DEX_FILE.
+ * If we don't find a match, we throw an exception.
+ */
+ protected Class<?> findClass(String name) throws ClassNotFoundException
+ {
+ if (mDexClass != null) {
+ return findClassDalvik(name);
+ } else {
+ return findClassNonDalvik(name);
+ }
+ }
+
+ /**
+ * Finds the class with the specified binary name, from a DEX file.
+ */
+ private Class<?> findClassDalvik(String name)
+ throws ClassNotFoundException {
+
+ if (mDexFile == null) {
+ synchronized (FancyLoader.class) {
+ Constructor ctor;
+ /*
+ * Construct a DexFile object through reflection.
+ */
+ try {
+ ctor = mDexClass.getConstructor(new Class[] {String.class});
+ } catch (NoSuchMethodException nsme) {
+ throw new ClassNotFoundException("getConstructor failed",
+ nsme);
+ }
+
+ try {
+ mDexFile = ctor.newInstance(DEX_FILE);
+ } catch (InstantiationException ie) {
+ throw new ClassNotFoundException("newInstance failed", ie);
+ } catch (IllegalAccessException iae) {
+ throw new ClassNotFoundException("newInstance failed", iae);
+ } catch (InvocationTargetException ite) {
+ throw new ClassNotFoundException("newInstance failed", ite);
+ }
+ }
+ }
+
+ /*
+ * Call DexFile.loadClass(String, ClassLoader).
+ */
+ Method meth;
+
+ try {
+ meth = mDexClass.getMethod("loadClass",
+ new Class[] { String.class, ClassLoader.class });
+ } catch (NoSuchMethodException nsme) {
+ throw new ClassNotFoundException("getMethod failed", nsme);
+ }
+
+ try {
+ meth.invoke(mDexFile, name, this);
+ } catch (IllegalAccessException iae) {
+ throw new ClassNotFoundException("loadClass failed", iae);
+ } catch (InvocationTargetException ite) {
+ throw new ClassNotFoundException("loadClass failed",
+ ite.getCause());
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the class with the specified binary name, from .class files.
+ */
+ private Class<?> findClassNonDalvik(String name)
+ throws ClassNotFoundException {
+
+ String pathName = CLASS_PATH + name + ".class";
+ //System.out.println("--- Fancy: looking for " + pathName);
+
+ File path = new File(pathName);
+ RandomAccessFile raf;
+
+ try {
+ raf = new RandomAccessFile(path, "r");
+ } catch (FileNotFoundException fnfe) {
+ throw new ClassNotFoundException("Not found: " + pathName);
+ }
+
+ /* read the entire file in */
+ byte[] fileData;
+ try {
+ fileData = new byte[(int) raf.length()];
+ raf.readFully(fileData);
+ } catch (IOException ioe) {
+ throw new ClassNotFoundException("Read error: " + pathName);
+ } finally {
+ try {
+ raf.close();
+ } catch (IOException ioe) {
+ // drop
+ }
+ }
+
+ /* create the class */
+ //System.out.println("--- Fancy: defining " + name);
+ try {
+ return defineClass(name, fileData, 0, fileData.length);
+ } catch (Throwable th) {
+ throw new ClassNotFoundException("defineClass failed", th);
+ }
+ }
+
+ /**
+ * Load a class.
+ *
+ * Normally a class loader wouldn't override this, but we want our
+ * version of the class to take precedence over an already-loaded
+ * version.
+ *
+ * We still want the system classes (e.g. java.lang.Object) from the
+ * bootstrap class loader.
+ */
+ protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ Class res;
+
+ /*
+ * 1. Invoke findLoadedClass(String) to check if the class has
+ * already been loaded.
+ *
+ * This doesn't change.
+ */
+ res = findLoadedClass(name);
+ if (res != null) {
+ System.out.println("FancyLoader.loadClass: "
+ + name + " already loaded");
+ if (resolve)
+ resolveClass(res);
+ return res;
+ }
+
+ /*
+ * 3. Invoke the findClass(String) method to find the class.
+ */
+ try {
+ res = findClass(name);
+ if (resolve)
+ resolveClass(res);
+ }
+ catch (ClassNotFoundException e) {
+ // we couldn't find it, so eat the exception and keep going
+ }
+
+ /*
+ * 2. Invoke the loadClass method on the parent class loader. If
+ * the parent loader is null the class loader built-in to the
+ * virtual machine is used, instead.
+ *
+ * (Since we're not in java.lang, we can't actually invoke the
+ * parent's loadClass() method, but we passed our parent to the
+ * super-class which can take care of it for us.)
+ */
+ res = super.loadClass(name, resolve); // returns class or throws
+ return res;
+ }
+}
diff --git a/test/138-duplicate-classes-check/src/Main.java b/test/138-duplicate-classes-check/src/Main.java
new file mode 100644
index 0000000..a9b5bb0
--- /dev/null
+++ b/test/138-duplicate-classes-check/src/Main.java
@@ -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.
+ */
+
+import java.io.File;
+import java.lang.reflect.Method;
+
+/**
+ * Structural hazard test.
+ */
+public class Main {
+ public static void main(String[] args) {
+ new Main().run();
+ }
+
+ private void run() {
+ System.out.println(new A().i);
+
+ // Now run the class from the -ex file.
+
+ FancyLoader loader = new FancyLoader(getClass().getClassLoader());
+
+ try {
+ Class testEx = loader.loadClass("TestEx");
+ Method test = testEx.getDeclaredMethod("test");
+ test.invoke(null);
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ }
+ }
+}
diff --git a/test/138-duplicate-classes-check2/build b/test/138-duplicate-classes-check2/build
new file mode 100755
index 0000000..abcbbb8
--- /dev/null
+++ b/test/138-duplicate-classes-check2/build
@@ -0,0 +1,32 @@
+#!/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.
+
+# Stop if something fails.
+set -e
+
+mkdir classes
+${JAVAC} -d classes `find src -name '*.java'`
+
+mkdir classes-ex
+${JAVAC} -d classes-ex `find src-ex -name '*.java'`
+rm classes-ex/A.class
+
+if [ ${NEED_DEX} = "true" ]; then
+ ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex --dump-width=1000 classes
+ zip $TEST_NAME.jar classes.dex
+ ${DX} -JXmx256m --debug --dex --dump-to=classes-ex.lst --output=classes.dex --dump-width=1000 classes-ex
+ zip ${TEST_NAME}-ex.jar classes.dex
+fi
diff --git a/test/138-duplicate-classes-check2/expected.txt b/test/138-duplicate-classes-check2/expected.txt
new file mode 100644
index 0000000..b2f7f08
--- /dev/null
+++ b/test/138-duplicate-classes-check2/expected.txt
@@ -0,0 +1,2 @@
+10
+10
diff --git a/test/138-duplicate-classes-check2/info.txt b/test/138-duplicate-classes-check2/info.txt
new file mode 100644
index 0000000..7100122
--- /dev/null
+++ b/test/138-duplicate-classes-check2/info.txt
@@ -0,0 +1,2 @@
+Check whether a duplicate class is not detected, even though we compiled against one (but removed
+it before creating the dex file).
diff --git a/test/138-duplicate-classes-check2/run b/test/138-duplicate-classes-check2/run
new file mode 100755
index 0000000..8494ad9
--- /dev/null
+++ b/test/138-duplicate-classes-check2/run
@@ -0,0 +1,19 @@
+#!/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.
+
+# We want to run as no-dex-file-fallback to confirm that even though the -ex file has a symbolic
+# reference to A, there's no class-def, so we don't detect a collision.
+exec ${RUN} --runtime-option -Xno-dex-file-fallback "${@}"
diff --git a/test/138-duplicate-classes-check2/src-ex/A.java b/test/138-duplicate-classes-check2/src-ex/A.java
new file mode 100644
index 0000000..8e52cb3
--- /dev/null
+++ b/test/138-duplicate-classes-check2/src-ex/A.java
@@ -0,0 +1,23 @@
+/*
+ * 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 A {
+ public volatile int i;
+
+ public A() {
+ i = 10;
+ }
+}
diff --git a/test/138-duplicate-classes-check2/src-ex/TestEx.java b/test/138-duplicate-classes-check2/src-ex/TestEx.java
new file mode 100644
index 0000000..87558fa
--- /dev/null
+++ b/test/138-duplicate-classes-check2/src-ex/TestEx.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class TestEx {
+ public static void test() {
+ System.out.println(new A().i);
+ }
+}
diff --git a/test/138-duplicate-classes-check2/src/A.java b/test/138-duplicate-classes-check2/src/A.java
new file mode 100644
index 0000000..e1773e5
--- /dev/null
+++ b/test/138-duplicate-classes-check2/src/A.java
@@ -0,0 +1,28 @@
+/*
+ * 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 A {
+ // Object fields add padding in the Foo class object layout. Therefore the field 'i' should
+ // be at a different offset compared to the A class from the ex DEX file.
+ public final Object anObject = null;
+ public final Object anotherObject = null;
+ // Use volatile to defeat inlining of the constructor + load-elimination.
+ public volatile int i;
+
+ public A() {
+ i = 10;
+ }
+}
diff --git a/test/138-duplicate-classes-check2/src/FancyLoader.java b/test/138-duplicate-classes-check2/src/FancyLoader.java
new file mode 100644
index 0000000..7e2bb08
--- /dev/null
+++ b/test/138-duplicate-classes-check2/src/FancyLoader.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A class loader with atypical behavior: we try to load a private
+ * class implementation before asking the system or boot loader. This
+ * is used to create multiple classes with identical names in a single VM.
+ *
+ * If DexFile is available, we use that; if not, we assume we're not in
+ * Dalvik and instantiate the class with defineClass().
+ *
+ * The location of the DEX files and class data is dependent upon the
+ * test framework.
+ */
+public class FancyLoader extends ClassLoader {
+ /* this is where the "alternate" .class files live */
+ static final String CLASS_PATH = "classes-ex/";
+
+ /* this is the "alternate" DEX/Jar file */
+ static final String DEX_FILE = System.getenv("DEX_LOCATION") +
+ "/138-duplicate-classes-check2-ex.jar";
+
+ /* on Dalvik, this is a DexFile; otherwise, it's null */
+ private Class mDexClass;
+
+ private Object mDexFile;
+
+ /**
+ * Construct FancyLoader, grabbing a reference to the DexFile class
+ * if we're running under Dalvik.
+ */
+ public FancyLoader(ClassLoader parent) {
+ super(parent);
+
+ try {
+ mDexClass = parent.loadClass("dalvik.system.DexFile");
+ } catch (ClassNotFoundException cnfe) {
+ // ignore -- not running Dalvik
+ }
+ }
+
+ /**
+ * Finds the class with the specified binary name.
+ *
+ * We search for a file in CLASS_PATH or pull an entry from DEX_FILE.
+ * If we don't find a match, we throw an exception.
+ */
+ protected Class<?> findClass(String name) throws ClassNotFoundException
+ {
+ if (mDexClass != null) {
+ return findClassDalvik(name);
+ } else {
+ return findClassNonDalvik(name);
+ }
+ }
+
+ /**
+ * Finds the class with the specified binary name, from a DEX file.
+ */
+ private Class<?> findClassDalvik(String name)
+ throws ClassNotFoundException {
+
+ if (mDexFile == null) {
+ synchronized (FancyLoader.class) {
+ Constructor ctor;
+ /*
+ * Construct a DexFile object through reflection.
+ */
+ try {
+ ctor = mDexClass.getConstructor(new Class[] {String.class});
+ } catch (NoSuchMethodException nsme) {
+ throw new ClassNotFoundException("getConstructor failed",
+ nsme);
+ }
+
+ try {
+ mDexFile = ctor.newInstance(DEX_FILE);
+ } catch (InstantiationException ie) {
+ throw new ClassNotFoundException("newInstance failed", ie);
+ } catch (IllegalAccessException iae) {
+ throw new ClassNotFoundException("newInstance failed", iae);
+ } catch (InvocationTargetException ite) {
+ throw new ClassNotFoundException("newInstance failed", ite);
+ }
+ }
+ }
+
+ /*
+ * Call DexFile.loadClass(String, ClassLoader).
+ */
+ Method meth;
+
+ try {
+ meth = mDexClass.getMethod("loadClass",
+ new Class[] { String.class, ClassLoader.class });
+ } catch (NoSuchMethodException nsme) {
+ throw new ClassNotFoundException("getMethod failed", nsme);
+ }
+
+ try {
+ meth.invoke(mDexFile, name, this);
+ } catch (IllegalAccessException iae) {
+ throw new ClassNotFoundException("loadClass failed", iae);
+ } catch (InvocationTargetException ite) {
+ throw new ClassNotFoundException("loadClass failed",
+ ite.getCause());
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the class with the specified binary name, from .class files.
+ */
+ private Class<?> findClassNonDalvik(String name)
+ throws ClassNotFoundException {
+
+ String pathName = CLASS_PATH + name + ".class";
+ //System.out.println("--- Fancy: looking for " + pathName);
+
+ File path = new File(pathName);
+ RandomAccessFile raf;
+
+ try {
+ raf = new RandomAccessFile(path, "r");
+ } catch (FileNotFoundException fnfe) {
+ throw new ClassNotFoundException("Not found: " + pathName);
+ }
+
+ /* read the entire file in */
+ byte[] fileData;
+ try {
+ fileData = new byte[(int) raf.length()];
+ raf.readFully(fileData);
+ } catch (IOException ioe) {
+ throw new ClassNotFoundException("Read error: " + pathName);
+ } finally {
+ try {
+ raf.close();
+ } catch (IOException ioe) {
+ // drop
+ }
+ }
+
+ /* create the class */
+ //System.out.println("--- Fancy: defining " + name);
+ try {
+ return defineClass(name, fileData, 0, fileData.length);
+ } catch (Throwable th) {
+ throw new ClassNotFoundException("defineClass failed", th);
+ }
+ }
+
+ /**
+ * Load a class.
+ *
+ * Normally a class loader wouldn't override this, but we want our
+ * version of the class to take precedence over an already-loaded
+ * version.
+ *
+ * We still want the system classes (e.g. java.lang.Object) from the
+ * bootstrap class loader.
+ */
+ protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ Class res;
+
+ /*
+ * 1. Invoke findLoadedClass(String) to check if the class has
+ * already been loaded.
+ *
+ * This doesn't change.
+ */
+ res = findLoadedClass(name);
+ if (res != null) {
+ System.out.println("FancyLoader.loadClass: "
+ + name + " already loaded");
+ if (resolve)
+ resolveClass(res);
+ return res;
+ }
+
+ /*
+ * 3. Invoke the findClass(String) method to find the class.
+ */
+ try {
+ res = findClass(name);
+ if (resolve)
+ resolveClass(res);
+ }
+ catch (ClassNotFoundException e) {
+ // we couldn't find it, so eat the exception and keep going
+ }
+
+ /*
+ * 2. Invoke the loadClass method on the parent class loader. If
+ * the parent loader is null the class loader built-in to the
+ * virtual machine is used, instead.
+ *
+ * (Since we're not in java.lang, we can't actually invoke the
+ * parent's loadClass() method, but we passed our parent to the
+ * super-class which can take care of it for us.)
+ */
+ res = super.loadClass(name, resolve); // returns class or throws
+ return res;
+ }
+}
diff --git a/test/138-duplicate-classes-check2/src/Main.java b/test/138-duplicate-classes-check2/src/Main.java
new file mode 100644
index 0000000..a9b5bb0
--- /dev/null
+++ b/test/138-duplicate-classes-check2/src/Main.java
@@ -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.
+ */
+
+import java.io.File;
+import java.lang.reflect.Method;
+
+/**
+ * Structural hazard test.
+ */
+public class Main {
+ public static void main(String[] args) {
+ new Main().run();
+ }
+
+ private void run() {
+ System.out.println(new A().i);
+
+ // Now run the class from the -ex file.
+
+ FancyLoader loader = new FancyLoader(getClass().getClassLoader());
+
+ try {
+ Class testEx = loader.loadClass("TestEx");
+ Method test = testEx.getDeclaredMethod("test");
+ test.invoke(null);
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ }
+ }
+}
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index c5abd46..93340fb 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -95,7 +95,7 @@
RELOCATE_TYPES += no-relocate
endif
ifeq ($(ART_TEST_RUN_TEST_RELOCATE_NO_PATCHOAT),true)
- RELOCATE_TYPES := relocate-npatchoat
+ RELOCATE_TYPES += relocate-npatchoat
endif
TRACE_TYPES := ntrace
ifeq ($(ART_TEST_TRACE),true)
@@ -250,6 +250,12 @@
$(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES),$(IMAGE_TYPES), \
$(PICTEST_TYPES),$(DEBUGGABLE_TYPES),130-hprof,$(ALL_ADDRESS_SIZES))
+# 131 is an old test. The functionality has been implemented at an earlier stage and is checked
+# in tests 138.
+ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
+ $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES),$(IMAGE_TYPES), \
+ $(PICTEST_TYPES),$(DEBUGGABLE_TYPES),131-structural-change,$(ALL_ADDRESS_SIZES))
+
# All these tests check that we have sane behavior if we don't have a patchoat or dex2oat.
# Therefore we shouldn't run them in situations where we actually don't have these since they
# explicitly test for them. These all also assume we have an image.
@@ -257,7 +263,12 @@
116-nodex2oat \
117-nopatchoat \
118-noimage-dex2oat \
- 119-noimage-patchoat
+ 119-noimage-patchoat \
+ 138-duplicate-classes-check2
+
+# This test fails without an image.
+TEST_ART_BROKEN_NO_IMAGE_RUN_TESTS := \
+ 138-duplicate-classes-check
ifneq (,$(filter no-dex2oat,$(PREBUILD_TYPES)))
ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),no-dex2oat, \
@@ -270,6 +281,9 @@
ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
$(COMPILER_TYPES), $(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES),no-image, \
$(PICTEST_TYPES), $(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_FALLBACK_RUN_TESTS),$(ALL_ADDRESS_SIZES))
+ ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
+ $(COMPILER_TYPES), $(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES),no-image, \
+ $(PICTEST_TYPES), $(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_NO_IMAGE_RUN_TESTS),$(ALL_ADDRESS_SIZES))
endif
ifneq (,$(filter relocate-npatchoat,$(RELOCATE_TYPES)))
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 2040b57..a387036 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -124,5 +124,11 @@
description: "Needs kernel updates on host/device",
result: EXEC_FAILED,
names: ["libcore.io.OsTest#test_socketPing"]
+},
+{
+ description: "Linker issues in chrooted environment",
+ modes: [device],
+ result: EXEC_FAILED,
+ names: ["org.apache.harmony.tests.java.lang.ProcessManagerTest#testEnvironment"]
}
]