Merge "Switch to using ELF-64 for 64-bit architectures."
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 730e61d..5d4f1d3 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -30,6 +30,7 @@
Interfaces \
Main \
MultiDex \
+ MultiDexModifiedSecondary \
MyClass \
MyClassNatives \
Nested \
@@ -68,7 +69,7 @@
ART_GTEST_instrumentation_test_DEX_DEPS := Instrumentation
ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives
ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods
-ART_GTEST_oat_file_assistant_test_DEX_DEPS := Main MainStripped MultiDex Nested
+ART_GTEST_oat_file_assistant_test_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested
ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex
ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY
ART_GTEST_proxy_test_DEX_DEPS := Interfaces
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 7b37f74..1c76630 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -391,7 +391,7 @@
location_builder_(graph, this),
instruction_visitor_(graph, this),
move_resolver_(graph->GetArena(), this),
- assembler_(true),
+ assembler_(false /* can_relocate_branches */),
isa_features_(isa_features) {
// Save the PC register to mimic Quick.
AddAllocatedRegister(Location::RegisterLocation(PC));
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index be28755..7da4f2d 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -265,6 +265,21 @@
StartAttributeStream("kind") << barrier->GetBarrierKind();
}
+ void VisitLoadClass(HLoadClass* load_cass) OVERRIDE {
+ StartAttributeStream("gen_clinit_check") << std::boolalpha
+ << load_cass->MustGenerateClinitCheck() << std::noboolalpha;
+ }
+
+ void VisitCheckCast(HCheckCast* check_cast) OVERRIDE {
+ StartAttributeStream("must_do_null_check") << std::boolalpha
+ << check_cast->MustDoNullCheck() << std::noboolalpha;
+ }
+
+ void VisitInstanceOf(HInstanceOf* instance_of) OVERRIDE {
+ StartAttributeStream("must_do_null_check") << std::boolalpha
+ << instance_of->MustDoNullCheck() << std::noboolalpha;
+ }
+
bool IsPass(const char* name) {
return strcmp(pass_name_, name) == 0;
}
diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc
index 0adb931..fcb3471 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -65,6 +65,7 @@
void VisitUShr(HUShr* instruction) OVERRIDE;
void VisitXor(HXor* instruction) OVERRIDE;
void VisitInstanceOf(HInstanceOf* instruction) OVERRIDE;
+ bool IsDominatedByInputNullCheck(HInstruction* instr);
OptimizingCompilerStats* stats_;
bool simplification_occurred_ = false;
@@ -174,9 +175,20 @@
}
}
+bool InstructionSimplifierVisitor::IsDominatedByInputNullCheck(HInstruction* instr) {
+ HInstruction* input = instr->InputAt(0);
+ for (HUseIterator<HInstruction*> it(input->GetUses()); !it.Done(); it.Advance()) {
+ HInstruction* use = it.Current()->GetUser();
+ if (use->IsNullCheck() && use->StrictlyDominates(instr)) {
+ return true;
+ }
+ }
+ return false;
+}
+
void InstructionSimplifierVisitor::VisitCheckCast(HCheckCast* check_cast) {
HLoadClass* load_class = check_cast->InputAt(1)->AsLoadClass();
- if (!check_cast->InputAt(0)->CanBeNull()) {
+ if (!check_cast->InputAt(0)->CanBeNull() || IsDominatedByInputNullCheck(check_cast)) {
check_cast->ClearMustDoNullCheck();
}
@@ -198,7 +210,7 @@
}
void InstructionSimplifierVisitor::VisitInstanceOf(HInstanceOf* instruction) {
- if (!instruction->InputAt(0)->CanBeNull()) {
+ if (!instruction->InputAt(0)->CanBeNull() || IsDominatedByInputNullCheck(instruction)) {
instruction->ClearMustDoNullCheck();
}
}
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 2850368..b712e5e 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -3444,8 +3444,8 @@
return generate_clinit_check_;
}
- void SetMustGenerateClinitCheck() {
- generate_clinit_check_ = true;
+ void SetMustGenerateClinitCheck(bool generate_clinit_check) {
+ generate_clinit_check_ = generate_clinit_check;
}
bool CanCallRuntime() const {
@@ -3920,7 +3920,9 @@
}
for (size_t i = 0, e = moves_.Size(); i < e; ++i) {
DCHECK(!destination.OverlapsWith(moves_.Get(i).GetDestination()))
- << "Overlapped destination for two moves in a parallel move.";
+ << "Overlapped destination for two moves in a parallel move: "
+ << moves_.Get(i).GetSource() << " ==> " << moves_.Get(i).GetDestination() << " and "
+ << source << " ==> " << destination;
}
}
moves_.Add(MoveOperands(source, destination, type, instruction));
diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc
index 78d1185..538736b 100644
--- a/compiler/optimizing/prepare_for_register_allocation.cc
+++ b/compiler/optimizing/prepare_for_register_allocation.cc
@@ -53,7 +53,7 @@
if (check->GetPrevious() == cls) {
// Pass the initialization duty to the `HLoadClass` instruction,
// and remove the instruction from the graph.
- cls->SetMustGenerateClinitCheck();
+ cls->SetMustGenerateClinitCheck(true);
check->GetBlock()->RemoveInstruction(check);
}
}
@@ -82,8 +82,15 @@
void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) {
if (invoke->IsStaticWithExplicitClinitCheck()) {
size_t last_input_index = invoke->InputCount() - 1;
- HInstruction* last_input = invoke->InputAt(last_input_index);
- DCHECK(last_input->IsLoadClass()) << last_input->DebugName();
+ HLoadClass* last_input = invoke->InputAt(last_input_index)->AsLoadClass();
+ DCHECK(last_input != nullptr)
+ << "Last input is not HLoadClass. It is " << last_input->DebugName();
+
+ // The static call will initialize the class so there's no need for a clinit check if
+ // it's the first user.
+ if (last_input == invoke->GetPrevious()) {
+ last_input->SetMustGenerateClinitCheck(false);
+ }
// Remove a load class instruction as last input of a static
// invoke, which has been added (along with a clinit check,
diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc
index 59a2852..aac5211 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -504,7 +504,7 @@
// typed and the value in a dex register will not be used for both floating point and
// non-floating point operations. So the only reason an instruction would want a floating
// point equivalent is for an unused phi that will be removed by the dead phi elimination phase.
- DCHECK(user->IsPhi());
+ DCHECK(user->IsPhi()) << "is actually " << user->DebugName() << " (" << user->GetId() << ")";
return value;
}
}
diff --git a/compiler/utils/arm/assembler_thumb2.cc b/compiler/utils/arm/assembler_thumb2.cc
index e7cf26e..75f2b77 100644
--- a/compiler/utils/arm/assembler_thumb2.cc
+++ b/compiler/utils/arm/assembler_thumb2.cc
@@ -1638,7 +1638,6 @@
// branch the size may change if it so happens that other branches change size that change
// the distance to the target and that distance puts this branch over the limit for 16 bits.
if (size == Branch::k16Bit) {
- DCHECK(!force_32bit_branches_);
Emit16(0); // Space for a 16 bit branch.
} else {
Emit32(0); // Space for a 32 bit branch.
@@ -1646,7 +1645,7 @@
} else {
// Branch is to an unbound label. Emit space for it.
uint16_t branch_id = AddBranch(branch_type, pc, cond); // Unresolved branch.
- if (force_32bit_branches_ || force_32bit_) {
+ if (!CanRelocateBranches() || force_32bit_) {
Emit16(static_cast<uint16_t>(label->position_)); // Emit current label link.
Emit16(0); // another 16 bits.
} else {
@@ -2282,7 +2281,7 @@
uint32_t branch_location = branch->GetLocation();
uint16_t next = buffer_.Load<uint16_t>(branch_location); // Get next in chain.
if (changed) {
- DCHECK(!force_32bit_branches_);
+ DCHECK(CanRelocateBranches());
MakeHoleForBranch(branch->GetLocation(), 2);
if (branch->IsCompareAndBranch()) {
// A cbz/cbnz instruction has changed size. There is no valid encoding for
@@ -2742,21 +2741,21 @@
void Thumb2Assembler::CompareAndBranchIfZero(Register r, Label* label) {
- if (force_32bit_branches_) {
+ if (CanRelocateBranches()) {
+ cbz(r, label);
+ } else {
cmp(r, ShifterOperand(0));
b(label, EQ);
- } else {
- cbz(r, label);
}
}
void Thumb2Assembler::CompareAndBranchIfNonZero(Register r, Label* label) {
- if (force_32bit_branches_) {
+ if (CanRelocateBranches()) {
+ cbnz(r, label);
+ } else {
cmp(r, ShifterOperand(0));
b(label, NE);
- } else {
- cbnz(r, label);
}
}
} // namespace arm
diff --git a/compiler/utils/arm/assembler_thumb2.h b/compiler/utils/arm/assembler_thumb2.h
index 17eae8b..90d489f 100644
--- a/compiler/utils/arm/assembler_thumb2.h
+++ b/compiler/utils/arm/assembler_thumb2.h
@@ -31,8 +31,8 @@
class Thumb2Assembler FINAL : public ArmAssembler {
public:
- explicit Thumb2Assembler(bool force_32bit_branches = false)
- : force_32bit_branches_(force_32bit_branches),
+ explicit Thumb2Assembler(bool can_relocate_branches = true)
+ : can_relocate_branches_(can_relocate_branches),
force_32bit_(false),
it_cond_index_(kNoItCondition),
next_condition_(AL) {
@@ -52,8 +52,8 @@
return force_32bit_;
}
- bool IsForced32BitBranches() const {
- return force_32bit_branches_;
+ bool CanRelocateBranches() const {
+ return can_relocate_branches_;
}
void FinalizeInstructions(const MemoryRegion& region) OVERRIDE {
@@ -439,8 +439,12 @@
void EmitShift(Register rd, Register rm, Shift shift, uint8_t amount, bool setcc = false);
void EmitShift(Register rd, Register rn, Shift shift, Register rm, bool setcc = false);
- bool force_32bit_branches_; // Force the assembler to use 32 bit branch instructions.
- bool force_32bit_; // Force the assembler to use 32 bit thumb2 instructions.
+ // Whether the assembler can relocate branches. If false, unresolved branches will be
+ // emitted on 32bits.
+ bool can_relocate_branches_;
+
+ // Force the assembler to use 32 bit thumb2 instructions.
+ bool force_32bit_;
// IfThen conditions. Used to check that conditional instructions match the preceding IT.
Condition it_conditions_[4];
@@ -556,12 +560,21 @@
// size of the branch to change return true. Otherwise return false.
bool Resolve(uint32_t target) {
target_ = target;
- Size newsize = CalculateSize();
- if (size_ != newsize) {
- size_ = newsize;
- return true;
+ if (assembler_->CanRelocateBranches()) {
+ Size new_size = CalculateSize();
+ if (size_ != new_size) {
+ size_ = new_size;
+ return true;
+ }
+ return false;
+ } else {
+ if (kIsDebugBuild) {
+ Size new_size = CalculateSize();
+ // Check that the size has not increased.
+ DCHECK(!(new_size == k32Bit && size_ == k16Bit));
+ }
+ return false;
}
- return false;
}
// Move a cbz/cbnz branch. This is always forward.
@@ -577,6 +590,7 @@
// size of the branch instruction. It returns true if the branch
// has changed size.
bool Relocate(uint32_t oldlocation, int32_t delta) {
+ DCHECK(assembler_->CanRelocateBranches());
if (location_ > oldlocation) {
location_ += delta;
}
@@ -589,9 +603,9 @@
}
// Calculate the new size.
- Size newsize = CalculateSize();
- if (size_ != newsize) {
- size_ = newsize;
+ Size new_size = CalculateSize();
+ if (size_ != new_size) {
+ size_ = new_size;
return true;
}
return false;
@@ -633,15 +647,13 @@
private:
// Calculate the size of the branch instruction based on its type and offset.
Size CalculateSize() const {
- if (assembler_->IsForced32BitBranches()) {
- return k32Bit;
- }
if (target_ == kUnresolved) {
if (assembler_->IsForced32Bit() && (type_ == kUnconditional || type_ == kConditional)) {
return k32Bit;
}
- return k16Bit;
+ return assembler_->CanRelocateBranches() ? k16Bit : k32Bit;
}
+ // When the target is resolved, we know the best encoding for it.
int32_t delta = target_ - location_ - 4;
if (delta < 0) {
delta = -delta;
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 7488578..3c145d7 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -313,8 +313,7 @@
/*
* All generated callsites for interface invokes and invocation slow paths will load arguments
* as usual - except instead of loading arg0/r0 with the target Method*, arg0/r0 will contain
- * the method_idx. This wrapper will save arg1-arg3, load the caller's Method*, align the
- * stack and call the appropriate C helper.
+ * the method_idx. This wrapper will save arg1-arg3, and call the appropriate C helper.
* NOTE: "this" is first visible argument of the target, and so can be found in arg1/r1.
*
* The helper will attempt to locate the target and return a 64-bit result in r0/r1 consisting
@@ -330,13 +329,10 @@
.extern \cxx_name
ENTRY \c_name
SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME r2, r3 @ save callee saves in case allocation triggers GC
- ldr r2, [sp, #FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE] @ pass caller Method*
- mov r3, r9 @ pass Thread::Current
- mov r12, sp
- str r12, [sp, #-16]! @ expand the frame and pass SP
+ mov r2, r9 @ pass Thread::Current
+ mov r3, sp
.cfi_adjust_cfa_offset 16
bl \cxx_name @ (method_idx, this, caller, Thread*, SP)
- add sp, #16 @ strip the extra frame
.cfi_adjust_cfa_offset -16
mov r12, r1 @ save Method*->code_
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index f8b0734..6b16a2e5 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -459,8 +459,7 @@
/*
* All generated callsites for interface invokes and invocation slow paths will load arguments
* as usual - except instead of loading arg0/x0 with the target Method*, arg0/x0 will contain
- * the method_idx. This wrapper will save arg1-arg3, load the caller's Method*, align the
- * stack and call the appropriate C helper.
+ * the method_idx. This wrapper will save arg1-arg3, and call the appropriate C helper.
* NOTE: "this" is first visible argument of the target, and so can be found in arg1/x1.
*
* The helper will attempt to locate the target and return a 128-bit result in x0/x1 consisting
@@ -483,10 +482,9 @@
// Helper signature is always
// (method_idx, *this_object, *caller_method, *self, sp)
- ldr w2, [sp, #FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE] // pass caller Method*
- mov x3, xSELF // pass Thread::Current
- mov x4, sp
- bl \cxx_name // (method_idx, this, caller, Thread*, SP)
+ mov x2, xSELF // pass Thread::Current
+ mov x3, sp
+ bl \cxx_name // (method_idx, this, Thread*, SP)
mov xIP0, x1 // save Method*->code_
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
cbz x0, 1f // did we find the target? if not go to exception delivery
diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S
index ee5c59f..92b180e 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -439,8 +439,7 @@
/*
* All generated callsites for interface invokes and invocation slow paths will load arguments
* as usual - except instead of loading arg0/$a0 with the target Method*, arg0/$a0 will contain
- * the method_idx. This wrapper will save arg1-arg3, load the caller's Method*, align the
- * stack and call the appropriate C helper.
+ * the method_idx. This wrapper will save arg1-arg3, and call the appropriate C helper.
* NOTE: "this" is first visable argument of the target, and so can be found in arg1/$a1.
*
* The helper will attempt to locate the target and return a 64-bit result in $v0/$v1 consisting
@@ -456,15 +455,13 @@
.extern \cxx_name
ENTRY \c_name
SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME # save callee saves in case allocation triggers GC
- lw $a2, FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE+ARG_SLOT_SIZE($sp) # pass caller Method*
- addiu $t0, $sp, ARG_SLOT_SIZE # save $sp (remove arg slots)
- move $a3, rSELF # pass Thread::Current
- jal \cxx_name # (method_idx, this, caller, Thread*, $sp)
- sw $t0, 16($sp) # pass $sp
- move $a0, $v0 # save target Method*
+ move $a2, rSELF # pass Thread::Current
+ jal \cxx_name # (method_idx, this, Thread*, $sp)
+ addiu $a3, $sp, ARG_SLOT_SIZE # pass $sp (remove arg slots)
+ move $a0, $v0 # save target Method*
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
beqz $v0, 1f
- move $t9, $v1 # save $v0->code_
+ move $t9, $v1 # save $v0->code_
jalr $zero, $t9
nop
1:
diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S
index ff79b5d..b7320a6 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -529,10 +529,9 @@
.extern \cxx_name
ENTRY \c_name
SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME # save callee saves in case allocation triggers GC
- lwu $a2, FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE($sp) # pass caller Method*
- move $a3, rSELF # pass Thread::Current
- jal \cxx_name # (method_idx, this, caller, Thread*, $sp)
- move $a4, $sp # pass $sp
+ move $a2, rSELF # pass Thread::Current
+ jal \cxx_name # (method_idx, this, Thread*, $sp)
+ move $a3, $sp # pass $sp
move $a0, $v0 # save target Method*
move $t9, $v1 # save $v0->code_
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 6ebeba3..d62c1bc 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -278,8 +278,7 @@
/*
* All generated callsites for interface invokes and invocation slow paths will load arguments
* as usual - except instead of loading arg0/r0 with the target Method*, arg0/r0 will contain
- * the method_idx. This wrapper will save arg1-arg3, load the caller's Method*, align the
- * stack and call the appropriate C helper.
+ * the method_idx. This wrapper will save arg1-arg3 and call the appropriate C helper.
* NOTE: "this" is first visible argument of the target, and so can be found in arg1/r1.
*
* The helper will attempt to locate the target and return a 64-bit result in r0/r1 consisting
@@ -297,19 +296,15 @@
movl %esp, %edx // remember SP
// Outgoing argument set up
- subl MACRO_LITERAL(12), %esp // alignment padding
- CFI_ADJUST_CFA_OFFSET(12)
PUSH edx // pass SP
pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current()
CFI_ADJUST_CFA_OFFSET(4)
- pushl 32+32(%edx) // pass caller Method*
- CFI_ADJUST_CFA_OFFSET(4)
PUSH ecx // pass arg2
PUSH eax // pass arg1
call VAR(cxx_name, 1) // cxx_name(arg1, arg2, arg3, Thread*, SP)
movl %edx, %edi // save code pointer in EDI
- addl MACRO_LITERAL(36), %esp // Pop arguments skip eax
- CFI_ADJUST_CFA_OFFSET(-36)
+ addl MACRO_LITERAL(20), %esp // Pop arguments skip eax
+ CFI_ADJUST_CFA_OFFSET(-20)
// Restore FPRs.
movsd 0(%esp), %xmm0
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index da4d92b..ddeb5b8 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -341,8 +341,7 @@
/*
* All generated callsites for interface invokes and invocation slow paths will load arguments
* as usual - except instead of loading arg0/rdi with the target Method*, arg0/rdi will contain
- * the method_idx. This wrapper will save arg1-arg3, load the caller's Method*, align the
- * stack and call the appropriate C helper.
+ * the method_idx. This wrapper will save arg1-arg3, and call the appropriate C helper.
* NOTE: "this" is first visible argument of the target, and so can be found in arg1/rsi.
*
* The helper will attempt to locate the target and return a 128-bit result in rax/rdx consisting
@@ -362,11 +361,10 @@
// Helper signature is always
// (method_idx, *this_object, *caller_method, *self, sp)
- movl FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE(%rsp), %edx // pass caller Method*
- movq %gs:THREAD_SELF_OFFSET, %rcx // pass Thread
- movq %rsp, %r8 // pass SP
+ movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread
+ movq %rsp, %rcx // pass SP
- call VAR(cxx_name, 1) // cxx_name(arg1, arg2, caller method*, Thread*, SP)
+ call VAR(cxx_name, 1) // cxx_name(arg1, arg2, Thread*, SP)
// save the code pointer
movq %rax, %rdi
movq %rdx, %rax
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index 9292cff..625e695 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -38,25 +38,34 @@
namespace art {
-inline mirror::ArtMethod* GetCalleeSaveMethodCaller(Thread* self, Runtime::CalleeSaveType type)
+inline mirror::ArtMethod* GetCalleeSaveMethodCaller(StackReference<mirror::ArtMethod>* sp,
+ Runtime::CalleeSaveType type,
+ bool do_caller_check = false)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- auto* refs_only_sp = self->GetManagedStack()->GetTopQuickFrame();
- DCHECK_EQ(refs_only_sp->AsMirrorPtr(), Runtime::Current()->GetCalleeSaveMethod(type));
+ DCHECK_EQ(sp->AsMirrorPtr(), Runtime::Current()->GetCalleeSaveMethod(type));
const size_t callee_frame_size = GetCalleeSaveFrameSize(kRuntimeISA, type);
auto* caller_sp = reinterpret_cast<StackReference<mirror::ArtMethod>*>(
- reinterpret_cast<uintptr_t>(refs_only_sp) + callee_frame_size);
+ reinterpret_cast<uintptr_t>(sp) + callee_frame_size);
auto* caller = caller_sp->AsMirrorPtr();
- if (kIsDebugBuild) {
- NthCallerVisitor visitor(self, 1, true);
+ if (kIsDebugBuild && do_caller_check) {
+ // Note that do_caller_check is optional, as this method can be called by
+ // stubs, and tests without a proper call stack.
+ NthCallerVisitor visitor(Thread::Current(), 1, true);
visitor.WalkStack();
- CHECK(caller == visitor.caller);
+ CHECK_EQ(caller, visitor.caller);
}
return caller;
}
+inline mirror::ArtMethod* GetCalleeSaveMethodCaller(Thread* self, Runtime::CalleeSaveType type)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return GetCalleeSaveMethodCaller(
+ self->GetManagedStack()->GetTopQuickFrame(), type, true /* do_caller_check */);
+}
+
template <const bool kAccessCheck>
ALWAYS_INLINE
inline mirror::Class* CheckObjectAlloc(uint32_t type_idx,
diff --git a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
index 46629f5..9148878 100644
--- a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
@@ -25,8 +25,7 @@
namespace art {
-extern "C" mirror::Class* artInitializeStaticStorageFromCode(uint32_t type_idx,
- Thread* self)
+extern "C" mirror::Class* artInitializeStaticStorageFromCode(uint32_t type_idx, Thread* self)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// Called to ensure static storage base is initialized for direct static field reads and writes.
// A class may be accessing another class' fields when it doesn't have access, as access has been
@@ -36,8 +35,7 @@
return ResolveVerifyAndClinit(type_idx, caller, self, true, false);
}
-extern "C" mirror::Class* artInitializeTypeFromCode(uint32_t type_idx,
- Thread* self)
+extern "C" mirror::Class* artInitializeTypeFromCode(uint32_t type_idx, Thread* self)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// Called when method->dex_cache_resolved_types_[] misses.
ScopedQuickEntrypointChecks sqec(self);
@@ -45,8 +43,7 @@
return ResolveVerifyAndClinit(type_idx, caller, self, false, false);
}
-extern "C" mirror::Class* artInitializeTypeAndVerifyAccessFromCode(uint32_t type_idx,
- Thread* self)
+extern "C" mirror::Class* artInitializeTypeAndVerifyAccessFromCode(uint32_t type_idx, Thread* self)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// Called when caller isn't guaranteed to have access to a type and the dex cache may be
// unpopulated.
@@ -55,8 +52,7 @@
return ResolveVerifyAndClinit(type_idx, caller, self, false, true);
}
-extern "C" mirror::String* artResolveStringFromCode(int32_t string_idx,
- Thread* self)
+extern "C" mirror::String* artResolveStringFromCode(int32_t string_idx, Thread* self)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
ScopedQuickEntrypointChecks sqec(self);
auto* caller = GetCalleeSaveMethodCaller(self, Runtime::kRefsOnly);
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index 2e7e2df..345b0ad 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -294,8 +294,13 @@
static mirror::ArtMethod* GetCallingMethod(StackReference<mirror::ArtMethod>* sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
DCHECK(sp->AsMirrorPtr()->IsCalleeSaveMethod());
- uint8_t* previous_sp = reinterpret_cast<uint8_t*>(sp) + kQuickCalleeSaveFrame_RefAndArgs_FrameSize;
- return reinterpret_cast<StackReference<mirror::ArtMethod>*>(previous_sp)->AsMirrorPtr();
+ return GetCalleeSaveMethodCaller(sp, Runtime::kRefsAndArgs);
+ }
+
+ static uint32_t GetCallingDexPc(StackReference<mirror::ArtMethod>* sp)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ DCHECK(sp->AsMirrorPtr()->IsCalleeSaveMethod());
+ return GetCallingMethod(sp)->ToDexPc(QuickArgumentVisitor::GetCallingPc(sp));
}
// For the given quick ref and args quick frame, return the caller's PC.
@@ -827,12 +832,13 @@
// Compute details about the called method (avoid GCs)
ClassLinker* linker = Runtime::Current()->GetClassLinker();
- mirror::ArtMethod* caller = QuickArgumentVisitor::GetCallingMethod(sp);
InvokeType invoke_type;
MethodReference called_method(nullptr, 0);
const bool called_method_known_on_entry = !called->IsRuntimeMethod();
+ mirror::ArtMethod* caller = nullptr;
if (!called_method_known_on_entry) {
- uint32_t dex_pc = caller->ToDexPc(QuickArgumentVisitor::GetCallingPc(sp));
+ caller = QuickArgumentVisitor::GetCallingMethod(sp);
+ uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
const DexFile::CodeItem* code;
called_method.dex_file = caller->GetDexFile();
code = caller->GetCodeItem();
@@ -1946,16 +1952,13 @@
// to hold the mutator lock (see SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) annotations).
template<InvokeType type, bool access_check>
-static TwoWordReturn artInvokeCommon(uint32_t method_idx, mirror::Object* this_object,
- mirror::ArtMethod* caller_method,
- Thread* self, StackReference<mirror::ArtMethod>* sp);
-
-template<InvokeType type, bool access_check>
-static TwoWordReturn artInvokeCommon(uint32_t method_idx, mirror::Object* this_object,
- mirror::ArtMethod* caller_method,
- Thread* self, StackReference<mirror::ArtMethod>* sp) {
+static TwoWordReturn artInvokeCommon(uint32_t method_idx,
+ mirror::Object* this_object,
+ Thread* self,
+ StackReference<mirror::ArtMethod>* sp) {
ScopedQuickEntrypointChecks sqec(self);
DCHECK_EQ(sp->AsMirrorPtr(), Runtime::Current()->GetCalleeSaveMethod(Runtime::kRefsAndArgs));
+ mirror::ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp);
mirror::ArtMethod* method = FindMethodFast(method_idx, this_object, caller_method, access_check,
type);
if (UNLIKELY(method == nullptr)) {
@@ -1994,7 +1997,6 @@
template SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) \
TwoWordReturn artInvokeCommon<type, access_check>(uint32_t method_idx, \
mirror::Object* this_object, \
- mirror::ArtMethod* caller_method, \
Thread* self, \
StackReference<mirror::ArtMethod>* sp) \
@@ -2012,58 +2014,58 @@
// See comments in runtime_support_asm.S
extern "C" TwoWordReturn artInvokeInterfaceTrampolineWithAccessCheck(
- uint32_t method_idx, mirror::Object* this_object,
- mirror::ArtMethod* caller_method, Thread* self,
+ uint32_t method_idx,
+ mirror::Object* this_object,
+ Thread* self,
StackReference<mirror::ArtMethod>* sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- return artInvokeCommon<kInterface, true>(method_idx, this_object,
- caller_method, self, sp);
+ return artInvokeCommon<kInterface, true>(method_idx, this_object, self, sp);
}
extern "C" TwoWordReturn artInvokeDirectTrampolineWithAccessCheck(
- uint32_t method_idx, mirror::Object* this_object,
- mirror::ArtMethod* caller_method, Thread* self,
+ uint32_t method_idx,
+ mirror::Object* this_object,
+ Thread* self,
StackReference<mirror::ArtMethod>* sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- return artInvokeCommon<kDirect, true>(method_idx, this_object, caller_method,
- self, sp);
+ return artInvokeCommon<kDirect, true>(method_idx, this_object, self, sp);
}
extern "C" TwoWordReturn artInvokeStaticTrampolineWithAccessCheck(
- uint32_t method_idx, mirror::Object* this_object,
- mirror::ArtMethod* caller_method, Thread* self,
+ uint32_t method_idx,
+ mirror::Object* this_object,
+ Thread* self,
StackReference<mirror::ArtMethod>* sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- return artInvokeCommon<kStatic, true>(method_idx, this_object, caller_method,
- self, sp);
+ return artInvokeCommon<kStatic, true>(method_idx, this_object, self, sp);
}
extern "C" TwoWordReturn artInvokeSuperTrampolineWithAccessCheck(
- uint32_t method_idx, mirror::Object* this_object,
- mirror::ArtMethod* caller_method, Thread* self,
+ uint32_t method_idx,
+ mirror::Object* this_object,
+ Thread* self,
StackReference<mirror::ArtMethod>* sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- return artInvokeCommon<kSuper, true>(method_idx, this_object, caller_method,
- self, sp);
+ return artInvokeCommon<kSuper, true>(method_idx, this_object, self, sp);
}
extern "C" TwoWordReturn artInvokeVirtualTrampolineWithAccessCheck(
- uint32_t method_idx, mirror::Object* this_object,
- mirror::ArtMethod* caller_method, Thread* self,
+ uint32_t method_idx,
+ mirror::Object* this_object,
+ Thread* self,
StackReference<mirror::ArtMethod>* sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- return artInvokeCommon<kVirtual, true>(method_idx, this_object, caller_method,
- self, sp);
+ return artInvokeCommon<kVirtual, true>(method_idx, this_object, self, sp);
}
// Determine target of interface dispatch. This object is known non-null.
extern "C" TwoWordReturn artInvokeInterfaceTrampoline(mirror::ArtMethod* interface_method,
mirror::Object* this_object,
- mirror::ArtMethod* caller_method,
Thread* self,
StackReference<mirror::ArtMethod>* sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
ScopedQuickEntrypointChecks sqec(self);
+ mirror::ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp);
mirror::ArtMethod* method;
if (LIKELY(interface_method->GetDexMethodIndex() != DexFile::kDexNoIndex)) {
method = this_object->GetClass()->FindVirtualMethodForInterface(interface_method);
@@ -2075,12 +2077,7 @@
} else {
DCHECK(interface_method == Runtime::Current()->GetResolutionMethod());
- // Find the caller PC.
- constexpr size_t pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, Runtime::kRefsAndArgs);
- uintptr_t caller_pc = *reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) + pc_offset);
-
- // Map the caller PC to a dex PC.
- uint32_t dex_pc = caller_method->ToDexPc(caller_pc);
+ uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
const DexFile::CodeItem* code = caller_method->GetCodeItem();
CHECK_LT(dex_pc, code->insns_size_in_code_units_);
const Instruction* instr = Instruction::At(&code->insns_[dex_pc]);
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index d0bfe6e..094d8b7 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -417,7 +417,7 @@
<< secondary_dex_location
<< ". Expected: " << expected_secondary_checksum
<< ", Actual: " << actual_secondary_checksum;
- return false;
+ return true;
}
} else {
// If we can't get the checksum for the secondary location, we assume
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 865fcb0..d8e3797 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -67,10 +67,23 @@
<< "Expected stripped dex file to be at: " << GetStrippedDexSrc1();
ASSERT_FALSE(DexFile::GetChecksum(GetStrippedDexSrc1().c_str(), &checksum, &error_msg))
<< "Expected stripped dex file to be stripped: " << GetStrippedDexSrc1();
- ASSERT_TRUE(OS::FileExists(GetMultiDexSrc1().c_str()))
- << "Expected multidex file to be at: " << GetMultiDexSrc1();
ASSERT_TRUE(OS::FileExists(GetDexSrc2().c_str()))
<< "Expected dex file to be at: " << GetDexSrc2();
+
+ // GetMultiDexSrc2 should have the same primary dex checksum as
+ // GetMultiDexSrc1, but a different secondary dex checksum.
+ std::vector<std::unique_ptr<const DexFile>> multi1;
+ ASSERT_TRUE(DexFile::Open(GetMultiDexSrc1().c_str(),
+ GetMultiDexSrc1().c_str(), &error_msg, &multi1)) << error_msg;
+ ASSERT_GT(multi1.size(), 1u);
+
+ std::vector<std::unique_ptr<const DexFile>> multi2;
+ ASSERT_TRUE(DexFile::Open(GetMultiDexSrc2().c_str(),
+ GetMultiDexSrc2().c_str(), &error_msg, &multi2)) << error_msg;
+ ASSERT_GT(multi2.size(), 1u);
+
+ ASSERT_EQ(multi1[0]->GetLocationChecksum(), multi2[0]->GetLocationChecksum());
+ ASSERT_NE(multi1[1]->GetLocationChecksum(), multi2[1]->GetLocationChecksum());
}
virtual void SetUpRuntimeOptions(RuntimeOptions* options) {
@@ -149,6 +162,12 @@
return GetTestDexFileName("MultiDex");
}
+ // Returns the path to a multidex file equivalent to GetMultiDexSrc2, but
+ // with the contents of the secondary dex file changed.
+ std::string GetMultiDexSrc2() {
+ return GetTestDexFileName("MultiDexModifiedSecondary");
+ }
+
std::string GetDexSrc2() {
return GetTestDexFileName("Nested");
}
@@ -344,6 +363,23 @@
EXPECT_EQ(2u, dex_files.size());
}
+// Case: We have a MultiDEX file where the secondary dex file is out of date.
+// Expect: The status is kDex2OatNeeded.
+TEST_F(OatFileAssistantTest, MultiDexSecondaryOutOfDate) {
+ std::string dex_location = GetScratchDir() + "/MultiDexSecondaryOutOfDate.jar";
+
+ // Compile code for GetMultiDexSrc1.
+ Copy(GetMultiDexSrc1(), dex_location);
+ GenerateOatForTest(dex_location.c_str());
+
+ // Now overwrite the dex file with GetMultiDexSrc2 so the secondary checksum
+ // is out of date.
+ Copy(GetMultiDexSrc2(), dex_location);
+
+ OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true);
+ EXPECT_EQ(OatFileAssistant::kDex2OatNeeded, oat_file_assistant.GetDexOptNeeded());
+}
+
// Case: We have a MultiDEX file and up-to-date OAT file for it with relative
// encoded dex locations.
// Expect: The oat file status is kNoDexOptNeeded.
@@ -1001,9 +1037,6 @@
// TODO: More Tests:
// * Test class linker falls back to unquickened dex for DexNoOat
// * Test class linker falls back to unquickened dex for MultiDexNoOat
-// * Test multidex files:
-// - Multidex with only classes2.dex out of date should have status
-// kOutOfDate
// * Test using secondary isa
// * Test with profiling info?
// * Test for status of oat while oat is being generated (how?)
diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc
new file mode 100644
index 0000000..b2d7e55
--- /dev/null
+++ b/test/137-cfi/cfi.cc
@@ -0,0 +1,207 @@
+/*
+ * 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 __linux__
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#endif
+
+#include "jni.h"
+
+#include <backtrace/Backtrace.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "utils.h"
+
+namespace art {
+
+// For testing debuggerd. We do not have expected-death tests, so can't test this by default.
+// Code for this is copied from SignalTest.
+static constexpr bool kCauseSegfault = false;
+char* go_away_compiler_cfi = nullptr;
+
+static void CauseSegfault() {
+#if defined(__arm__) || defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+ // On supported architectures we cause a real SEGV.
+ *go_away_compiler_cfi = 'a';
+#else
+ // On other architectures we simulate SEGV.
+ kill(getpid(), SIGSEGV);
+#endif
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_sleep(JNIEnv*, jobject, jint, jboolean, jdouble) {
+ // Keep pausing.
+ for (;;) {
+ pause();
+ }
+}
+
+// Helper to look for a sequence in the stack trace.
+#if __linux__
+static bool CheckStack(Backtrace* bt, const std::vector<std::string>& seq) {
+ size_t cur_search_index = 0; // The currently active index in seq.
+ CHECK_GT(seq.size(), 0U);
+
+ for (Backtrace::const_iterator it = bt->begin(); it != bt->end(); ++it) {
+ if (BacktraceMap::IsValid(it->map)) {
+ LOG(INFO) << "Got " << it->func_name << ", looking for " << seq[cur_search_index];
+ if (it->func_name == seq[cur_search_index]) {
+ cur_search_index++;
+ if (cur_search_index == seq.size()) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+#endif
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindInProcess(JNIEnv*, jobject, jint, jboolean) {
+#if __linux__
+ // TODO: What to do on Valgrind?
+
+ std::unique_ptr<Backtrace> bt(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, GetTid()));
+ if (!bt->Unwind(0, nullptr)) {
+ return JNI_FALSE;
+ } else if (bt->NumFrames() == 0) {
+ return JNI_FALSE;
+ }
+
+ // We cannot really parse an exact stack, as the optimizing compiler may inline some functions.
+ // This is also risky, as deduping might play a trick on us, so the test needs to make sure that
+ // only unique functions are being expected.
+ std::vector<std::string> seq = {
+ "Java_Main_unwindInProcess", // This function.
+ "boolean Main.unwindInProcess(int, boolean)", // The corresponding Java native method frame.
+ "void Main.main(java.lang.String[])" // The Java entry method.
+ };
+
+ bool result = CheckStack(bt.get(), seq);
+ if (!kCauseSegfault) {
+ return result ? JNI_TRUE : JNI_FALSE;
+ } else {
+ LOG(INFO) << "Result of check-stack: " << result;
+ }
+#endif
+
+ if (kCauseSegfault) {
+ CauseSegfault();
+ }
+
+ return JNI_FALSE;
+}
+
+#if __linux__
+static constexpr int kSleepTimeMicroseconds = 50000; // 0.05 seconds
+static constexpr int kMaxTotalSleepTimeMicroseconds = 1000000; // 1 second
+
+// Wait for a sigstop. This code is copied from libbacktrace.
+int wait_for_sigstop(pid_t tid, int* total_sleep_time_usec, bool* detach_failed ATTRIBUTE_UNUSED) {
+ for (;;) {
+ int status;
+ pid_t n = TEMP_FAILURE_RETRY(waitpid(tid, &status, __WALL | WNOHANG));
+ if (n == -1) {
+ PLOG(WARNING) << "waitpid failed: tid " << tid;
+ break;
+ } else if (n == tid) {
+ if (WIFSTOPPED(status)) {
+ return WSTOPSIG(status);
+ } else {
+ PLOG(ERROR) << "unexpected waitpid response: n=" << n << ", status=" << std::hex << status;
+ break;
+ }
+ }
+
+ if (*total_sleep_time_usec > kMaxTotalSleepTimeMicroseconds) {
+ PLOG(WARNING) << "timed out waiting for stop signal: tid=" << tid;
+ break;
+ }
+
+ usleep(kSleepTimeMicroseconds);
+ *total_sleep_time_usec += kSleepTimeMicroseconds;
+ }
+
+ return -1;
+}
+#endif
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess(JNIEnv*, jobject, jint pid_int) {
+#if __linux__
+ // TODO: What to do on Valgrind?
+ pid_t pid = static_cast<pid_t>(pid_int);
+
+ // OK, this is painful. debuggerd uses ptrace to unwind other processes.
+
+ if (ptrace(PTRACE_ATTACH, pid, 0, 0)) {
+ // Were not able to attach, bad.
+ PLOG(ERROR) << "Failed to attach.";
+ kill(pid, SIGCONT);
+ return JNI_FALSE;
+ }
+
+ kill(pid, SIGSTOP);
+
+ bool detach_failed = false;
+ int total_sleep_time_usec = 0;
+ int signal = wait_for_sigstop(pid, &total_sleep_time_usec, &detach_failed);
+ if (signal == -1) {
+ LOG(WARNING) << "wait_for_sigstop failed.";
+ }
+
+ std::unique_ptr<Backtrace> bt(Backtrace::Create(pid, BACKTRACE_CURRENT_THREAD));
+ bool result = true;
+ if (!bt->Unwind(0, nullptr)) {
+ result = false;
+ } else if (bt->NumFrames() == 0) {
+ result = false;
+ }
+
+ if (result) {
+ // See comment in unwindInProcess for non-exact stack matching.
+ std::vector<std::string> seq = {
+ // "Java_Main_sleep", // The sleep function being executed in the
+ // other runtime.
+ // Note: For some reason, the name isn't
+ // resolved, so don't look for it right now.
+ "boolean Main.sleep(int, boolean, double)", // The corresponding Java native method frame.
+ "void Main.main(java.lang.String[])" // The Java entry method.
+ };
+
+ result = CheckStack(bt.get(), seq);
+ }
+
+ if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) {
+ PLOG(ERROR) << "Detach failed";
+ }
+
+ // Continue the process so we can kill it on the Java side.
+ kill(pid, SIGCONT);
+
+ return result ? JNI_TRUE : JNI_FALSE;
+#else
+ return JNI_FALSE;
+#endif
+}
+
+} // namespace art
diff --git a/test/137-cfi/expected.txt b/test/137-cfi/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/137-cfi/expected.txt
diff --git a/test/137-cfi/info.txt b/test/137-cfi/info.txt
new file mode 100644
index 0000000..7d59605
--- /dev/null
+++ b/test/137-cfi/info.txt
@@ -0,0 +1 @@
+Test that unwinding with CFI info works.
diff --git a/test/137-cfi/src/Main.java b/test/137-cfi/src/Main.java
new file mode 100644
index 0000000..e184e66
--- /dev/null
+++ b/test/137-cfi/src/Main.java
@@ -0,0 +1,155 @@
+/*
+ * 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.BufferedReader;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class Main {
+ // Whether to test local unwinding. Libunwind uses linker info to find executables. As we do
+ // not dlopen at the moment, this doesn't work, so keep it off for now.
+ public final static boolean TEST_LOCAL_UNWINDING = false;
+
+ // Unwinding another process, modelling debuggerd. This doesn't use the linker, so should work
+ // no matter whether we're using dlopen or not.
+ public final static boolean TEST_REMOTE_UNWINDING = true;
+
+ private boolean secondary;
+
+ public Main(boolean secondary) {
+ this.secondary = secondary;
+ }
+
+ public static void main(String[] args) throws Exception {
+ boolean secondary = false;
+ if (args.length > 0 && args[args.length - 1].equals("--secondary")) {
+ secondary = true;
+ }
+ new Main(secondary).run();
+ }
+
+ static {
+ System.loadLibrary("arttest");
+ }
+
+ private void run() {
+ if (secondary) {
+ if (!TEST_REMOTE_UNWINDING) {
+ throw new RuntimeException("Should not be running secondary!");
+ }
+ runSecondary();
+ } else {
+ runPrimary();
+ }
+ }
+
+ private void runSecondary() {
+ foo(true);
+ throw new RuntimeException("Didn't expect to get back...");
+ }
+
+ private void runPrimary() {
+ // First do the in-process unwinding.
+ if (TEST_LOCAL_UNWINDING && !foo(false)) {
+ System.out.println("Unwinding self failed.");
+ }
+
+ if (!TEST_REMOTE_UNWINDING) {
+ // Skip the remote step.
+ return;
+ }
+
+ // Fork the secondary.
+ String[] cmdline = getCmdLine();
+ String[] secCmdLine = new String[cmdline.length + 1];
+ System.arraycopy(cmdline, 0, secCmdLine, 0, cmdline.length);
+ secCmdLine[secCmdLine.length - 1] = "--secondary";
+ Process p = exec(secCmdLine);
+
+ try {
+ int pid = getPid(p);
+ if (pid <= 0) {
+ throw new RuntimeException("Couldn't parse process");
+ }
+
+ // Wait a bit, so the forked process has time to run until its sleep phase.
+ try {
+ Thread.sleep(5000);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ if (!unwindOtherProcess(pid)) {
+ System.out.println("Unwinding other process failed.");
+ }
+ } finally {
+ // Kill the forked process.
+ p.destroy();
+ }
+ }
+
+ private static Process exec(String[] args) {
+ try {
+ return Runtime.getRuntime().exec(args);
+ } catch (Exception exc) {
+ throw new RuntimeException(exc);
+ }
+ }
+
+ private static int getPid(Process p) {
+ // Could do reflection for the private pid field, but String parsing is easier.
+ String s = p.toString();
+ if (s.startsWith("Process[pid=")) {
+ return Integer.parseInt(s.substring("Process[pid=".length(), s.length() - 1));
+ } else {
+ return -1;
+ }
+ }
+
+ // Read /proc/self/cmdline to find the invocation command line (so we can fork another runtime).
+ private static String[] getCmdLine() {
+ try {
+ BufferedReader in = new BufferedReader(new FileReader("/proc/self/cmdline"));
+ String s = in.readLine();
+ in.close();
+ return s.split("\0");
+ } catch (Exception exc) {
+ throw new RuntimeException(exc);
+ }
+ }
+
+ public boolean foo(boolean b) {
+ return bar(b);
+ }
+
+ public boolean bar(boolean b) {
+ if (b) {
+ return sleep(2, b, 1.0);
+ } else {
+ return unwindInProcess(1, b);
+ }
+ }
+
+ // Native functions. Note: to avoid deduping, they must all have different signatures.
+
+ public native boolean sleep(int i, boolean b, double dummy);
+
+ public native boolean unwindInProcess(int i, boolean b);
+ public native boolean unwindOtherProcess(int pid);
+}
diff --git a/test/476-checker-ctor-memory-barrier/src/Main.java b/test/476-checker-ctor-memory-barrier/src/Main.java
index 9badc0f..75cb1d7 100644
--- a/test/476-checker-ctor-memory-barrier/src/Main.java
+++ b/test/476-checker-ctor-memory-barrier/src/Main.java
@@ -157,7 +157,7 @@
// CHECK-START: void Main.inlineNew() register (after)
// CHECK: MemoryBarrier kind:StoreStore
// CHECK-NOT: {{.*}}
- // CHECK: Return
+ // CHECK: ReturnVoid
// CHECK-START: void Main.inlineNew() register (after)
// CHECK-NOT: InvokeStaticOrDirect
@@ -168,7 +168,7 @@
// CHECK-START: void Main.inlineNew1() register (after)
// CHECK: MemoryBarrier kind:StoreStore
// CHECK-NOT: {{.*}}
- // CHECK: Return
+ // CHECK: ReturnVoid
// CHECK-START: void Main.inlineNew1() register (after)
// CHECK-NOT: InvokeStaticOrDirect
@@ -179,7 +179,7 @@
// CHECK-START: void Main.inlineNew2() register (after)
// CHECK: MemoryBarrier kind:StoreStore
// CHECK-NOT: {{.*}}
- // CHECK: Return
+ // CHECK: ReturnVoid
// CHECK-START: void Main.inlineNew2() register (after)
// CHECK-NOT: InvokeStaticOrDirect
@@ -191,7 +191,7 @@
// CHECK: MemoryBarrier kind:StoreStore
// CHECK: MemoryBarrier kind:StoreStore
// CHECK-NOT: {{.*}}
- // CHECK: Return
+ // CHECK: ReturnVoid
// CHECK-START: void Main.inlineNew3() register (after)
// CHECK-NOT: InvokeStaticOrDirect
diff --git a/test/478-checker-clinit-check-pruning/src/Main.java b/test/478-checker-clinit-check-pruning/src/Main.java
index 61199a7..e8739b8 100644
--- a/test/478-checker-clinit-check-pruning/src/Main.java
+++ b/test/478-checker-clinit-check-pruning/src/Main.java
@@ -24,12 +24,12 @@
*/
// CHECK-START: void Main.invokeStaticInlined() builder (after)
- // CHECK-DAG: <<LoadClass:l\d+>> LoadClass
+ // CHECK-DAG: <<LoadClass:l\d+>> LoadClass gen_clinit_check:false
// CHECK-DAG: <<ClinitCheck:l\d+>> ClinitCheck [<<LoadClass>>]
// CHECK-DAG: InvokeStaticOrDirect [<<ClinitCheck>>]
// CHECK-START: void Main.invokeStaticInlined() inliner (after)
- // CHECK-DAG: <<LoadClass:l\d+>> LoadClass
+ // CHECK-DAG: <<LoadClass:l\d+>> LoadClass gen_clinit_check:false
// CHECK-DAG: <<ClinitCheck:l\d+>> ClinitCheck [<<LoadClass>>]
// CHECK-START: void Main.invokeStaticInlined() inliner (after)
@@ -42,7 +42,7 @@
// CFG as it is before the next pass (liveness analysis) instead.
// CHECK-START: void Main.invokeStaticInlined() liveness (before)
- // CHECK-DAG: LoadClass
+ // CHECK-DAG: LoadClass gen_clinit_check:true
// CHECK-START: void Main.invokeStaticInlined() liveness (before)
// CHECK-NOT: ClinitCheck
@@ -67,12 +67,12 @@
*/
// CHECK-START: void Main.invokeStaticNotInlined() builder (after)
- // CHECK-DAG: <<LoadClass:l\d+>> LoadClass
+ // CHECK-DAG: <<LoadClass:l\d+>> LoadClass gen_clinit_check:false
// CHECK-DAG: <<ClinitCheck:l\d+>> ClinitCheck [<<LoadClass>>]
// CHECK-DAG: InvokeStaticOrDirect [<<ClinitCheck>>]
// CHECK-START: void Main.invokeStaticNotInlined() inliner (after)
- // CHECK-DAG: <<LoadClass:l\d+>> LoadClass
+ // CHECK-DAG: <<LoadClass:l\d+>> LoadClass gen_clinit_check:false
// CHECK-DAG: <<ClinitCheck:l\d+>> ClinitCheck [<<LoadClass>>]
// CHECK-DAG: InvokeStaticOrDirect [<<ClinitCheck>>]
@@ -260,6 +260,44 @@
}
}
+
+ /*
+ * Verify that if we have a static call immediately after the load class
+ * we don't do generate a clinit check.
+ */
+
+ // CHECK-START: void Main.noClinitBecauseOfInvokeStatic() liveness (before)
+ // CHECK-DAG: <<IntConstant:i\d+>> IntConstant 0
+ // CHECK-DAG: <<LoadClass:l\d+>> LoadClass gen_clinit_check:false
+ // CHECK-DAG: InvokeStaticOrDirect
+ // CHECK-DAG: StaticFieldSet [<<LoadClass>>,<<IntConstant>>]
+
+ // CHECK-START: void Main.noClinitBecauseOfInvokeStatic() liveness (before)
+ // CHECK-NOT: ClinitCheck
+
+ static void noClinitBecauseOfInvokeStatic() {
+ ClassWithClinit2.staticMethod();
+ ClassWithClinit2.doThrow = false;
+ }
+
+ /*
+ * Verify that if the static call is after a field access, the load class
+ * will generate a clinit check.
+ */
+
+ // CHECK-START: void Main.clinitBecauseOfFieldAccess() liveness (before)
+ // CHECK-DAG: <<IntConstant:i\d+>> IntConstant 0
+ // CHECK-DAG: <<LoadClass:l\d+>> LoadClass gen_clinit_check:true
+ // CHECK-DAG: StaticFieldSet [<<LoadClass>>,<<IntConstant>>]
+ // CHECK-DAG: InvokeStaticOrDirect
+
+ // CHECK-START: void Main.clinitBecauseOfFieldAccess() liveness (before)
+ // CHECK-NOT: ClinitCheck
+ static void clinitBecauseOfFieldAccess() {
+ ClassWithClinit2.doThrow = false;
+ ClassWithClinit2.staticMethod();
+ }
+
// TODO: Add a test for the case of a static method whose declaring
// class type index is not available (i.e. when `storage_index`
// equals `DexFile::kDexNoIndex` in
diff --git a/test/486-checker-must-do-null-check/expected.txt b/test/486-checker-must-do-null-check/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/486-checker-must-do-null-check/expected.txt
diff --git a/test/486-checker-must-do-null-check/info.txt b/test/486-checker-must-do-null-check/info.txt
new file mode 100644
index 0000000..494ff1c
--- /dev/null
+++ b/test/486-checker-must-do-null-check/info.txt
@@ -0,0 +1 @@
+Verifies MustDoNullCheck() on InstanceOf and CheckCast
diff --git a/test/486-checker-must-do-null-check/src/Main.java b/test/486-checker-must-do-null-check/src/Main.java
new file mode 100644
index 0000000..f285566
--- /dev/null
+++ b/test/486-checker-must-do-null-check/src/Main.java
@@ -0,0 +1,53 @@
+/*
+ * 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 Main {
+ // CHECK-START: void Main.InstanceOfPreChecked(java.lang.Object) instruction_simplifier (after)
+ // CHECK: InstanceOf must_do_null_check:false
+ public void InstanceOfPreChecked(Object o) throws Exception {
+ o.toString();
+ if (o instanceof Main) {
+ throw new Exception();
+ }
+ }
+
+ // CHECK-START: void Main.InstanceOf(java.lang.Object) instruction_simplifier (after)
+ // CHECK: InstanceOf must_do_null_check:true
+ public void InstanceOf(Object o) throws Exception {
+ if (o instanceof Main) {
+ throw new Exception();
+ }
+ }
+
+ // CHECK-START: void Main.CheckCastPreChecked(java.lang.Object) instruction_simplifier (after)
+ // CHECK: CheckCast must_do_null_check:false
+ public void CheckCastPreChecked(Object o) {
+ o.toString();
+ ((Main)o).Bar();
+ }
+
+ // CHECK-START: void Main.CheckCast(java.lang.Object) instruction_simplifier (after)
+ // CHECK: CheckCast must_do_null_check:true
+ public void CheckCast(Object o) {
+ ((Main)o).Bar();
+ }
+
+ void Bar() {throw new RuntimeException();}
+
+ public static void main(String[] sa) {
+ Main t = new Main();
+ }
+}
diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk
index 5e768ee..6abcadf 100644
--- a/test/Android.libarttest.mk
+++ b/test/Android.libarttest.mk
@@ -28,6 +28,7 @@
116-nodex2oat/nodex2oat.cc \
117-nopatchoat/nopatchoat.cc \
118-noimage-dex2oat/noimage-dex2oat.cc \
+ 137-cfi/cfi.cc \
454-get-vreg/get_vreg_jni.cc \
455-set-vreg/set_vreg_jni.cc \
457-regs/regs_jni.cc \
@@ -56,7 +57,7 @@
LOCAL_MODULE_TAGS := tests
endif
LOCAL_SRC_FILES := $(LIBARTTEST_COMMON_SRC_FILES)
- LOCAL_SHARED_LIBRARIES += libartd
+ LOCAL_SHARED_LIBRARIES += libartd libbacktrace
LOCAL_C_INCLUDES += $(ART_C_INCLUDES) art/runtime
LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk
LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.libarttest.mk
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 07e7620..986276d 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -323,6 +323,7 @@
118-noimage-dex2oat \
119-noimage-patchoat \
131-structural-change \
+ 137-cfi \
454-get-vreg \
455-set-vreg \
457-regs \
@@ -438,6 +439,11 @@
TEST_ART_BROKEN_READ_BARRIER_RUN_TESTS :=
+# Test 137-cfi works in 32-bit only until we enable 64-bit ELF files.
+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),137-cfi,64)
+
# Clear variables ahead of appending to them when defining tests.
$(foreach target, $(TARGET_TYPES), $(eval ART_RUN_TEST_$(call name-to-var,$(target))_RULES :=))
$(foreach target, $(TARGET_TYPES), \
diff --git a/test/MultiDexModifiedSecondary/Main.java b/test/MultiDexModifiedSecondary/Main.java
new file mode 100644
index 0000000..659dba9
--- /dev/null
+++ b/test/MultiDexModifiedSecondary/Main.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+class Main {
+ public static void main(String args[]) {
+ Second second = new Second();
+ System.out.println(second.getSecond());
+ }
+}
diff --git a/test/MultiDexModifiedSecondary/README.txt b/test/MultiDexModifiedSecondary/README.txt
new file mode 100644
index 0000000..4cf3a56
--- /dev/null
+++ b/test/MultiDexModifiedSecondary/README.txt
@@ -0,0 +1,4 @@
+MultiDexModifiedSecondary is designed to result in a multidex file that has
+the same classes.dex file as MultiDex, but a different classes2.dex.
+
+This is used in the OatFileAssistantTest.MultiDexSecondaryOutOfDate gtest.
diff --git a/test/MultiDexModifiedSecondary/Second.java b/test/MultiDexModifiedSecondary/Second.java
new file mode 100644
index 0000000..3555a7f
--- /dev/null
+++ b/test/MultiDexModifiedSecondary/Second.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+class Second {
+ public String getThird() {
+ return "I Third That.";
+ }
+
+ public String getSecond() {
+ return "I Second That.";
+ }
+}
diff --git a/test/MultiDexModifiedSecondary/main.jpp b/test/MultiDexModifiedSecondary/main.jpp
new file mode 100644
index 0000000..a5d7a6c
--- /dev/null
+++ b/test/MultiDexModifiedSecondary/main.jpp
@@ -0,0 +1,3 @@
+main:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Main
diff --git a/test/MultiDexModifiedSecondary/main.list b/test/MultiDexModifiedSecondary/main.list
new file mode 100644
index 0000000..44ba78e
--- /dev/null
+++ b/test/MultiDexModifiedSecondary/main.list
@@ -0,0 +1 @@
+Main.class
diff --git a/tools/checker/common/immutables.py b/tools/checker/common/immutables.py
new file mode 100644
index 0000000..e016867
--- /dev/null
+++ b/tools/checker/common/immutables.py
@@ -0,0 +1,25 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class ImmutableDict(dict):
+ def __setitem__(self, key, value):
+ raise RuntimeError("Cannot modify ImmutableDict")
+
+ def __delitem__(self, key):
+ raise RuntimeError("Cannot modify ImmutableDict")
+
+ def copyWith(self, key, value):
+ newDict = ImmutableDict(self)
+ dict.__setitem__(newDict, key, value)
+ return newDict
diff --git a/tools/checker/match/file.py b/tools/checker/match/file.py
index 2ed4aa7..116fe9a 100644
--- a/tools/checker/match/file.py
+++ b/tools/checker/match/file.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from common.immutables import ImmutableDict
from common.logger import Logger
from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
from file_format.checker.struct import CheckerFile, TestCase, TestAssertion
@@ -112,7 +113,7 @@
responsible for running the checks in the right order and scope, and
for propagating the variable state between the check lines.
"""
- varState = {}
+ varState = ImmutableDict()
checkLines = checkGroup.assertions
outputLines = outputGroup.body
startLineNo = outputGroup.startLineNo
diff --git a/tools/checker/match/line.py b/tools/checker/match/line.py
index f0253c3..711d814 100644
--- a/tools/checker/match/line.py
+++ b/tools/checker/match/line.py
@@ -13,77 +13,84 @@
# limitations under the License.
from common.logger import Logger
-from file_format.checker.struct import TestAssertion, RegexExpression
+from file_format.checker.struct import RegexExpression
import re
-def __isMatchAtStart(match):
- """ Tests if the given Match occurred at the beginning of the line. """
- return (match is not None) and (match.start() == 0)
+def headAndTail(list):
+ return list[0], list[1:]
-def __generatePattern(checkLine, linePart, varState):
- """ Returns the regex pattern to be matched in the output line. Variable
- references are substituted with their current values provided in the
- 'varState' argument.
+def splitAtSeparators(expressions):
+ """ Splits a list of RegexExpressions at separators. """
+ splitExpressions = []
+ wordStart = 0
+ for index, expression in enumerate(expressions):
+ if expression.variant == RegexExpression.Variant.Separator:
+ splitExpressions.append(expressions[wordStart:index])
+ wordStart = index + 1
+ splitExpressions.append(expressions[wordStart:])
+ return splitExpressions
- An exception is raised if a referenced variable is undefined.
+def matchWords(checkerWord, stringWord, variables, pos):
+ """ Attempts to match a list of RegexExpressions against a string.
+ Returns updated variable dictionary if successful and None otherwise.
"""
- if linePart.variant == RegexExpression.Variant.VarRef:
- try:
- return re.escape(varState[linePart.name])
- except KeyError:
- Logger.testFailed("Use of undefined variable \"" + linePart.name + "\"",
- checkLine.fileName, checkLine.lineNo)
- else:
- return linePart.pattern
-
-def __isSeparated(outputLine, matchStart):
- return (matchStart == 0) or (outputLine[matchStart - 1:matchStart].isspace())
-
-def MatchLines(checkLine, outputLine, initialVarState):
- """ Attempts to match the check line against a line from the output file with
- the given initial variable values. It returns the new variable state if
- successful and None otherwise.
- """
- # Do the full matching on a shadow copy of the variable state. If the
- # matching fails half-way, we will not need to revert the state.
- varState = dict(initialVarState)
-
- matchStart = 0
- isAfterSeparator = True
-
- # Now try to parse all of the parts of the check line in the right order.
- # Variable values are updated on-the-fly, meaning that a variable can
- # be referenced immediately after its definition.
- for part in checkLine.expressions:
- if part.variant == RegexExpression.Variant.Separator:
- isAfterSeparator = True
- continue
-
- # Find the earliest match for this line part.
- pattern = __generatePattern(checkLine, part, varState)
- while True:
- match = re.search(pattern, outputLine[matchStart:])
- if (match is None) or (not isAfterSeparator and not __isMatchAtStart(match)):
- return None
- matchEnd = matchStart + match.end()
- matchStart += match.start()
-
- # Check if this is a valid match if we expect a whitespace separator
- # before the matched text. Otherwise loop and look for another match.
- if not isAfterSeparator or __isSeparated(outputLine, matchStart):
- break
+ for expression in checkerWord:
+ # If `expression` is a variable reference, replace it with the value.
+ if expression.variant == RegexExpression.Variant.VarRef:
+ if expression.name in variables:
+ pattern = re.escape(variables[expression.name])
else:
- matchStart += 1
+ Logger.testFailed("Multiple definitions of variable \"{}\"".format(expression.name),
+ pos.fileName, pos.lineNo)
+ else:
+ pattern = expression.pattern
- if part.variant == RegexExpression.Variant.VarDef:
- if part.name in varState:
- Logger.testFailed("Multiple definitions of variable \"" + part.name + "\"",
- checkLine.fileName, checkLine.lineNo)
- varState[part.name] = outputLine[matchStart:matchEnd]
+ # Match the expression's regex pattern against the remainder of the word.
+ # Note: re.match will succeed only if matched from the beginning.
+ match = re.match(pattern, stringWord)
+ if not match:
+ return None
- matchStart = matchEnd
- isAfterSeparator = False
+ # If `expression` was a variable definition, set the variable's value.
+ if expression.variant == RegexExpression.Variant.VarDef:
+ if expression.name not in variables:
+ variables = variables.copyWith(expression.name, stringWord[:match.end()])
+ else:
+ Logger.testFailed("Multiple definitions of variable \"{}\"".format(expression.name),
+ pos.fileName, pos.lineNo)
- # All parts were successfully matched. Return the new variable state.
- return varState
+ # Move cursor by deleting the matched characters.
+ stringWord = stringWord[match.end():]
+
+ # Make sure the entire word matched, i.e. `stringWord` is empty.
+ if stringWord:
+ return None
+
+ return variables
+
+def MatchLines(checkerLine, stringLine, variables):
+ """ Attempts to match a CHECK line against a string. Returns variable state
+ after the match if successful and None otherwise.
+ """
+ checkerWords = splitAtSeparators(checkerLine.expressions)
+ stringWords = stringLine.split()
+
+ while checkerWords:
+ # Get the next run of RegexExpressions which must match one string word.
+ checkerWord, checkerWords = headAndTail(checkerWords)
+
+ # Keep reading words until a match is found.
+ wordMatched = False
+ while stringWords:
+ stringWord, stringWords = headAndTail(stringWords)
+ newVariables = matchWords(checkerWord, stringWord, variables, checkerLine)
+ if newVariables is not None:
+ wordMatched = True
+ variables = newVariables
+ break
+ if not wordMatched:
+ return None
+
+ # All RegexExpressions matched. Return new variable state.
+ return variables
diff --git a/tools/checker/match/test.py b/tools/checker/match/test.py
index bb3b1af..97725ad 100644
--- a/tools/checker/match/test.py
+++ b/tools/checker/match/test.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from common.immutables import ImmutableDict
from common.testing import ToUnicode
from file_format.c1visualizer.parser import ParseC1visualizerStream
from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
@@ -33,7 +34,9 @@
return ParseCheckerAssertion(testCase, checkerString, TestAssertion.Variant.InOrder, 0)
def tryMatch(self, checkerString, c1String, varState={}):
- return MatchLines(self.createTestAssertion(checkerString), ToUnicode(c1String), varState)
+ return MatchLines(self.createTestAssertion(checkerString),
+ ToUnicode(c1String),
+ ImmutableDict(varState))
def matches(self, checkerString, c1String, varState={}):
return self.tryMatch(checkerString, c1String, varState) is not None
@@ -137,8 +140,8 @@
// CHECK: abc<<X>>def
""",
"""
- foo bar
- abc def
+ foo0bar
+ abc0def
"""))
self.assertTrue(self.matches(
"""
@@ -161,6 +164,12 @@
abc1235def
"""))
+ def test_WholeWordMustMatch(self):
+ self.assertTrue(self.matches( "// CHECK: b{{.}}r", "abc bar def"))
+ self.assertFalse(self.matches( "// CHECK: b{{.}}r", "abc Xbar def"))
+ self.assertFalse(self.matches( "// CHECK: b{{.}}r", "abc barX def"))
+ self.assertFalse(self.matches( "// CHECK: b{{.}}r", "abc b r def"))
+
def test_InOrderAssertions(self):
self.assertTrue(self.matches(
"""