diff options
68 files changed, 1283 insertions, 396 deletions
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc index 251dc39864..c4c2399ccd 100644 --- a/compiler/jni/jni_compiler_test.cc +++ b/compiler/jni/jni_compiler_test.cc @@ -98,8 +98,8 @@ class JniCompilerTest : public CommonCompilerTest { CompileForTest(class_loader_, direct, method_name, method_sig); // Start runtime. Thread::Current()->TransitionFromSuspendedToRunnable(); - bool started = runtime_->Start(); android::InitializeNativeLoader(); + bool started = runtime_->Start(); CHECK(started); } // JNI operations after runtime start. diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index e7fa4e472b..51fbaea519 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -50,6 +50,7 @@ #include "mirror/array-inl.h" #include "mirror/object_array-inl.h" #include "mirror/object_reference.h" +#include "mirror/string.h" #include "parallel_move_resolver.h" #include "ssa_liveness_analysis.h" #include "utils/assembler.h" @@ -139,6 +140,12 @@ size_t CodeGenerator::GetCachePointerOffset(uint32_t index) { return pointer_size * index; } +uint32_t CodeGenerator::GetArrayLengthOffset(HArrayLength* array_length) { + return array_length->IsStringLength() + ? mirror::String::CountOffset().Uint32Value() + : mirror::Array::LengthOffset().Uint32Value(); +} + bool CodeGenerator::GoesToNextBlock(HBasicBlock* current, HBasicBlock* next) const { DCHECK_EQ((*block_order_)[current_block_index_], current); return GetNextBlockToEmit() == FirstNonEmptyBlock(next); diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index d69c41055b..6e75e3bb2e 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -340,6 +340,11 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { // Pointer variant for ArtMethod and ArtField arrays. size_t GetCachePointerOffset(uint32_t index); + // Helper that returns the offset of the array's length field. + // Note: Besides the normal arrays, we also use the HArrayLength for + // accessing the String's `count` field in String intrinsics. + static uint32_t GetArrayLengthOffset(HArrayLength* array_length); + void EmitParallelMoves(Location from1, Location to1, Primitive::Type type1, diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 197e473473..e0106628c6 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -4742,7 +4742,7 @@ void LocationsBuilderARM::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorARM::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); Register obj = locations->InAt(0).AsRegister<Register>(); Register out = locations->Out().AsRegister<Register>(); __ LoadFromOffset(kLoadWord, out, obj, offset); diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 9680f2bf45..261c04f062 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -2118,9 +2118,9 @@ void LocationsBuilderARM64::VisitArrayLength(HArrayLength* instruction) { } void InstructionCodeGeneratorARM64::VisitArrayLength(HArrayLength* instruction) { + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); BlockPoolsScope block_pools(GetVIXLAssembler()); - __ Ldr(OutputRegister(instruction), - HeapOperand(InputRegisterAt(instruction, 0), mirror::Array::LengthOffset())); + __ Ldr(OutputRegister(instruction), HeapOperand(InputRegisterAt(instruction, 0), offset)); codegen_->MaybeRecordImplicitNullCheck(instruction); } diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index 12d1164d03..fb50680c91 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -1803,7 +1803,7 @@ void LocationsBuilderMIPS::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorMIPS::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); Register obj = locations->InAt(0).AsRegister<Register>(); Register out = locations->Out().AsRegister<Register>(); __ LoadFromOffset(kLoadWord, out, obj, offset); diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 56ac38ef84..e67d8d0dc5 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -1426,7 +1426,7 @@ void LocationsBuilderMIPS64::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorMIPS64::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); GpuRegister obj = locations->InAt(0).AsRegister<GpuRegister>(); GpuRegister out = locations->Out().AsRegister<GpuRegister>(); __ LoadFromOffset(kLoadWord, out, obj, offset); diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 6dc480bbee..50892a9d48 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -5480,7 +5480,7 @@ void LocationsBuilderX86::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorX86::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); Register obj = locations->InAt(0).AsRegister<Register>(); Register out = locations->Out().AsRegister<Register>(); __ movl(out, Address(obj, offset)); diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 96ec09c2a8..56c5b06945 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -4956,7 +4956,7 @@ void LocationsBuilderX86_64::VisitArrayLength(HArrayLength* instruction) { void InstructionCodeGeneratorX86_64::VisitArrayLength(HArrayLength* instruction) { LocationSummary* locations = instruction->GetLocations(); - uint32_t offset = mirror::Array::LengthOffset().Uint32Value(); + uint32_t offset = CodeGenerator::GetArrayLengthOffset(instruction); CpuRegister obj = locations->InAt(0).AsRegister<CpuRegister>(); CpuRegister out = locations->Out().AsRegister<CpuRegister>(); __ movl(out, Address(obj, offset)); diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc index 5f11024996..49cfff46d8 100644 --- a/compiler/optimizing/dead_code_elimination.cc +++ b/compiler/optimizing/dead_code_elimination.cc @@ -23,7 +23,7 @@ namespace art { static void MarkReachableBlocks(HGraph* graph, ArenaBitVector* visited) { - ArenaVector<HBasicBlock*> worklist(graph->GetArena()->Adapter()); + ArenaVector<HBasicBlock*> worklist(graph->GetArena()->Adapter(kArenaAllocDCE)); constexpr size_t kDefaultWorlistSize = 8; worklist.reserve(kDefaultWorlistSize); visited->SetBit(graph->GetEntryBlock()->GetBlockId()); diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index 9efc13f61b..568b8a8df6 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -389,6 +389,11 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { << instance_of->MustDoNullCheck() << std::noboolalpha; } + void VisitArrayLength(HArrayLength* array_length) OVERRIDE { + StartAttributeStream("is_string_length") << std::boolalpha + << array_length->IsStringLength() << std::noboolalpha; + } + void VisitArraySet(HArraySet* array_set) OVERRIDE { StartAttributeStream("value_can_be_null") << std::boolalpha << array_set->GetValueCanBeNull() << std::noboolalpha; diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc index a834216d0c..aaddc01f1f 100644 --- a/compiler/optimizing/instruction_builder.cc +++ b/compiler/optimizing/instruction_builder.cc @@ -721,6 +721,11 @@ ArtMethod* HInstructionBuilder::ResolveMethod(uint16_t method_idx, InvokeType in DCHECK(Runtime::Current()->IsAotCompiler()); return nullptr; } + if (!methods_class->IsAssignableFrom(compiling_class.Get())) { + // We cannot statically determine the target method. The runtime will throw a + // NoSuchMethodError on this one. + return nullptr; + } ArtMethod* actual_method; if (methods_class->IsInterface()) { actual_method = methods_class->FindVirtualMethodForInterfaceSuper( diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index d7b3856bf4..fd79901ffc 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -101,6 +101,7 @@ class InstructionSimplifierVisitor : public HGraphDelegateVisitor { void SimplifyCompare(HInvoke* invoke, bool is_signum, Primitive::Type type); void SimplifyIsNaN(HInvoke* invoke); void SimplifyFP2Int(HInvoke* invoke); + void SimplifyStringIsEmptyOrLength(HInvoke* invoke); void SimplifyMemBarrier(HInvoke* invoke, MemBarrierKind barrier_kind); OptimizingCompilerStats* stats_; @@ -1673,6 +1674,27 @@ void InstructionSimplifierVisitor::SimplifyFP2Int(HInvoke* invoke) { invoke->ReplaceWithExceptInReplacementAtIndex(select, 0); // false at index 0 } +void InstructionSimplifierVisitor::SimplifyStringIsEmptyOrLength(HInvoke* invoke) { + HInstruction* str = invoke->InputAt(0); + uint32_t dex_pc = invoke->GetDexPc(); + // We treat String as an array to allow DCE and BCE to seamlessly work on strings, + // so create the HArrayLength. + HArrayLength* length = new (GetGraph()->GetArena()) HArrayLength(str, dex_pc); + length->MarkAsStringLength(); + HInstruction* replacement; + if (invoke->GetIntrinsic() == Intrinsics::kStringIsEmpty) { + // For String.isEmpty(), create the `HEqual` representing the `length == 0`. + invoke->GetBlock()->InsertInstructionBefore(length, invoke); + HIntConstant* zero = GetGraph()->GetIntConstant(0); + HEqual* equal = new (GetGraph()->GetArena()) HEqual(length, zero, dex_pc); + replacement = equal; + } else { + DCHECK_EQ(invoke->GetIntrinsic(), Intrinsics::kStringLength); + replacement = length; + } + invoke->GetBlock()->ReplaceAndRemoveInstructionWith(invoke, replacement); +} + void InstructionSimplifierVisitor::SimplifyMemBarrier(HInvoke* invoke, MemBarrierKind barrier_kind) { uint32_t dex_pc = invoke->GetDexPc(); HMemoryBarrier* mem_barrier = new (GetGraph()->GetArena()) HMemoryBarrier(barrier_kind, dex_pc); @@ -1719,6 +1741,10 @@ void InstructionSimplifierVisitor::VisitInvoke(HInvoke* instruction) { case Intrinsics::kDoubleDoubleToLongBits: SimplifyFP2Int(instruction); break; + case Intrinsics::kStringIsEmpty: + case Intrinsics::kStringLength: + SimplifyStringIsEmptyOrLength(instruction); + break; case Intrinsics::kUnsafeLoadFence: SimplifyMemBarrier(instruction, MemBarrierKind::kLoadAny); break; diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc index 5d4c4e2950..418d59c6cb 100644 --- a/compiler/optimizing/intrinsics.cc +++ b/compiler/optimizing/intrinsics.cc @@ -388,10 +388,8 @@ static Intrinsics GetIntrinsic(InlineMethod method) { case kIntrinsicGetCharsNoCheck: return Intrinsics::kStringGetCharsNoCheck; case kIntrinsicIsEmptyOrLength: - // The inliner can handle these two cases - and this is the preferred approach - // since after inlining the call is no longer visible (as opposed to waiting - // until codegen to handle intrinsic). - return Intrinsics::kNone; + return ((method.d.data & kIntrinsicFlagIsEmpty) == 0) ? + Intrinsics::kStringLength : Intrinsics::kStringIsEmpty; case kIntrinsicIndexOf: return ((method.d.data & kIntrinsicFlagBase0) == 0) ? Intrinsics::kStringIndexOfAfter : Intrinsics::kStringIndexOf; diff --git a/compiler/optimizing/intrinsics.h b/compiler/optimizing/intrinsics.h index 39a1313ba0..214250f337 100644 --- a/compiler/optimizing/intrinsics.h +++ b/compiler/optimizing/intrinsics.h @@ -239,6 +239,8 @@ UNREACHABLE_INTRINSIC(Arch, IntegerCompare) \ UNREACHABLE_INTRINSIC(Arch, LongCompare) \ UNREACHABLE_INTRINSIC(Arch, IntegerSignum) \ UNREACHABLE_INTRINSIC(Arch, LongSignum) \ +UNREACHABLE_INTRINSIC(Arch, StringIsEmpty) \ +UNREACHABLE_INTRINSIC(Arch, StringLength) \ UNREACHABLE_INTRINSIC(Arch, UnsafeLoadFence) \ UNREACHABLE_INTRINSIC(Arch, UnsafeStoreFence) \ UNREACHABLE_INTRINSIC(Arch, UnsafeFullFence) diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc index 146fea1fe0..4e3ace498d 100644 --- a/compiler/optimizing/intrinsics_arm.cc +++ b/compiler/optimizing/intrinsics_arm.cc @@ -1115,15 +1115,15 @@ static void GenerateVisitStringIndexOf(HInvoke* invoke, ArenaAllocator* allocator, bool start_at_zero) { LocationSummary* locations = invoke->GetLocations(); - Register tmp_reg = locations->GetTemp(0).AsRegister<Register>(); // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, - // or directly dispatch if we have a constant. + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCode* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { if (static_cast<uint32_t>(invoke->InputAt(1)->AsIntConstant()->GetValue()) > std::numeric_limits<uint16_t>::max()) { // Always needs the slow-path. We could directly dispatch to it, but this case should be @@ -1134,16 +1134,18 @@ static void GenerateVisitStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { Register char_reg = locations->InAt(1).AsRegister<Register>(); - __ LoadImmediate(tmp_reg, std::numeric_limits<uint16_t>::max()); - __ cmp(char_reg, ShifterOperand(tmp_reg)); + // 0xffff is not modified immediate but 0x10000 is, so use `>= 0x10000` instead of `> 0xffff`. + __ cmp(char_reg, + ShifterOperand(static_cast<uint32_t>(std::numeric_limits<uint16_t>::max()) + 1)); slow_path = new (allocator) IntrinsicSlowPathARM(invoke); codegen->AddSlowPath(slow_path); - __ b(slow_path->GetEntryLabel(), HI); + __ b(slow_path->GetEntryLabel(), HS); } if (start_at_zero) { + Register tmp_reg = locations->GetTemp(0).AsRegister<Register>(); DCHECK_EQ(tmp_reg, R2); // Start-index = 0. __ LoadImmediate(tmp_reg, 0); @@ -1170,7 +1172,7 @@ void IntrinsicLocationsBuilderARM::VisitStringIndexOf(HInvoke* invoke) { locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1))); locations->SetOut(Location::RegisterLocation(R0)); - // Need a temp for slow-path codepoint compare, and need to send start-index=0. + // Need to send start-index=0. locations->AddTemp(Location::RegisterLocation(calling_convention.GetRegisterAt(2))); } @@ -1190,9 +1192,6 @@ void IntrinsicLocationsBuilderARM::VisitStringIndexOfAfter(HInvoke* invoke) { locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1))); locations->SetInAt(2, Location::RegisterLocation(calling_convention.GetRegisterAt(2))); locations->SetOut(Location::RegisterLocation(R0)); - - // Need a temp for slow-path codepoint compare. - locations->AddTemp(Location::RequiresRegister()); } void IntrinsicCodeGeneratorARM::VisitStringIndexOfAfter(HInvoke* invoke) { diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc index 1d8229674c..cc5fd65e2e 100644 --- a/compiler/optimizing/intrinsics_arm64.cc +++ b/compiler/optimizing/intrinsics_arm64.cc @@ -1390,15 +1390,15 @@ static void GenerateVisitStringIndexOf(HInvoke* invoke, ArenaAllocator* allocator, bool start_at_zero) { LocationSummary* locations = invoke->GetLocations(); - Register tmp_reg = WRegisterFrom(locations->GetTemp(0)); // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, - // or directly dispatch if we have a constant. + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCodeARM64* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { if (static_cast<uint32_t>(invoke->InputAt(1)->AsIntConstant()->GetValue()) > 0xFFFFU) { // Always needs the slow-path. We could directly dispatch to it, but this case should be // rare, so for simplicity just put the full slow-path down and branch unconditionally. @@ -1408,17 +1408,17 @@ static void GenerateVisitStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { Register char_reg = WRegisterFrom(locations->InAt(1)); - __ Mov(tmp_reg, 0xFFFF); - __ Cmp(char_reg, Operand(tmp_reg)); + __ Tst(char_reg, 0xFFFF0000); slow_path = new (allocator) IntrinsicSlowPathARM64(invoke); codegen->AddSlowPath(slow_path); - __ B(hi, slow_path->GetEntryLabel()); + __ B(ne, slow_path->GetEntryLabel()); } if (start_at_zero) { // Start-index = 0. + Register tmp_reg = WRegisterFrom(locations->GetTemp(0)); __ Mov(tmp_reg, 0); } @@ -1442,7 +1442,7 @@ void IntrinsicLocationsBuilderARM64::VisitStringIndexOf(HInvoke* invoke) { locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); locations->SetOut(calling_convention.GetReturnLocation(Primitive::kPrimInt)); - // Need a temp for slow-path codepoint compare, and need to send start_index=0. + // Need to send start_index=0. locations->AddTemp(LocationFrom(calling_convention.GetRegisterAt(2))); } @@ -1462,9 +1462,6 @@ void IntrinsicLocationsBuilderARM64::VisitStringIndexOfAfter(HInvoke* invoke) { locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); locations->SetInAt(2, LocationFrom(calling_convention.GetRegisterAt(2))); locations->SetOut(calling_convention.GetReturnLocation(Primitive::kPrimInt)); - - // Need a temp for slow-path codepoint compare. - locations->AddTemp(Location::RequiresRegister()); } void IntrinsicCodeGeneratorARM64::VisitStringIndexOfAfter(HInvoke* invoke) { diff --git a/compiler/optimizing/intrinsics_list.h b/compiler/optimizing/intrinsics_list.h index dd9294d486..db60238fb4 100644 --- a/compiler/optimizing/intrinsics_list.h +++ b/compiler/optimizing/intrinsics_list.h @@ -107,6 +107,8 @@ V(StringGetCharsNoCheck, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow) \ V(StringIndexOf, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow) \ V(StringIndexOfAfter, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow) \ + V(StringIsEmpty, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kNoThrow) \ + V(StringLength, kDirect, kNeedsEnvironmentOrCache, kReadSideEffects, kNoThrow) \ V(StringNewStringFromBytes, kStatic, kNeedsEnvironmentOrCache, kAllSideEffects, kCanThrow) \ V(StringNewStringFromChars, kStatic, kNeedsEnvironmentOrCache, kAllSideEffects, kCanThrow) \ V(StringNewStringFromString, kStatic, kNeedsEnvironmentOrCache, kAllSideEffects, kCanThrow) \ diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc index 46195c104a..20b61f8a1c 100644 --- a/compiler/optimizing/intrinsics_mips.cc +++ b/compiler/optimizing/intrinsics_mips.cc @@ -2067,10 +2067,11 @@ static void GenerateStringIndexOf(HInvoke* invoke, // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - // Check for code points > 0xFFFF. Either a slow-path check when we - // don't know statically, or directly dispatch if we have a constant. + // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCodeMIPS* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { if (!IsUint<16>(invoke->InputAt(1)->AsIntConstant()->GetValue())) { // Always needs the slow-path. We could directly dispatch to it, // but this case should be rare, so for simplicity just put the @@ -2081,7 +2082,7 @@ static void GenerateStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { Register char_reg = locations->InAt(1).AsRegister<Register>(); // The "bltu" conditional branch tests to see if the character value // fits in a valid 16-bit (MIPS halfword) value. If it doesn't then diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index 1524e1e011..7188e1cc75 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -1563,10 +1563,11 @@ static void GenerateStringIndexOf(HInvoke* invoke, // Note that the null check must have been done earlier. DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0))); - // Check for code points > 0xFFFF. Either a slow-path check when we - // don't know statically, or directly dispatch if we have a constant. + // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCodeMIPS64* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { if (!IsUint<16>(invoke->InputAt(1)->AsIntConstant()->GetValue())) { // Always needs the slow-path. We could directly dispatch to it, // but this case should be rare, so for simplicity just put the @@ -1577,7 +1578,7 @@ static void GenerateStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { GpuRegister char_reg = locations->InAt(1).AsRegister<GpuRegister>(); __ LoadConst32(tmp_reg, std::numeric_limits<uint16_t>::max()); slow_path = new (allocator) IntrinsicSlowPathMIPS64(invoke); diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc index 4aab3e2768..d0edecae22 100644 --- a/compiler/optimizing/intrinsics_x86.cc +++ b/compiler/optimizing/intrinsics_x86.cc @@ -1418,9 +1418,10 @@ static void GenerateStringIndexOf(HInvoke* invoke, DCHECK_EQ(out, EDI); // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, - // or directly dispatch if we have a constant. + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCode* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { if (static_cast<uint32_t>(invoke->InputAt(1)->AsIntConstant()->GetValue()) > std::numeric_limits<uint16_t>::max()) { // Always needs the slow-path. We could directly dispatch to it, but this case should be @@ -1431,7 +1432,7 @@ static void GenerateStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { __ cmpl(search_value, Immediate(std::numeric_limits<uint16_t>::max())); slow_path = new (allocator) IntrinsicSlowPathX86(invoke); codegen->AddSlowPath(slow_path); diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc index 1d32dc7bc5..4ee2368ff5 100644 --- a/compiler/optimizing/intrinsics_x86_64.cc +++ b/compiler/optimizing/intrinsics_x86_64.cc @@ -1517,9 +1517,10 @@ static void GenerateStringIndexOf(HInvoke* invoke, DCHECK_EQ(out.AsRegister(), RDI); // Check for code points > 0xFFFF. Either a slow-path check when we don't know statically, - // or directly dispatch if we have a constant. + // or directly dispatch for a large constant, or omit slow-path for a small constant or a char. SlowPathCode* slow_path = nullptr; - if (invoke->InputAt(1)->IsIntConstant()) { + HInstruction* code_point = invoke->InputAt(1); + if (code_point->IsIntConstant()) { if (static_cast<uint32_t>(invoke->InputAt(1)->AsIntConstant()->GetValue()) > std::numeric_limits<uint16_t>::max()) { // Always needs the slow-path. We could directly dispatch to it, but this case should be @@ -1530,7 +1531,7 @@ static void GenerateStringIndexOf(HInvoke* invoke, __ Bind(slow_path->GetExitLabel()); return; } - } else { + } else if (code_point->GetType() != Primitive::kPrimChar) { __ cmpl(search_value, Immediate(std::numeric_limits<uint16_t>::max())); slow_path = new (allocator) IntrinsicSlowPathX86_64(invoke); codegen->AddSlowPath(slow_path); diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc index 2de41580b6..8a75a90cfd 100644 --- a/compiler/optimizing/load_store_elimination.cc +++ b/compiler/optimizing/load_store_elimination.cc @@ -733,19 +733,14 @@ class LSEVisitor : public HGraphVisitor { if (Primitive::PrimitiveKind(heap_value->GetType()) != Primitive::PrimitiveKind(instruction->GetType())) { // The only situation where the same heap location has different type is when - // we do an array get from a null constant. In order to stay properly typed - // we do not merge the array gets. + // we do an array get on an instruction that originates from the null constant + // (the null could be behind a field access, an array access, a null check or + // a bound type). + // In order to stay properly typed on primitive types, we do not eliminate + // the array gets. if (kIsDebugBuild) { DCHECK(heap_value->IsArrayGet()) << heap_value->DebugName(); DCHECK(instruction->IsArrayGet()) << instruction->DebugName(); - HInstruction* array = instruction->AsArrayGet()->GetArray(); - DCHECK(array->IsNullCheck()) << array->DebugName(); - HInstruction* input = HuntForOriginalReference(array->InputAt(0)); - DCHECK(input->IsNullConstant()) << input->DebugName(); - array = heap_value->AsArrayGet()->GetArray(); - DCHECK(array->IsNullCheck()) << array->DebugName(); - input = HuntForOriginalReference(array->InputAt(0)); - DCHECK(input->IsNullConstant()) << input->DebugName(); } return; } diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 6703695484..1e6bf07e42 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -56,9 +56,11 @@ void HGraph::FindBackEdges(ArenaBitVector* visited) { // Nodes that we're currently visiting, indexed by block id. ArenaBitVector visiting(arena_, blocks_.size(), false, kArenaAllocGraphBuilder); // Number of successors visited from a given node, indexed by block id. - ArenaVector<size_t> successors_visited(blocks_.size(), 0u, arena_->Adapter()); + ArenaVector<size_t> successors_visited(blocks_.size(), + 0u, + arena_->Adapter(kArenaAllocGraphBuilder)); // Stack of nodes that we're currently visiting (same as marked in "visiting" above). - ArenaVector<HBasicBlock*> worklist(arena_->Adapter()); + ArenaVector<HBasicBlock*> worklist(arena_->Adapter(kArenaAllocGraphBuilder)); constexpr size_t kDefaultWorklistSize = 8; worklist.reserve(kDefaultWorklistSize); visited->SetBit(entry_block_->GetBlockId()); @@ -206,17 +208,35 @@ HInstruction* HBasicBlock::GetFirstInstructionDisregardMoves() const { return instruction; } +static bool UpdateDominatorOfSuccessor(HBasicBlock* block, HBasicBlock* successor) { + DCHECK(ContainsElement(block->GetSuccessors(), successor)); + + HBasicBlock* old_dominator = successor->GetDominator(); + HBasicBlock* new_dominator = + (old_dominator == nullptr) ? block + : CommonDominator::ForPair(old_dominator, block); + + if (old_dominator == new_dominator) { + return false; + } else { + successor->SetDominator(new_dominator); + return true; + } +} + void HGraph::ComputeDominanceInformation() { DCHECK(reverse_post_order_.empty()); reverse_post_order_.reserve(blocks_.size()); reverse_post_order_.push_back(entry_block_); // Number of visits of a given node, indexed by block id. - ArenaVector<size_t> visits(blocks_.size(), 0u, arena_->Adapter()); + ArenaVector<size_t> visits(blocks_.size(), 0u, arena_->Adapter(kArenaAllocGraphBuilder)); // Number of successors visited from a given node, indexed by block id. - ArenaVector<size_t> successors_visited(blocks_.size(), 0u, arena_->Adapter()); + ArenaVector<size_t> successors_visited(blocks_.size(), + 0u, + arena_->Adapter(kArenaAllocGraphBuilder)); // Nodes for which we need to visit successors. - ArenaVector<HBasicBlock*> worklist(arena_->Adapter()); + ArenaVector<HBasicBlock*> worklist(arena_->Adapter(kArenaAllocGraphBuilder)); constexpr size_t kDefaultWorklistSize = 8; worklist.reserve(kDefaultWorklistSize); worklist.push_back(entry_block_); @@ -228,15 +248,7 @@ void HGraph::ComputeDominanceInformation() { worklist.pop_back(); } else { HBasicBlock* successor = current->GetSuccessors()[successors_visited[current_id]++]; - - if (successor->GetDominator() == nullptr) { - successor->SetDominator(current); - } else { - // The CommonDominator can work for multiple blocks as long as the - // domination information doesn't change. However, since we're changing - // that information here, we can use the finder only for pairs of blocks. - successor->SetDominator(CommonDominator::ForPair(successor->GetDominator(), current)); - } + UpdateDominatorOfSuccessor(current, successor); // Once all the forward edges have been visited, we know the immediate // dominator of the block. We can then start visiting its successors. @@ -248,6 +260,44 @@ void HGraph::ComputeDominanceInformation() { } } + // Check if the graph has back edges not dominated by their respective headers. + // If so, we need to update the dominators of those headers and recursively of + // their successors. We do that with a fix-point iteration over all blocks. + // The algorithm is guaranteed to terminate because it loops only if the sum + // of all dominator chains has decreased in the current iteration. + bool must_run_fix_point = false; + for (HBasicBlock* block : blocks_) { + if (block != nullptr && + block->IsLoopHeader() && + block->GetLoopInformation()->HasBackEdgeNotDominatedByHeader()) { + must_run_fix_point = true; + break; + } + } + if (must_run_fix_point) { + bool update_occurred = true; + while (update_occurred) { + update_occurred = false; + for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { + HBasicBlock* block = it.Current(); + for (HBasicBlock* successor : block->GetSuccessors()) { + update_occurred |= UpdateDominatorOfSuccessor(block, successor); + } + } + } + } + + // Make sure that there are no remaining blocks whose dominator information + // needs to be updated. + if (kIsDebugBuild) { + for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { + HBasicBlock* block = it.Current(); + for (HBasicBlock* successor : block->GetSuccessors()) { + DCHECK(!UpdateDominatorOfSuccessor(block, successor)); + } + } + } + // Populate `dominated_blocks_` information after computing all dominators. // The potential presence of irreducible loops requires to do it after. for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { @@ -595,20 +645,16 @@ void HLoopInformation::Populate() { blocks_.SetBit(header_->GetBlockId()); header_->SetInLoop(this); - bool is_irreducible_loop = false; - for (HBasicBlock* back_edge : GetBackEdges()) { - DCHECK(back_edge->GetDominator() != nullptr); - if (!header_->Dominates(back_edge)) { - is_irreducible_loop = true; - break; - } - } + bool is_irreducible_loop = HasBackEdgeNotDominatedByHeader(); if (is_irreducible_loop) { ArenaBitVector visited(graph->GetArena(), graph->GetBlocks().size(), /* expandable */ false, kArenaAllocGraphBuilder); + // Stop marking blocks at the loop header. + visited.SetBit(header_->GetBlockId()); + for (HBasicBlock* back_edge : GetBackEdges()) { PopulateIrreducibleRecursive(back_edge, &visited); } @@ -667,6 +713,16 @@ size_t HLoopInformation::GetLifetimeEnd() const { return last_position; } +bool HLoopInformation::HasBackEdgeNotDominatedByHeader() const { + for (HBasicBlock* back_edge : GetBackEdges()) { + DCHECK(back_edge->GetDominator() != nullptr); + if (!header_->Dominates(back_edge)) { + return true; + } + } + return false; +} + bool HBasicBlock::Dominates(HBasicBlock* other) const { // Walk up the dominator tree from `other`, to find out if `this` // is an ancestor. @@ -2396,6 +2452,7 @@ void HLoadString::SetLoadKindInternal(LoadKind load_kind) { } if (!NeedsEnvironment()) { RemoveEnvironment(); + SetSideEffects(SideEffects::None()); } } diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index afb995d438..934d355e82 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -725,6 +725,8 @@ class HLoopInformation : public ArenaObject<kArenaAllocLoopInfo> { blocks_.ClearAllBits(); } + bool HasBackEdgeNotDominatedByHeader() const; + private: // Internal recursive implementation of `Populate`. void PopulateRecursive(HBasicBlock* block); @@ -5226,9 +5228,22 @@ class HArrayLength : public HExpression<1> { return obj == InputAt(0); } + void MarkAsStringLength() { SetPackedFlag<kFlagIsStringLength>(); } + bool IsStringLength() const { return GetPackedFlag<kFlagIsStringLength>(); } + DECLARE_INSTRUCTION(ArrayLength); private: + // We treat a String as an array, creating the HArrayLength from String.length() + // or String.isEmpty() intrinsic in the instruction simplifier. We can always + // determine whether a particular HArrayLength is actually a String.length() by + // looking at the type of the input but that requires holding the mutator lock, so + // we prefer to use a flag, so that code generators don't need to do the locking. + static constexpr size_t kFlagIsStringLength = kNumberOfExpressionPackedBits; + static constexpr size_t kNumberOfArrayLengthPackedBits = kFlagIsStringLength + 1; + static_assert(kNumberOfArrayLengthPackedBits <= HInstruction::kMaxNumberOfPackedBits, + "Too many packed fields."); + DISALLOW_COPY_AND_ASSIGN(HArrayLength); }; @@ -5530,6 +5545,7 @@ class HLoadString : public HExpression<1> { SetPackedFlag<kFlagIsInDexCache>(true); DCHECK(!NeedsEnvironment()); RemoveEnvironment(); + SetSideEffects(SideEffects::None()); } size_t InputCount() const OVERRIDE { diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index c2aa0c0075..f96ca321c9 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -233,7 +233,7 @@ bool SsaBuilder::UpdatePrimitiveType(HPhi* phi, ArenaVector<HPhi*>* worklist) { } void SsaBuilder::RunPrimitiveTypePropagation() { - ArenaVector<HPhi*> worklist(graph_->GetArena()->Adapter()); + ArenaVector<HPhi*> worklist(graph_->GetArena()->Adapter(kArenaAllocGraphBuilder)); for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); @@ -319,7 +319,7 @@ bool SsaBuilder::FixAmbiguousArrayOps() { // uses (because they are untyped) and environment uses (if --debuggable). // After resolving all ambiguous ArrayGets, we will re-run primitive type // propagation on the Phis which need to be updated. - ArenaVector<HPhi*> worklist(graph_->GetArena()->Adapter()); + ArenaVector<HPhi*> worklist(graph_->GetArena()->Adapter(kArenaAllocGraphBuilder)); { ScopedObjectAccess soa(Thread::Current()); diff --git a/compiler/optimizing/ssa_phi_elimination.cc b/compiler/optimizing/ssa_phi_elimination.cc index 0978ae17f8..c67612e651 100644 --- a/compiler/optimizing/ssa_phi_elimination.cc +++ b/compiler/optimizing/ssa_phi_elimination.cc @@ -17,6 +17,7 @@ #include "ssa_phi_elimination.h" #include "base/arena_containers.h" +#include "base/arena_bit_vector.h" #include "base/bit_vector-inl.h" namespace art { @@ -30,7 +31,7 @@ void SsaDeadPhiElimination::MarkDeadPhis() { // Phis are constructed live and should not be revived if previously marked // dead. This algorithm temporarily breaks that invariant but we DCHECK that // only phis which were initially live are revived. - ArenaSet<HPhi*> initially_live(graph_->GetArena()->Adapter()); + ArenaSet<HPhi*> initially_live(graph_->GetArena()->Adapter(kArenaAllocSsaPhiElimination)); // Add to the worklist phis referenced by non-phi instructions. for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { @@ -127,8 +128,11 @@ void SsaRedundantPhiElimination::Run() { } } - ArenaSet<uint32_t> visited_phis_in_cycle(graph_->GetArena()->Adapter()); - ArenaVector<HPhi*> cycle_worklist(graph_->GetArena()->Adapter()); + ArenaBitVector visited_phis_in_cycle(graph_->GetArena(), + graph_->GetCurrentInstructionId(), + /* expandable */ false, + kArenaAllocSsaPhiElimination); + ArenaVector<HPhi*> cycle_worklist(graph_->GetArena()->Adapter(kArenaAllocSsaPhiElimination)); while (!worklist_.empty()) { HPhi* phi = worklist_.back(); @@ -146,11 +150,11 @@ void SsaRedundantPhiElimination::Run() { } HInstruction* candidate = nullptr; - visited_phis_in_cycle.clear(); + visited_phis_in_cycle.ClearAllBits(); cycle_worklist.clear(); cycle_worklist.push_back(phi); - visited_phis_in_cycle.insert(phi->GetId()); + visited_phis_in_cycle.SetBit(phi->GetId()); bool catch_phi_in_cycle = phi->IsCatchPhi(); bool irreducible_loop_phi_in_cycle = phi->IsIrreducibleLoopHeaderPhi(); @@ -182,9 +186,9 @@ void SsaRedundantPhiElimination::Run() { if (input == current) { continue; } else if (input->IsPhi()) { - if (!ContainsElement(visited_phis_in_cycle, input->GetId())) { + if (!visited_phis_in_cycle.IsBitSet(input->GetId())) { cycle_worklist.push_back(input->AsPhi()); - visited_phis_in_cycle.insert(input->GetId()); + visited_phis_in_cycle.SetBit(input->GetId()); catch_phi_in_cycle |= input->AsPhi()->IsCatchPhi(); irreducible_loop_phi_in_cycle |= input->IsIrreducibleLoopHeaderPhi(); } else { @@ -233,7 +237,7 @@ void SsaRedundantPhiElimination::Run() { // for elimination. Add phis that use this phi to the worklist. for (const HUseListNode<HInstruction*>& use : current->GetUses()) { HInstruction* user = use.GetUser(); - if (user->IsPhi() && !ContainsElement(visited_phis_in_cycle, user->GetId())) { + if (user->IsPhi() && !visited_phis_in_cycle.IsBitSet(user->GetId())) { worklist_.push_back(user->AsPhi()); } } diff --git a/profman/profman.cc b/profman/profman.cc index 3e632bce8b..37a560d203 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -216,7 +216,11 @@ class ProfMan FINAL { } void LogCompletionTime() { - LOG(INFO) << "profman took " << PrettyDuration(NanoTime() - start_ns_); + static constexpr uint64_t kLogThresholdTime = MsToNs(100); // 100ms + uint64_t time_taken = NanoTime() - start_ns_; + if (time_taken > kLogThresholdTime) { + LOG(WARNING) << "profman took " << PrettyDuration(time_taken); + } } std::vector<std::string> profile_files_; diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 562ee2d810..8064ed696f 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -894,57 +894,107 @@ DEFINE_FUNCTION art_quick_alloc_object_rosalloc RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception END_FUNCTION art_quick_alloc_object_rosalloc -// A handle-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB). -DEFINE_FUNCTION art_quick_alloc_object_tlab - // Fast path tlab allocation. - // RDI: uint32_t type_idx, RSI: ArtMethod* - // RDX, RCX, R8, R9: free. RAX: return val. - // TODO: Add read barrier when this function is used. - // Note this function can/should implement read barrier fast path only - // (no read barrier slow path) because this is the fast path of tlab allocation. - // We can fall back to the allocation slow path to do the read barrier slow path. -#if defined(USE_READ_BARRIER) - int3 - int3 -#endif - // Might need a special macro since rsi and edx is 32b/64b mismatched. - movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rsi), %rdx // Load dex cache resolved types array - // TODO: Add read barrier when this function is used. - // Might need to break down into multiple instructions to get the base address in a register. - // Load the class - movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx +// The common fast path code for art_quick_alloc_object_tlab and art_quick_alloc_object_region_tlab. +// +// RDI: type_idx, RSI: ArtMethod*, RDX/EDX: the class, RAX: return value. +// RCX: scratch, r8: Thread::Current(). +MACRO1(ALLOC_OBJECT_TLAB_FAST_PATH, slowPathLabel) testl %edx, %edx // Check null class - jz .Lart_quick_alloc_object_tlab_slow_path + jz RAW_VAR(slowPathLabel) // Check class status. cmpl LITERAL(MIRROR_CLASS_STATUS_INITIALIZED), MIRROR_CLASS_STATUS_OFFSET(%rdx) - jne .Lart_quick_alloc_object_tlab_slow_path - // Check access flags has kAccClassIsFinalizable + jne RAW_VAR(slowPathLabel) + // No fake dependence needed on x86 + // between status and flags load, + // since each load is a load-acquire, + // no loads reordering. + // Check access flags has + // kAccClassIsFinalizable testl LITERAL(ACCESS_FLAGS_CLASS_IS_FINALIZABLE), MIRROR_CLASS_ACCESS_FLAGS_OFFSET(%rdx) - jnz .Lart_quick_alloc_object_tlab_slow_path + jnz RAW_VAR(slowPathLabel) + movq %gs:THREAD_SELF_OFFSET, %r8 // r8 = thread + movq THREAD_LOCAL_END_OFFSET(%r8), %rax // Load thread_local_end. + subq THREAD_LOCAL_POS_OFFSET(%r8), %rax // Compute the remaining buffer size. movl MIRROR_CLASS_OBJECT_SIZE_OFFSET(%rdx), %ecx // Load the object size. + cmpq %rax, %rcx // Check if it fits. OK to do this + // before rounding up the object size + // assuming the buf size alignment. + ja RAW_VAR(slowPathLabel) addl LITERAL(OBJECT_ALIGNMENT_MASK), %ecx // Align the size by 8. (addr + 7) & ~7. andl LITERAL(OBJECT_ALIGNMENT_MASK_TOGGLED), %ecx - movq %gs:THREAD_SELF_OFFSET, %r8 // r8 = thread - movq THREAD_LOCAL_POS_OFFSET(%r8), %rax // Load thread_local_pos. + movq THREAD_LOCAL_POS_OFFSET(%r8), %rax // Load thread_local_pos + // as allocated object. addq %rax, %rcx // Add the object size. - cmpq THREAD_LOCAL_END_OFFSET(%r8), %rcx // Check if it fits. - ja .Lart_quick_alloc_object_tlab_slow_path movq %rcx, THREAD_LOCAL_POS_OFFSET(%r8) // Update thread_local_pos. - addq LITERAL(1), THREAD_LOCAL_OBJECTS_OFFSET(%r8) // Increment thread_local_objects. + addq LITERAL(1), THREAD_LOCAL_OBJECTS_OFFSET(%r8) // Increase thread_local_objects. // Store the class pointer in the header. // No fence needed for x86. + POISON_HEAP_REF edx movl %edx, MIRROR_OBJECT_CLASS_OFFSET(%rax) ret // Fast path succeeded. -.Lart_quick_alloc_object_tlab_slow_path: +END_MACRO + +// The common slow path code for art_quick_alloc_object_tlab and art_quick_alloc_object_region_tlab. +MACRO1(ALLOC_OBJECT_TLAB_SLOW_PATH, cxx_name) SETUP_REFS_ONLY_CALLEE_SAVE_FRAME // save ref containing registers for GC // Outgoing argument set up movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current() - call SYMBOL(artAllocObjectFromCodeTLAB) // cxx_name(arg0, arg1, Thread*) + call VAR(cxx_name) // cxx_name(arg0, arg1, Thread*) RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME // restore frame up to return address RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception +END_MACRO + +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB). +DEFINE_FUNCTION art_quick_alloc_object_tlab + // Fast path tlab allocation. + // RDI: uint32_t type_idx, RSI: ArtMethod* + // RDX, RCX, R8, R9: free. RAX: return val. +#if defined(USE_READ_BARRIER) + int3 + int3 +#endif + // Might need a special macro since rsi and edx is 32b/64b mismatched. + movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rsi), %rdx // Load dex cache resolved types array + // Might need to break down into multiple instructions to get the base address in a register. + // Load the class + movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx + ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_tlab_slow_path +.Lart_quick_alloc_object_tlab_slow_path: + ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeTLAB END_FUNCTION art_quick_alloc_object_tlab -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_region_tlab, RegionTLAB) +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_region_tlab, RegionTLAB). +DEFINE_FUNCTION art_quick_alloc_object_region_tlab + // Fast path region tlab allocation. + // RDI: uint32_t type_idx, RSI: ArtMethod* + // RDX, RCX, R8, R9: free. RAX: return val. +#if !defined(USE_READ_BARRIER) + int3 + int3 +#endif + // Might need a special macro since rsi and edx is 32b/64b mismatched. + movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rsi), %rdx // Load dex cache resolved types array + // Might need to break down into multiple instructions to get the base address in a register. + // Load the class + movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx + cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET + jne .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path +.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit: + ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_region_tlab_slow_path +.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path: + // The read barrier slow path. Mark the class. + PUSH rdi + PUSH rsi + // Outgoing argument set up + movq %rdx, %rdi // Pass the class as the first param. + call SYMBOL(artReadBarrierMark) // cxx_name(mirror::Object* obj) + movq %rax, %rdx + POP rsi + POP rdi + jmp .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_object_region_tlab_slow_path: + ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeRegionTLAB +END_FUNCTION art_quick_alloc_object_region_tlab ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc index 3df4e98c84..bbffbbb7b7 100644 --- a/runtime/dex_file_verifier.cc +++ b/runtime/dex_file_verifier.cc @@ -251,6 +251,14 @@ bool DexFileVerifier::CheckValidOffsetAndSize(uint32_t offset, return true; } +bool DexFileVerifier::CheckSizeLimit(uint32_t size, uint32_t limit, const char* label) { + if (size > limit) { + ErrorStringPrintf("Size(%u) should not exceed limit(%u) for %s.", size, limit, label); + return false; + } + return true; +} + bool DexFileVerifier::CheckHeader() { // Check file size from the header. uint32_t expected_size = header_->file_size_; @@ -298,10 +306,12 @@ bool DexFileVerifier::CheckHeader() { header_->type_ids_size_, 4, "type-ids") && + CheckSizeLimit(header_->type_ids_size_, DexFile::kDexNoIndex16, "type-ids") && CheckValidOffsetAndSize(header_->proto_ids_off_, header_->proto_ids_size_, 4, "proto-ids") && + CheckSizeLimit(header_->proto_ids_size_, DexFile::kDexNoIndex16, "proto-ids") && CheckValidOffsetAndSize(header_->field_ids_off_, header_->field_ids_size_, 4, @@ -1786,13 +1796,8 @@ bool DexFileVerifier::CheckInterProtoIdItem() { while (curr_it.HasNext() && prev_it.HasNext()) { uint16_t prev_idx = prev_it.GetTypeIdx(); uint16_t curr_idx = curr_it.GetTypeIdx(); - if (prev_idx == DexFile::kDexNoIndex16) { - break; - } - if (UNLIKELY(curr_idx == DexFile::kDexNoIndex16)) { - ErrorStringPrintf("Out-of-order proto_id arguments"); - return false; - } + DCHECK_NE(prev_idx, DexFile::kDexNoIndex16); + DCHECK_NE(curr_idx, DexFile::kDexNoIndex16); if (prev_idx < curr_idx) { break; @@ -1804,6 +1809,12 @@ bool DexFileVerifier::CheckInterProtoIdItem() { prev_it.Next(); curr_it.Next(); } + if (!curr_it.HasNext()) { + // Either a duplicate ProtoId or a ProtoId with a shorter argument list follows + // a ProtoId with a longer one. Both cases are forbidden by the specification. + ErrorStringPrintf("Out-of-order proto_id arguments"); + return false; + } } } @@ -2358,7 +2369,8 @@ static bool CheckAtMostOneOfPublicProtectedPrivate(uint32_t flags) { static std::string GetStringOrError(const uint8_t* const begin, const DexFile::Header* const header, uint32_t string_idx) { - if (header->string_ids_size_ < string_idx) { + // The `string_idx` is not guaranteed to be valid yet. + if (header->string_ids_size_ <= string_idx) { return "(error)"; } @@ -2375,9 +2387,11 @@ static std::string GetStringOrError(const uint8_t* const begin, static std::string GetClassOrError(const uint8_t* const begin, const DexFile::Header* const header, uint32_t class_idx) { - if (header->type_ids_size_ < class_idx) { - return "(error)"; - } + // The `class_idx` is either `FieldId::class_idx_` or `MethodId::class_idx_` and + // it has already been checked in `DexFileVerifier::CheckClassDataItemField()` + // or `DexFileVerifier::CheckClassDataItemMethod()`, respectively, to match + // a valid defining class. + CHECK_LT(class_idx, header->type_ids_size_); const DexFile::TypeId* type_id = reinterpret_cast<const DexFile::TypeId*>(begin + header->type_ids_off_) + class_idx; @@ -2390,9 +2404,8 @@ static std::string GetClassOrError(const uint8_t* const begin, static std::string GetFieldDescriptionOrError(const uint8_t* const begin, const DexFile::Header* const header, uint32_t idx) { - if (header->field_ids_size_ < idx) { - return "(error)"; - } + // The `idx` has already been checked in `DexFileVerifier::CheckClassDataItemField()`. + CHECK_LT(idx, header->field_ids_size_); const DexFile::FieldId* field_id = reinterpret_cast<const DexFile::FieldId*>(begin + header->field_ids_off_) + idx; @@ -2408,9 +2421,8 @@ static std::string GetFieldDescriptionOrError(const uint8_t* const begin, static std::string GetMethodDescriptionOrError(const uint8_t* const begin, const DexFile::Header* const header, uint32_t idx) { - if (header->method_ids_size_ < idx) { - return "(error)"; - } + // The `idx` has already been checked in `DexFileVerifier::CheckClassDataItemMethod()`. + CHECK_LT(idx, header->method_ids_size_); const DexFile::MethodId* method_id = reinterpret_cast<const DexFile::MethodId*>(begin + header->method_ids_off_) + idx; @@ -2608,7 +2620,13 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, *error_msg = StringPrintf("Constructor %" PRIu32 "(%s) is not flagged correctly wrt/ static.", method_index, GetMethodDescriptionOrError(begin_, header_, method_index).c_str()); - return false; + if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) { + return false; + } else { + // Allow in older versions, but warn. + LOG(WARNING) << "This dex file is invalid and will be rejected in the future. Error is: " + << *error_msg; + } } } // Check that static and private methods, as well as constructors, are in the direct methods list, @@ -2662,7 +2680,13 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, *error_msg = StringPrintf("Constructor %u(%s) must not be abstract or native", method_index, GetMethodDescriptionOrError(begin_, header_, method_index).c_str()); - return false; + if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) { + return false; + } else { + // Allow in older versions, but warn. + LOG(WARNING) << "This dex file is invalid and will be rejected in the future. Error is: " + << *error_msg; + } } if ((method_access_flags & kAccAbstract) != 0) { // Abstract methods are not allowed to have the following flags. diff --git a/runtime/dex_file_verifier.h b/runtime/dex_file_verifier.h index be0e6d83f8..90409db44b 100644 --- a/runtime/dex_file_verifier.h +++ b/runtime/dex_file_verifier.h @@ -49,6 +49,8 @@ class DexFileVerifier { // Checks whether the offset is zero (when size is zero) or that the offset falls within the area // claimed by the file. bool CheckValidOffsetAndSize(uint32_t offset, uint32_t size, size_t alignment, const char* label); + // Checks whether the size is less than the limit. + bool CheckSizeLimit(uint32_t size, uint32_t limit, const char* label); bool CheckIndex(uint32_t field, uint32_t limit, const char* label); bool CheckHeader(); diff --git a/runtime/dex_file_verifier_test.cc b/runtime/dex_file_verifier_test.cc index 344d186ad4..3741c1e76c 100644 --- a/runtime/dex_file_verifier_test.cc +++ b/runtime/dex_file_verifier_test.cc @@ -57,7 +57,14 @@ static const uint8_t kBase64Map[256] = { 255, 255, 255, 255 }; -static inline uint8_t* DecodeBase64(const char* src, size_t* dst_size) { +// Make the Dex file version 37. +static void MakeDexVersion37(DexFile* dex_file) { + size_t offset = OFFSETOF_MEMBER(DexFile::Header, magic_) + 6; + CHECK_EQ(*(dex_file->Begin() + offset), '5'); + *(const_cast<uint8_t*>(dex_file->Begin()) + offset) = '7'; +} + +static inline std::unique_ptr<uint8_t[]> DecodeBase64(const char* src, size_t* dst_size) { std::vector<uint8_t> tmp; uint32_t t = 0, y = 0; int g = 3; @@ -100,7 +107,7 @@ static inline uint8_t* DecodeBase64(const char* src, size_t* dst_size) { *dst_size = 0; } std::copy(tmp.begin(), tmp.end(), dst.get()); - return dst.release(); + return dst; } static void FixUpChecksum(uint8_t* dex_file) { @@ -113,25 +120,18 @@ static void FixUpChecksum(uint8_t* dex_file) { header->checksum_ = adler_checksum; } -// Custom deleter. Necessary to clean up the memory we use (to be able to mutate). -struct DexFileDeleter { - void operator()(DexFile* in) { - if (in != nullptr) { - delete[] in->Begin(); - delete in; - } - } -}; - -using DexFileUniquePtr = std::unique_ptr<DexFile, DexFileDeleter>; - class DexFileVerifierTest : public CommonRuntimeTest { protected: void VerifyModification(const char* dex_file_base64_content, const char* location, std::function<void(DexFile*)> f, const char* expected_error) { - DexFileUniquePtr dex_file(WrapAsDexFile(dex_file_base64_content)); + size_t length; + std::unique_ptr<uint8_t[]> dex_bytes = DecodeBase64(dex_file_base64_content, &length); + CHECK(dex_bytes != nullptr); + // Note: `dex_file` will be destroyed before `dex_bytes`. + std::unique_ptr<DexFile> dex_file( + new DexFile(dex_bytes.get(), length, "tmp", 0, nullptr, nullptr)); f(dex_file.get()); FixUpChecksum(const_cast<uint8_t*>(dex_file->Begin())); @@ -150,15 +150,6 @@ class DexFileVerifierTest : public CommonRuntimeTest { } } } - - private: - static DexFile* WrapAsDexFile(const char* dex_file_content_in_base_64) { - // Decode base64. - size_t length; - uint8_t* dex_bytes = DecodeBase64(dex_file_content_in_base_64, &length); - CHECK(dex_bytes != nullptr); - return new DexFile(dex_bytes, length, "tmp", 0, nullptr, nullptr); - } }; static std::unique_ptr<const DexFile> OpenDexFileBase64(const char* base64, @@ -290,7 +281,9 @@ static const char kMethodFlagsTestDex[] = // Find the method data for the first method with the given name (from class 0). Note: the pointer // is to the access flags, so that the caller doesn't have to handle the leb128-encoded method-index // delta. -static const uint8_t* FindMethodData(const DexFile* dex_file, const char* name) { +static const uint8_t* FindMethodData(const DexFile* dex_file, + const char* name, + /*out*/ uint32_t* method_idx = nullptr) { const DexFile::ClassDef& class_def = dex_file->GetClassDef(0); const uint8_t* class_data = dex_file->GetClassData(class_def); @@ -316,6 +309,9 @@ static const uint8_t* FindMethodData(const DexFile* dex_file, const char* name) const DexFile::StringId& string_id = dex_file->GetStringId(name_index); const char* str = dex_file->GetStringData(string_id); if (strcmp(name, str) == 0) { + if (method_idx != nullptr) { + *method_idx = method_index; + } DecodeUnsignedLeb128(&trailing); return trailing; } @@ -449,6 +445,7 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsConstructors) { kMethodFlagsTestDex, "method_flags_constructor_native_nocode", [&](DexFile* dex_file) { + MakeDexVersion37(dex_file); ApplyMaskToMethodFlags(dex_file, "foo", ~kAccDeclaredSynchronized); ApplyMaskToMethodFlags(dex_file, "bar", ~kAccDeclaredSynchronized); @@ -461,6 +458,7 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsConstructors) { kMethodFlagsTestDex, "method_flags_constructor_abstract_nocode", [&](DexFile* dex_file) { + MakeDexVersion37(dex_file); ApplyMaskToMethodFlags(dex_file, "foo", ~kAccDeclaredSynchronized); ApplyMaskToMethodFlags(dex_file, "bar", ~kAccDeclaredSynchronized); @@ -521,6 +519,7 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsConstructors) { kMethodFlagsTestDex, "init_not_allowed_flags", [&](DexFile* dex_file) { + MakeDexVersion37(dex_file); ApplyMaskToMethodFlags(dex_file, "foo", ~kAccDeclaredSynchronized); ApplyMaskToMethodFlags(dex_file, "bar", ~kAccDeclaredSynchronized); @@ -683,6 +682,22 @@ TEST_F(DexFileVerifierTest, MethodAccessFlagsIgnoredOK) { } } +TEST_F(DexFileVerifierTest, B28552165) { + // Regression test for bad error string retrieval in different situations. + // Using invalid access flags to trigger the error. + VerifyModification( + kMethodFlagsTestDex, + "b28552165", + [](DexFile* dex_file) { + OrMaskToMethodFlags(dex_file, "foo", kAccPublic | kAccProtected); + uint32_t method_idx; + FindMethodData(dex_file, "foo", &method_idx); + auto* method_id = const_cast<DexFile::MethodId*>(&dex_file->GetMethodId(method_idx)); + method_id->name_idx_ = dex_file->NumStringIds(); + }, + "Method may have only one of public/protected/private, LMethodFlags;.(error)"); +} + // Set of dex files for interface method tests. As it's not as easy to mutate method names, it's // just easier to break up bad cases. @@ -725,13 +740,6 @@ static uint32_t ApplyMaskShifted(uint32_t src_value, uint32_t mask) { return result; } -// Make the Dex file version 37. -static void MakeDexVersion37(DexFile* dex_file) { - size_t offset = OFFSETOF_MEMBER(DexFile::Header, magic_) + 6; - CHECK_EQ(*(dex_file->Begin() + offset), '5'); - *(const_cast<uint8_t*>(dex_file->Begin()) + offset) = '7'; -} - TEST_F(DexFileVerifierTest, MethodAccessFlagsInterfaces) { VerifyModification( kMethodFlagsInterface, @@ -1436,4 +1444,81 @@ TEST_F(DexFileVerifierTest, SectionAlignment) { } } +// Generated from +// +// .class LOverloading; +// +// .super Ljava/lang/Object; +// +// .method public static foo()V +// .registers 1 +// return-void +// .end method +// +// .method public static foo(I)V +// .registers 1 +// return-void +// .end method +static const char kProtoOrderingTestDex[] = + "ZGV4CjAzNQA1L+ABE6voQ9Lr4Ci//efB53oGnDr5PinsAQAAcAAAAHhWNBIAAAAAAAAAAFgBAAAG" + "AAAAcAAAAAQAAACIAAAAAgAAAJgAAAAAAAAAAAAAAAIAAACwAAAAAQAAAMAAAAAMAQAA4AAAAOAA" + "AADjAAAA8gAAAAYBAAAJAQAADQEAAAAAAAABAAAAAgAAAAMAAAADAAAAAwAAAAAAAAAEAAAAAwAA" + "ABQBAAABAAAABQAAAAEAAQAFAAAAAQAAAAAAAAACAAAAAAAAAP////8AAAAASgEAAAAAAAABSQAN" + "TE92ZXJsb2FkaW5nOwASTGphdmEvbGFuZy9PYmplY3Q7AAFWAAJWSQADZm9vAAAAAQAAAAAAAAAA" + "AAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAOAAAAAQABAAAAAAAAAAAAAQAAAA4AAAACAAAJpAIBCbgC" + "AAAMAAAAAAAAAAEAAAAAAAAAAQAAAAYAAABwAAAAAgAAAAQAAACIAAAAAwAAAAIAAACYAAAABQAA" + "AAIAAACwAAAABgAAAAEAAADAAAAAAiAAAAYAAADgAAAAARAAAAEAAAAUAQAAAxAAAAIAAAAcAQAA" + "ASAAAAIAAAAkAQAAACAAAAEAAABKAQAAABAAAAEAAABYAQAA"; + +TEST_F(DexFileVerifierTest, ProtoOrdering) { + { + // The input dex file should be good before modification. + ScratchFile tmp; + std::string error_msg; + std::unique_ptr<const DexFile> raw(OpenDexFileBase64(kProtoOrderingTestDex, + tmp.GetFilename().c_str(), + &error_msg)); + ASSERT_TRUE(raw.get() != nullptr) << error_msg; + } + + // Modify the order of the ProtoIds for two overloads of "foo" with the + // same return type and one having longer parameter list than the other. + for (size_t i = 0; i != 2; ++i) { + VerifyModification( + kProtoOrderingTestDex, + "proto_ordering", + [i](DexFile* dex_file) { + uint32_t method_idx; + const uint8_t* data = FindMethodData(dex_file, "foo", &method_idx); + CHECK(data != nullptr); + // There should be 2 methods called "foo". + CHECK_LT(method_idx + 1u, dex_file->NumMethodIds()); + CHECK_EQ(dex_file->GetMethodId(method_idx).name_idx_, + dex_file->GetMethodId(method_idx + 1).name_idx_); + CHECK_EQ(dex_file->GetMethodId(method_idx).proto_idx_ + 1u, + dex_file->GetMethodId(method_idx + 1).proto_idx_); + // Their return types should be the same. + uint32_t proto1_idx = dex_file->GetMethodId(method_idx).proto_idx_; + const DexFile::ProtoId& proto1 = dex_file->GetProtoId(proto1_idx); + const DexFile::ProtoId& proto2 = dex_file->GetProtoId(proto1_idx + 1u); + CHECK_EQ(proto1.return_type_idx_, proto2.return_type_idx_); + // And the first should not have any parameters while the second should have some. + CHECK(!DexFileParameterIterator(*dex_file, proto1).HasNext()); + CHECK(DexFileParameterIterator(*dex_file, proto2).HasNext()); + if (i == 0) { + // Swap the proto parameters and shorties to break the ordering. + std::swap(const_cast<uint32_t&>(proto1.parameters_off_), + const_cast<uint32_t&>(proto2.parameters_off_)); + std::swap(const_cast<uint32_t&>(proto1.shorty_idx_), + const_cast<uint32_t&>(proto2.shorty_idx_)); + } else { + // Copy the proto parameters and shorty to create duplicate proto id. + const_cast<uint32_t&>(proto1.parameters_off_) = proto2.parameters_off_; + const_cast<uint32_t&>(proto1.shorty_idx_) = proto2.shorty_idx_; + } + }, + "Out-of-order proto_id arguments"); + } +} + } // namespace art diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 16fbfaad32..fc6257302a 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -514,12 +514,18 @@ inline ArtMethod* FindMethodFromCode(uint32_t method_idx, mirror::Object** this_ CHECK(self->IsExceptionPending()); return nullptr; } else if (!method_reference_class->IsInterface()) { - // It is not an interface. - mirror::Class* super_class = referring_class->GetSuperClass(); + // It is not an interface. If the referring class is in the class hierarchy of the + // referenced class in the bytecode, we use its super class. Otherwise, we throw + // a NoSuchMethodError. + mirror::Class* super_class = nullptr; + if (method_reference_class->IsAssignableFrom(referring_class)) { + super_class = referring_class->GetSuperClass(); + } uint16_t vtable_index = resolved_method->GetMethodIndex(); if (access_check) { // Check existence of super class. - if (super_class == nullptr || !super_class->HasVTable() || + if (super_class == nullptr || + !super_class->HasVTable() || vtable_index >= static_cast<uint32_t>(super_class->GetVTableLength())) { // Behavior to agree with that of the verifier. ThrowNoSuchMethodError(type, resolved_method->GetDeclaringClass(), @@ -693,8 +699,13 @@ inline ArtMethod* FindMethodFast(uint32_t method_idx, mirror::Object* this_objec // Need to do full type resolution... return nullptr; } else if (!method_reference_class->IsInterface()) { - // It is not an interface. - mirror::Class* super_class = referrer->GetDeclaringClass()->GetSuperClass(); + // It is not an interface. If the referring class is in the class hierarchy of the + // referenced class in the bytecode, we use its super class. Otherwise, we cannot + // resolve the method. + if (!method_reference_class->IsAssignableFrom(referring_class)) { + return nullptr; + } + mirror::Class* super_class = referring_class->GetSuperClass(); if (resolved_method->GetMethodIndex() >= super_class->GetVTableLength()) { // The super class does not have the method. return nullptr; diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc index 3de718b397..d983a9fa19 100644 --- a/runtime/java_vm_ext.cc +++ b/runtime/java_vm_ext.cc @@ -74,10 +74,6 @@ class SharedLibrary { if (self != nullptr) { self->GetJniEnv()->DeleteWeakGlobalRef(class_loader_); } - - if (!needs_native_bridge_) { - android::CloseNativeLibrary(handle_); - } } jweak GetClassLoader() const { @@ -275,7 +271,8 @@ class Libraries { REQUIRES(!Locks::jni_libraries_lock_) SHARED_REQUIRES(Locks::mutator_lock_) { ScopedObjectAccessUnchecked soa(Thread::Current()); - std::vector<SharedLibrary*> unload_libraries; + typedef void (*JNI_OnUnloadFn)(JavaVM*, void*); + std::vector<JNI_OnUnloadFn> unload_functions; { MutexLock mu(soa.Self(), *Locks::jni_libraries_lock_); for (auto it = libraries_.begin(); it != libraries_.end(); ) { @@ -286,7 +283,15 @@ class Libraries { // the native libraries of the boot class loader. if (class_loader != nullptr && soa.Self()->IsJWeakCleared(class_loader)) { - unload_libraries.push_back(library); + void* const sym = library->FindSymbol("JNI_OnUnload", nullptr); + if (sym == nullptr) { + VLOG(jni) << "[No JNI_OnUnload found in \"" << library->GetPath() << "\"]"; + } else { + VLOG(jni) << "[JNI_OnUnload found for \"" << library->GetPath() << "\"]"; + JNI_OnUnloadFn jni_on_unload = reinterpret_cast<JNI_OnUnloadFn>(sym); + unload_functions.push_back(jni_on_unload); + } + delete library; it = libraries_.erase(it); } else { ++it; @@ -294,17 +299,9 @@ class Libraries { } } // Do this without holding the jni libraries lock to prevent possible deadlocks. - typedef void (*JNI_OnUnloadFn)(JavaVM*, void*); - for (auto library : unload_libraries) { - void* const sym = library->FindSymbol("JNI_OnUnload", nullptr); - if (sym == nullptr) { - VLOG(jni) << "[No JNI_OnUnload found in \"" << library->GetPath() << "\"]"; - } else { - VLOG(jni) << "[JNI_OnUnload found for \"" << library->GetPath() << "\"]: Calling..."; - JNI_OnUnloadFn jni_on_unload = reinterpret_cast<JNI_OnUnloadFn>(sym); - jni_on_unload(soa.Vm(), nullptr); - } - delete library; + for (JNI_OnUnloadFn fn : unload_functions) { + VLOG(jni) << "Calling JNI_OnUnload"; + (*fn)(soa.Vm(), nullptr); } } @@ -946,6 +943,11 @@ extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { if (!Runtime::Create(options, ignore_unrecognized)) { return JNI_ERR; } + + // Initialize native loader. This step makes sure we have + // everything set up before we start using JNI. + android::InitializeNativeLoader(); + Runtime* runtime = Runtime::Current(); bool started = runtime->Start(); if (!started) { @@ -955,10 +957,6 @@ extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { return JNI_ERR; } - // Initialize native loader. This step makes sure we have - // everything set up before we start using JNI. - android::InitializeNativeLoader(); - *p_env = Thread::Current()->GetJniEnv(); *p_vm = runtime->GetJavaVM(); return JNI_OK; diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index e9317a5435..dcc6300636 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -103,13 +103,13 @@ JitOptions* JitOptions::CreateFromRuntimeArguments(const RuntimeArgumentMap& opt } if (options.Exists(RuntimeArgumentMap::JITInvokeTransitionWeight)) { + jit_options->invoke_transition_weight_ = + *options.Get(RuntimeArgumentMap::JITInvokeTransitionWeight); if (jit_options->invoke_transition_weight_ > jit_options->warmup_threshold_) { LOG(FATAL) << "Invoke transition weight is above the warmup threshold."; } else if (jit_options->invoke_transition_weight_ == 0) { - LOG(FATAL) << "Invoke transition ratio cannot be 0."; + LOG(FATAL) << "Invoke transition weight cannot be 0."; } - jit_options->invoke_transition_weight_ = - *options.Get(RuntimeArgumentMap::JITInvokeTransitionWeight); } else { jit_options->invoke_transition_weight_ = std::max( jit_options->warmup_threshold_ / Jit::kDefaultInvokeTransitionWeightRatio, diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc index 0abe39d872..0126b4d0a4 100644 --- a/runtime/native/dalvik_system_DexFile.cc +++ b/runtime/native/dalvik_system_DexFile.cc @@ -16,6 +16,8 @@ #include "dalvik_system_DexFile.h" +#include <sstream> + #include "base/logging.h" #include "base/stl_util.h" #include "base/stringprintf.h" @@ -27,6 +29,7 @@ #include "mirror/class_loader.h" #include "mirror/object-inl.h" #include "mirror/string.h" +#include "oat_file.h" #include "oat_file_assistant.h" #include "oat_file_manager.h" #include "os.h" @@ -387,6 +390,61 @@ static jint GetDexOptNeeded(JNIEnv* env, return oat_file_assistant.GetDexOptNeeded(filter); } +static jstring DexFile_getDexFileStatus(JNIEnv* env, + jclass, + jstring javaFilename, + jstring javaInstructionSet) { + ScopedUtfChars filename(env, javaFilename); + if (env->ExceptionCheck()) { + return nullptr; + } + + ScopedUtfChars instruction_set(env, javaInstructionSet); + if (env->ExceptionCheck()) { + return nullptr; + } + + const InstructionSet target_instruction_set = GetInstructionSetFromString( + instruction_set.c_str()); + if (target_instruction_set == kNone) { + ScopedLocalRef<jclass> iae(env, env->FindClass("java/lang/IllegalArgumentException")); + std::string message(StringPrintf("Instruction set %s is invalid.", instruction_set.c_str())); + env->ThrowNew(iae.get(), message.c_str()); + return nullptr; + } + + OatFileAssistant oat_file_assistant(filename.c_str(), target_instruction_set, + false /* profile_changed */, + false /* load_executable */); + + std::ostringstream status; + bool oat_file_exists = false; + bool odex_file_exists = false; + if (oat_file_assistant.OatFileExists()) { + oat_file_exists = true; + status << *oat_file_assistant.OatFileName() << " [compilation_filter="; + status << CompilerFilter::NameOfFilter(oat_file_assistant.OatFileCompilerFilter()); + status << ", status=" << oat_file_assistant.OatFileStatus(); + } + + if (oat_file_assistant.OdexFileExists()) { + odex_file_exists = true; + if (oat_file_exists) { + status << "] "; + } + status << *oat_file_assistant.OdexFileName() << " [compilation_filter="; + status << CompilerFilter::NameOfFilter(oat_file_assistant.OdexFileCompilerFilter()); + status << ", status=" << oat_file_assistant.OdexFileStatus(); + } + + if (!oat_file_exists && !odex_file_exists) { + status << "invalid["; + } + + status << "]"; + return env->NewStringUTF(status.str().c_str()); +} + static jint DexFile_getDexOptNeeded(JNIEnv* env, jclass, jstring javaFilename, @@ -481,6 +539,16 @@ static jstring DexFile_getNonProfileGuidedCompilerFilter(JNIEnv* env, return env->NewStringUTF(new_filter_str.c_str()); } +static jboolean DexFile_isBackedByOatFile(JNIEnv* env, jclass, jobject cookie) { + const OatFile* oat_file = nullptr; + std::vector<const DexFile*> dex_files; + if (!ConvertJavaArrayToDexFiles(env, cookie, /*out */ dex_files, /* out */ oat_file)) { + DCHECK(env->ExceptionCheck()); + return false; + } + return oat_file != nullptr; +} + static JNINativeMethod gMethods[] = { NATIVE_METHOD(DexFile, closeDexFile, "(Ljava/lang/Object;)Z"), NATIVE_METHOD(DexFile, @@ -506,6 +574,9 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(DexFile, getNonProfileGuidedCompilerFilter, "(Ljava/lang/String;)Ljava/lang/String;"), + NATIVE_METHOD(DexFile, isBackedByOatFile, "(Ljava/lang/Object;)Z"), + NATIVE_METHOD(DexFile, getDexFileStatus, + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") }; void register_dalvik_system_DexFile(JNIEnv* env) { diff --git a/runtime/native/java_lang_reflect_Constructor.cc b/runtime/native/java_lang_reflect_Constructor.cc index ddcaadefa3..54b8afd1f3 100644 --- a/runtime/native/java_lang_reflect_Constructor.cc +++ b/runtime/native/java_lang_reflect_Constructor.cc @@ -34,20 +34,38 @@ static jobject Constructor_getAnnotationNative(JNIEnv* env, jobject javaMethod, ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); - return soa.AddLocalReference<jobject>( - method->GetDexFile()->GetAnnotationForMethod(method, klass)); + if (method->IsProxyMethod()) { + return nullptr; + } else { + Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); + return soa.AddLocalReference<jobject>( + method->GetDexFile()->GetAnnotationForMethod(method, klass)); + } } static jobjectArray Constructor_getDeclaredAnnotations(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - return soa.AddLocalReference<jobjectArray>(method->GetDexFile()->GetAnnotationsForMethod(method)); + if (method->IsProxyMethod()) { + mirror::Class* class_class = mirror::Class::GetJavaLangClass(); + mirror::Class* class_array_class = + Runtime::Current()->GetClassLinker()->FindArrayClass(soa.Self(), &class_class); + if (class_array_class == nullptr) { + return nullptr; + } + mirror::ObjectArray<mirror::Class>* empty_array = + mirror::ObjectArray<mirror::Class>::Alloc(soa.Self(), class_array_class, 0); + return soa.AddLocalReference<jobjectArray>(empty_array); + } else { + return soa.AddLocalReference<jobjectArray>( + method->GetDexFile()->GetAnnotationsForMethod(method)); + } } static jobjectArray Constructor_getExceptionTypes(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); - ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); + ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod) + ->GetInterfaceMethodIfProxy(sizeof(void*)); mirror::ObjectArray<mirror::Class>* result_array = method->GetDexFile()->GetExceptionTypesForMethod(method); if (result_array == nullptr) { @@ -69,7 +87,12 @@ static jobjectArray Constructor_getExceptionTypes(JNIEnv* env, jobject javaMetho static jobjectArray Constructor_getParameterAnnotationsNative(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - return soa.AddLocalReference<jobjectArray>(method->GetDexFile()->GetParameterAnnotations(method)); + if (method->IsProxyMethod()) { + return nullptr; + } else { + return soa.AddLocalReference<jobjectArray>( + method->GetDexFile()->GetParameterAnnotations(method)); + } } static jboolean Constructor_isAnnotationPresentNative(JNIEnv* env, jobject javaMethod, @@ -77,6 +100,10 @@ static jboolean Constructor_isAnnotationPresentNative(JNIEnv* env, jobject javaM ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); + if (method->IsProxyMethod()) { + // Proxies have no annotations. + return false; + } Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); return method->GetDexFile()->IsMethodAnnotationPresent(method, klass); } diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index a508e87c87..713e2f3fa9 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -30,6 +30,7 @@ #include "base/logging.h" #include "base/stringprintf.h" +#include "compiler_filter.h" #include "class_linker.h" #include "gc/heap.h" #include "gc/space/image_space.h" @@ -43,6 +44,24 @@ namespace art { +std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStatus status) { + switch (status) { + case OatFileAssistant::kOatOutOfDate: + stream << "kOatOutOfDate"; + break; + case OatFileAssistant::kOatUpToDate: + stream << "kOatUpToDate"; + break; + case OatFileAssistant::kOatNeedsRelocation: + stream << "kOatNeedsRelocation"; + break; + default: + UNREACHABLE(); + } + + return stream; +} + OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, bool profile_changed, @@ -377,6 +396,12 @@ bool OatFileAssistant::OdexFileIsUpToDate() { return cached_odex_file_is_up_to_date_; } +CompilerFilter::Filter OatFileAssistant::OdexFileCompilerFilter() { + const OatFile* odex_file = GetOdexFile(); + CHECK(odex_file != nullptr); + + return odex_file->GetCompilerFilter(); +} std::string OatFileAssistant::ArtFileName(const OatFile* oat_file) const { const std::string oat_file_location = oat_file->GetLocation(); // Replace extension with .art @@ -455,6 +480,13 @@ bool OatFileAssistant::OatFileIsUpToDate() { return cached_oat_file_is_up_to_date_; } +CompilerFilter::Filter OatFileAssistant::OatFileCompilerFilter() { + const OatFile* oat_file = GetOatFile(); + CHECK(oat_file != nullptr); + + return oat_file->GetCompilerFilter(); +} + OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& file) { // TODO: This could cause GivenOatFileIsOutOfDate to be called twice, which // is more work than we need to do. If performance becomes a concern, and diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 85f4a47868..f48cdf343c 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -19,6 +19,7 @@ #include <cstdint> #include <memory> +#include <sstream> #include <string> #include "arch/instruction_set.h" @@ -211,6 +212,9 @@ class OatFileAssistant { bool OdexFileIsOutOfDate(); bool OdexFileNeedsRelocation(); bool OdexFileIsUpToDate(); + // Must only be called if the associated odex file exists, i.e, if + // |OdexFileExists() == true|. + CompilerFilter::Filter OdexFileCompilerFilter(); // When the dex files is compiled on the target device, the oat file is the // result. The oat file will have been relocated to some @@ -227,6 +231,9 @@ class OatFileAssistant { bool OatFileIsOutOfDate(); bool OatFileNeedsRelocation(); bool OatFileIsUpToDate(); + // Must only be called if the associated oat file exists, i.e, if + // |OatFileExists() == true|. + CompilerFilter::Filter OatFileCompilerFilter(); // Return image file name. Does not cache since it relies on the oat file. std::string ArtFileName(const OatFile* oat_file) const; @@ -436,6 +443,8 @@ class OatFileAssistant { DISALLOW_COPY_AND_ASSIGN(OatFileAssistant); }; +std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStatus status); + } // namespace art #endif // ART_RUNTIME_OAT_FILE_ASSISTANT_H_ diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index d05ae42b0e..2b96328f80 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -4101,8 +4101,8 @@ ArtMethod* MethodVerifier::VerifyInvocationArgs( << " to super " << PrettyMethod(res_method); return nullptr; } - mirror::Class* super_klass = super.GetClass(); - if (res_method->GetMethodIndex() >= super_klass->GetVTableLength()) { + if (!reference_class->IsAssignableFrom(GetDeclaringClass().GetClass()) || + (res_method->GetMethodIndex() >= super.GetClass()->GetVTableLength())) { Fail(VERIFY_ERROR_NO_METHOD) << "invalid invoke-super from " << PrettyMethod(dex_method_idx_, *dex_file_) << " to super " << super diff --git a/test/004-JniTest/jni_test.cc b/test/004-JniTest/jni_test.cc index 2bdf8d1e71..8619ff7f3e 100644 --- a/test/004-JniTest/jni_test.cc +++ b/test/004-JniTest/jni_test.cc @@ -14,23 +14,22 @@ * limitations under the License. */ -#include <assert.h> #include <iostream> #include <pthread.h> #include <stdio.h> #include <vector> +#include "art_method-inl.h" +#include "base/logging.h" #include "jni.h" -#if defined(NDEBUG) -#error test code compiled without NDEBUG -#endif +namespace art { static JavaVM* jvm = nullptr; extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void*) { - assert(vm != nullptr); - assert(jvm == nullptr); + CHECK(vm != nullptr); + CHECK(jvm == nullptr); jvm = vm; std::cout << "JNI_OnLoad called" << std::endl; return JNI_VERSION_1_6; @@ -39,24 +38,24 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void*) { extern "C" JNIEXPORT void JNI_OnUnload(JavaVM*, void*) { // std::cout since LOG(INFO) adds extra stuff like pid. std::cout << "JNI_OnUnload called" << std::endl; - // Clear jvm for assert in test 004-JniTest. + // Clear jvm for CHECK in test 004-JniTest. jvm = nullptr; } static void* AttachHelper(void* arg) { - assert(jvm != nullptr); + CHECK(jvm != nullptr); JNIEnv* env = nullptr; JavaVMAttachArgs args = { JNI_VERSION_1_6, __FUNCTION__, nullptr }; int attach_result = jvm->AttachCurrentThread(&env, &args); - assert(attach_result == 0); + CHECK_EQ(attach_result, 0); typedef void (*Fn)(JNIEnv*); Fn fn = reinterpret_cast<Fn>(arg); fn(env); int detach_result = jvm->DetachCurrentThread(); - assert(detach_result == 0); + CHECK_EQ(detach_result, 0); return nullptr; } @@ -64,19 +63,19 @@ static void PthreadHelper(void (*fn)(JNIEnv*)) { pthread_t pthread; int pthread_create_result = pthread_create(&pthread, nullptr, AttachHelper, reinterpret_cast<void*>(fn)); - assert(pthread_create_result == 0); + CHECK_EQ(pthread_create_result, 0); int pthread_join_result = pthread_join(pthread, nullptr); - assert(pthread_join_result == 0); + CHECK_EQ(pthread_join_result, 0); } static void testFindClassOnAttachedNativeThread(JNIEnv* env) { jclass clazz = env->FindClass("Main"); - assert(clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(clazz != nullptr); + CHECK(!env->ExceptionCheck()); jobjectArray array = env->NewObjectArray(0, clazz, nullptr); - assert(array != nullptr); - assert(!env->ExceptionCheck()); + CHECK(array != nullptr); + CHECK(!env->ExceptionCheck()); } // http://b/10994325 @@ -86,12 +85,12 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testFindClassOnAttachedNativeThread( static void testFindFieldOnAttachedNativeThread(JNIEnv* env) { jclass clazz = env->FindClass("Main"); - assert(clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(clazz != nullptr); + CHECK(!env->ExceptionCheck()); jfieldID field = env->GetStaticFieldID(clazz, "testFindFieldOnAttachedNativeThreadField", "Z"); - assert(field != nullptr); - assert(!env->ExceptionCheck()); + CHECK(field != nullptr); + CHECK(!env->ExceptionCheck()); env->SetStaticBooleanField(clazz, field, JNI_TRUE); } @@ -103,38 +102,38 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testFindFieldOnAttachedNativeThreadN static void testReflectFieldGetFromAttachedNativeThread(JNIEnv* env) { jclass clazz = env->FindClass("Main"); - assert(clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(clazz != nullptr); + CHECK(!env->ExceptionCheck()); jclass class_clazz = env->FindClass("java/lang/Class"); - assert(class_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(class_clazz != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID getFieldMetodId = env->GetMethodID(class_clazz, "getField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;"); - assert(getFieldMetodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getFieldMetodId != nullptr); + CHECK(!env->ExceptionCheck()); jstring field_name = env->NewStringUTF("testReflectFieldGetFromAttachedNativeThreadField"); - assert(field_name != nullptr); - assert(!env->ExceptionCheck()); + CHECK(field_name != nullptr); + CHECK(!env->ExceptionCheck()); jobject field = env->CallObjectMethod(clazz, getFieldMetodId, field_name); - assert(field != nullptr); - assert(!env->ExceptionCheck()); + CHECK(field != nullptr); + CHECK(!env->ExceptionCheck()); jclass field_clazz = env->FindClass("java/lang/reflect/Field"); - assert(field_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(field_clazz != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID getBooleanMetodId = env->GetMethodID(field_clazz, "getBoolean", "(Ljava/lang/Object;)Z"); - assert(getBooleanMetodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getBooleanMetodId != nullptr); + CHECK(!env->ExceptionCheck()); jboolean value = env->CallBooleanMethod(field, getBooleanMetodId, /* ignored */ clazz); - assert(value == false); - assert(!env->ExceptionCheck()); + CHECK(value == false); + CHECK(!env->ExceptionCheck()); } // http://b/15539150 @@ -148,22 +147,22 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testReflectFieldGetFromAttachedNativ extern "C" JNIEXPORT void JNICALL Java_Main_testCallStaticVoidMethodOnSubClassNative(JNIEnv* env, jclass) { jclass super_class = env->FindClass("Main$testCallStaticVoidMethodOnSubClass_SuperClass"); - assert(super_class != nullptr); + CHECK(super_class != nullptr); jmethodID execute = env->GetStaticMethodID(super_class, "execute", "()V"); - assert(execute != nullptr); + CHECK(execute != nullptr); jclass sub_class = env->FindClass("Main$testCallStaticVoidMethodOnSubClass_SubClass"); - assert(sub_class != nullptr); + CHECK(sub_class != nullptr); env->CallStaticVoidMethod(sub_class, execute); } extern "C" JNIEXPORT jobject JNICALL Java_Main_testGetMirandaMethodNative(JNIEnv* env, jclass) { jclass abstract_class = env->FindClass("Main$testGetMirandaMethod_MirandaAbstract"); - assert(abstract_class != nullptr); + CHECK(abstract_class != nullptr); jmethodID miranda_method = env->GetMethodID(abstract_class, "inInterface", "()Z"); - assert(miranda_method != nullptr); + CHECK(miranda_method != nullptr); return env->ToReflectedMethod(abstract_class, miranda_method, JNI_FALSE); } @@ -171,11 +170,11 @@ extern "C" JNIEXPORT jobject JNICALL Java_Main_testGetMirandaMethodNative(JNIEnv extern "C" void JNICALL Java_Main_testZeroLengthByteBuffers(JNIEnv* env, jclass) { std::vector<uint8_t> buffer(1); jobject byte_buffer = env->NewDirectByteBuffer(&buffer[0], 0); - assert(byte_buffer != nullptr); - assert(!env->ExceptionCheck()); + CHECK(byte_buffer != nullptr); + CHECK(!env->ExceptionCheck()); - assert(env->GetDirectBufferAddress(byte_buffer) == &buffer[0]); - assert(env->GetDirectBufferCapacity(byte_buffer) == 0); + CHECK_EQ(env->GetDirectBufferAddress(byte_buffer), &buffer[0]); + CHECK_EQ(env->GetDirectBufferCapacity(byte_buffer), 0); } constexpr size_t kByteReturnSize = 7; @@ -185,18 +184,18 @@ extern "C" jbyte JNICALL Java_Main_byteMethod(JNIEnv*, jclass, jbyte b1, jbyte b jbyte b3, jbyte b4, jbyte b5, jbyte b6, jbyte b7, jbyte b8, jbyte b9, jbyte b10) { // We use b1 to drive the output. - assert(b2 == 2); - assert(b3 == -3); - assert(b4 == 4); - assert(b5 == -5); - assert(b6 == 6); - assert(b7 == -7); - assert(b8 == 8); - assert(b9 == -9); - assert(b10 == 10); - - assert(0 <= b1); - assert(b1 < static_cast<jbyte>(kByteReturnSize)); + CHECK_EQ(b2, 2); + CHECK_EQ(b3, -3); + CHECK_EQ(b4, 4); + CHECK_EQ(b5, -5); + CHECK_EQ(b6, 6); + CHECK_EQ(b7, -7); + CHECK_EQ(b8, 8); + CHECK_EQ(b9, -9); + CHECK_EQ(b10, 10); + + CHECK_LE(0, b1); + CHECK_LT(b1, static_cast<jbyte>(kByteReturnSize)); return byte_returns[b1]; } @@ -210,18 +209,18 @@ extern "C" jshort JNICALL Java_Main_shortMethod(JNIEnv*, jclass, jshort s1, jsho jshort s3, jshort s4, jshort s5, jshort s6, jshort s7, jshort s8, jshort s9, jshort s10) { // We use s1 to drive the output. - assert(s2 == 2); - assert(s3 == -3); - assert(s4 == 4); - assert(s5 == -5); - assert(s6 == 6); - assert(s7 == -7); - assert(s8 == 8); - assert(s9 == -9); - assert(s10 == 10); - - assert(0 <= s1); - assert(s1 < static_cast<jshort>(kShortReturnSize)); + CHECK_EQ(s2, 2); + CHECK_EQ(s3, -3); + CHECK_EQ(s4, 4); + CHECK_EQ(s5, -5); + CHECK_EQ(s6, 6); + CHECK_EQ(s7, -7); + CHECK_EQ(s8, 8); + CHECK_EQ(s9, -9); + CHECK_EQ(s10, 10); + + CHECK_LE(0, s1); + CHECK_LT(s1, static_cast<jshort>(kShortReturnSize)); return short_returns[s1]; } @@ -231,17 +230,17 @@ extern "C" jboolean JNICALL Java_Main_booleanMethod(JNIEnv*, jclass, jboolean b1 jboolean b5, jboolean b6, jboolean b7, jboolean b8, jboolean b9, jboolean b10) { // We use b1 to drive the output. - assert(b2 == JNI_TRUE); - assert(b3 == JNI_FALSE); - assert(b4 == JNI_TRUE); - assert(b5 == JNI_FALSE); - assert(b6 == JNI_TRUE); - assert(b7 == JNI_FALSE); - assert(b8 == JNI_TRUE); - assert(b9 == JNI_FALSE); - assert(b10 == JNI_TRUE); - - assert(b1 == JNI_TRUE || b1 == JNI_FALSE); + CHECK_EQ(b2, JNI_TRUE); + CHECK_EQ(b3, JNI_FALSE); + CHECK_EQ(b4, JNI_TRUE); + CHECK_EQ(b5, JNI_FALSE); + CHECK_EQ(b6, JNI_TRUE); + CHECK_EQ(b7, JNI_FALSE); + CHECK_EQ(b8, JNI_TRUE); + CHECK_EQ(b9, JNI_FALSE); + CHECK_EQ(b10, JNI_TRUE); + + CHECK(b1 == JNI_TRUE || b1 == JNI_FALSE); return b1; } @@ -252,17 +251,17 @@ extern "C" jchar JNICALL Java_Main_charMethod(JNIEnv*, jclass, jchar c1, jchar c jchar c3, jchar c4, jchar c5, jchar c6, jchar c7, jchar c8, jchar c9, jchar c10) { // We use c1 to drive the output. - assert(c2 == 'a'); - assert(c3 == 'b'); - assert(c4 == 'c'); - assert(c5 == '0'); - assert(c6 == '1'); - assert(c7 == '2'); - assert(c8 == 1234); - assert(c9 == 2345); - assert(c10 == 3456); - - assert(c1 < static_cast<jchar>(kCharReturnSize)); + CHECK_EQ(c2, 'a'); + CHECK_EQ(c3, 'b'); + CHECK_EQ(c4, 'c'); + CHECK_EQ(c5, '0'); + CHECK_EQ(c6, '1'); + CHECK_EQ(c7, '2'); + CHECK_EQ(c8, 1234); + CHECK_EQ(c9, 2345); + CHECK_EQ(c10, 3456); + + CHECK_LT(c1, static_cast<jchar>(kCharReturnSize)); return char_returns[c1]; } @@ -281,39 +280,39 @@ static void testShallowGetCallingClassLoader(JNIEnv* env) { // Test direct call. { jclass vmstack_clazz = env->FindClass("dalvik/system/VMStack"); - assert(vmstack_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(vmstack_clazz != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID getCallingClassLoaderMethodId = env->GetStaticMethodID(vmstack_clazz, "getCallingClassLoader", "()Ljava/lang/ClassLoader;"); - assert(getCallingClassLoaderMethodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getCallingClassLoaderMethodId != nullptr); + CHECK(!env->ExceptionCheck()); jobject class_loader = env->CallStaticObjectMethod(vmstack_clazz, getCallingClassLoaderMethodId); - assert(class_loader == nullptr); - assert(!env->ExceptionCheck()); + CHECK(class_loader == nullptr); + CHECK(!env->ExceptionCheck()); } // Test one-level call. Use System.loadLibrary(). { jclass system_clazz = env->FindClass("java/lang/System"); - assert(system_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(system_clazz != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID loadLibraryMethodId = env->GetStaticMethodID(system_clazz, "loadLibrary", "(Ljava/lang/String;)V"); - assert(loadLibraryMethodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(loadLibraryMethodId != nullptr); + CHECK(!env->ExceptionCheck()); // Create a string object. jobject library_string = env->NewStringUTF("non_existing_library"); - assert(library_string != nullptr); - assert(!env->ExceptionCheck()); + CHECK(library_string != nullptr); + CHECK(!env->ExceptionCheck()); env->CallStaticVoidMethod(system_clazz, loadLibraryMethodId, library_string); - assert(env->ExceptionCheck()); + CHECK(env->ExceptionCheck()); // We expect UnsatisfiedLinkError. jthrowable thrown = env->ExceptionOccurred(); @@ -321,7 +320,7 @@ static void testShallowGetCallingClassLoader(JNIEnv* env) { jclass unsatisfied_link_error_clazz = env->FindClass("java/lang/UnsatisfiedLinkError"); jclass thrown_class = env->GetObjectClass(thrown); - assert(env->IsSameObject(unsatisfied_link_error_clazz, thrown_class)); + CHECK(env->IsSameObject(unsatisfied_link_error_clazz, thrown_class)); } } @@ -333,31 +332,31 @@ extern "C" JNIEXPORT void JNICALL Java_Main_nativeTestShallowGetCallingClassLoad static void testShallowGetStackClass2(JNIEnv* env) { jclass vmstack_clazz = env->FindClass("dalvik/system/VMStack"); - assert(vmstack_clazz != nullptr); - assert(!env->ExceptionCheck()); + CHECK(vmstack_clazz != nullptr); + CHECK(!env->ExceptionCheck()); // Test direct call. { jmethodID getStackClass2MethodId = env->GetStaticMethodID(vmstack_clazz, "getStackClass2", "()Ljava/lang/Class;"); - assert(getStackClass2MethodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getStackClass2MethodId != nullptr); + CHECK(!env->ExceptionCheck()); jobject caller_class = env->CallStaticObjectMethod(vmstack_clazz, getStackClass2MethodId); - assert(caller_class == nullptr); - assert(!env->ExceptionCheck()); + CHECK(caller_class == nullptr); + CHECK(!env->ExceptionCheck()); } // Test one-level call. Use VMStack.getStackClass1(). { jmethodID getStackClass1MethodId = env->GetStaticMethodID(vmstack_clazz, "getStackClass1", "()Ljava/lang/Class;"); - assert(getStackClass1MethodId != nullptr); - assert(!env->ExceptionCheck()); + CHECK(getStackClass1MethodId != nullptr); + CHECK(!env->ExceptionCheck()); jobject caller_class = env->CallStaticObjectMethod(vmstack_clazz, getStackClass1MethodId); - assert(caller_class == nullptr); - assert(!env->ExceptionCheck()); + CHECK(caller_class == nullptr); + CHECK(!env->ExceptionCheck()); } // For better testing we would need to compile against libcore and have a two-deep stack @@ -416,8 +415,8 @@ class JniCallNonvirtualVoidMethodTest { env_->ExceptionDescribe(); env_->FatalError(__FUNCTION__); } - assert(!env_->ExceptionCheck()); - assert(c != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(c != nullptr); return c; } @@ -429,7 +428,7 @@ class JniCallNonvirtualVoidMethodTest { env_->ExceptionDescribe(); env_->FatalError(__FUNCTION__); } - assert(m != nullptr); + CHECK(m != nullptr); return m; } @@ -439,7 +438,7 @@ class JniCallNonvirtualVoidMethodTest { env_->ExceptionDescribe(); env_->FatalError(__FUNCTION__); } - assert(o != nullptr); + CHECK(o != nullptr); return o; } @@ -467,7 +466,7 @@ class JniCallNonvirtualVoidMethodTest { env_->ExceptionDescribe(); env_->FatalError(__FUNCTION__); } - assert(m != nullptr); + CHECK(m != nullptr); return m; } @@ -508,21 +507,21 @@ class JniCallNonvirtualVoidMethodTest { jobject sub_super = CallConstructor(sub_, super_constructor_); jobject sub_sub = CallConstructor(sub_, sub_constructor_); - assert(env_->IsInstanceOf(super_super, super_)); - assert(!env_->IsInstanceOf(super_super, sub_)); + CHECK(env_->IsInstanceOf(super_super, super_)); + CHECK(!env_->IsInstanceOf(super_super, sub_)); // Note that even though we called (and ran) the subclass // constructor, we are not the subclass. - assert(env_->IsInstanceOf(super_sub, super_)); - assert(!env_->IsInstanceOf(super_sub, sub_)); + CHECK(env_->IsInstanceOf(super_sub, super_)); + CHECK(!env_->IsInstanceOf(super_sub, sub_)); // Note that even though we called the superclass constructor, we // are still the subclass. - assert(env_->IsInstanceOf(sub_super, super_)); - assert(env_->IsInstanceOf(sub_super, sub_)); + CHECK(env_->IsInstanceOf(sub_super, super_)); + CHECK(env_->IsInstanceOf(sub_super, sub_)); - assert(env_->IsInstanceOf(sub_sub, super_)); - assert(env_->IsInstanceOf(sub_sub, sub_)); + CHECK(env_->IsInstanceOf(sub_sub, super_)); + CHECK(env_->IsInstanceOf(sub_sub, sub_)); } void TestnonstaticCallNonvirtualMethod(bool super_object, bool super_class, bool super_method, const char* test_case) { @@ -542,8 +541,8 @@ class JniCallNonvirtualVoidMethodTest { CallMethod(o, c, m, true, test_case); jboolean super_field = GetBooleanField(o, super_field_); jboolean sub_field = GetBooleanField(o, sub_field_); - assert(super_field == super_method); - assert(sub_field != super_method); + CHECK_EQ(super_field, super_method); + CHECK_NE(sub_field, super_method); } void TestnonstaticCallNonvirtualMethod() { @@ -565,20 +564,20 @@ extern "C" void JNICALL Java_Main_testCallNonvirtual(JNIEnv* env, jclass) { extern "C" JNIEXPORT void JNICALL Java_Main_testNewStringObject(JNIEnv* env, jclass) { jclass c = env->FindClass("java/lang/String"); - assert(c != nullptr); + CHECK(c != nullptr); jmethodID mid1 = env->GetMethodID(c, "<init>", "()V"); - assert(mid1 != nullptr); - assert(!env->ExceptionCheck()); + CHECK(mid1 != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID mid2 = env->GetMethodID(c, "<init>", "([B)V"); - assert(mid2 != nullptr); - assert(!env->ExceptionCheck()); + CHECK(mid2 != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID mid3 = env->GetMethodID(c, "<init>", "([C)V"); - assert(mid3 != nullptr); - assert(!env->ExceptionCheck()); + CHECK(mid3 != nullptr); + CHECK(!env->ExceptionCheck()); jmethodID mid4 = env->GetMethodID(c, "<init>", "(Ljava/lang/String;)V"); - assert(mid4 != nullptr); - assert(!env->ExceptionCheck()); + CHECK(mid4 != nullptr); + CHECK(!env->ExceptionCheck()); const char* test_array = "Test"; int byte_array_length = strlen(test_array); @@ -587,22 +586,22 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testNewStringObject(JNIEnv* env, jcl // Test NewObject jstring s = reinterpret_cast<jstring>(env->NewObject(c, mid2, byte_array)); - assert(s != nullptr); - assert(env->GetStringLength(s) == byte_array_length); - assert(env->GetStringUTFLength(s) == byte_array_length); + CHECK(s != nullptr); + CHECK_EQ(env->GetStringLength(s), byte_array_length); + CHECK_EQ(env->GetStringUTFLength(s), byte_array_length); const char* chars = env->GetStringUTFChars(s, nullptr); - assert(strcmp(test_array, chars) == 0); + CHECK_EQ(strcmp(test_array, chars), 0); env->ReleaseStringUTFChars(s, chars); // Test AllocObject and Call(Nonvirtual)VoidMethod jstring s1 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s1 != nullptr); + CHECK(s1 != nullptr); jstring s2 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s2 != nullptr); + CHECK(s2 != nullptr); jstring s3 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s3 != nullptr); + CHECK(s3 != nullptr); jstring s4 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s4 != nullptr); + CHECK(s4 != nullptr); jcharArray char_array = env->NewCharArray(5); jstring string_arg = env->NewStringUTF("helloworld"); @@ -621,18 +620,18 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testNewStringObject(JNIEnv* env, jcl // Test with global and weak global references jstring s5 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s5 != nullptr); + CHECK(s5 != nullptr); s5 = reinterpret_cast<jstring>(env->NewGlobalRef(s5)); jstring s6 = reinterpret_cast<jstring>(env->AllocObject(c)); - assert(s6 != nullptr); + CHECK(s6 != nullptr); s6 = reinterpret_cast<jstring>(env->NewWeakGlobalRef(s6)); env->CallVoidMethod(s5, mid1); env->CallNonvirtualVoidMethod(s6, c, mid2, byte_array); - assert(env->GetStringLength(s5) == 0); - assert(env->GetStringLength(s6) == byte_array_length); + CHECK_EQ(env->GetStringLength(s5), 0); + CHECK_EQ(env->GetStringLength(s6), byte_array_length); const char* chars6 = env->GetStringUTFChars(s6, nullptr); - assert(strcmp(test_array, chars6) == 0); + CHECK_EQ(strcmp(test_array, chars6), 0); env->ReleaseStringUTFChars(s6, chars6); } @@ -664,8 +663,8 @@ class JniCallDefaultMethodsTest { public: explicit JniCallDefaultMethodsTest(JNIEnv* env) : env_(env), concrete_class_(env_->FindClass("ConcreteClass")) { - assert(!env_->ExceptionCheck()); - assert(concrete_class_ != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(concrete_class_ != nullptr); } void Test() { @@ -688,14 +687,14 @@ class JniCallDefaultMethodsTest { void TestCalls(const char* declaring_class, std::vector<const char*> methods) { jmethodID new_method = env_->GetMethodID(concrete_class_, "<init>", "()V"); jobject obj = env_->NewObject(concrete_class_, new_method); - assert(!env_->ExceptionCheck()); - assert(obj != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(obj != nullptr); jclass decl_class = env_->FindClass(declaring_class); - assert(!env_->ExceptionCheck()); - assert(decl_class != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(decl_class != nullptr); for (const char* method : methods) { jmethodID method_id = env_->GetMethodID(decl_class, method, "()V"); - assert(!env_->ExceptionCheck()); + CHECK(!env_->ExceptionCheck()); printf("Calling method %s->%s on object of type ConcreteClass\n", declaring_class, method); env_->CallVoidMethod(obj, method_id); if (env_->ExceptionCheck()) { @@ -704,10 +703,10 @@ class JniCallDefaultMethodsTest { jmethodID to_string = env_->GetMethodID( env_->FindClass("java/lang/Object"), "toString", "()Ljava/lang/String;"); jstring exception_string = (jstring) env_->CallObjectMethod(thrown, to_string); - assert(!env_->ExceptionCheck()); + CHECK(!env_->ExceptionCheck()); const char* exception_string_utf8 = env_->GetStringUTFChars(exception_string, nullptr); - assert(!env_->ExceptionCheck()); - assert(exception_string_utf8 != nullptr); + CHECK(!env_->ExceptionCheck()); + CHECK(exception_string_utf8 != nullptr); printf("EXCEPTION OCCURED: %s\n", exception_string_utf8); env_->ReleaseStringUTFChars(exception_string, exception_string_utf8); } @@ -724,12 +723,12 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testCallDefaultMethods(JNIEnv* env) static void InvokeSpecificMethod(JNIEnv* env, jobject obj, const char* method) { jclass lambda_class = env->FindClass("LambdaInterface"); - assert(!env->ExceptionCheck()); - assert(lambda_class != nullptr); + CHECK(!env->ExceptionCheck()); + CHECK(lambda_class != nullptr); jmethodID method_id = env->GetMethodID(lambda_class, method, "()V"); - assert(!env->ExceptionCheck()); + CHECK(!env->ExceptionCheck()); env->CallVoidMethod(obj, method_id); - assert(!env->ExceptionCheck()); + CHECK(!env->ExceptionCheck()); } extern "C" JNIEXPORT void JNICALL Java_Main_testInvokeLambdaDefaultMethod( @@ -740,3 +739,6 @@ extern "C" JNIEXPORT void JNICALL Java_Main_testInvokeLambdaDefaultMethod( extern "C" JNIEXPORT void JNICALL Java_Main_testInvokeLambdaMethod(JNIEnv* e, jclass, jobject l) { InvokeSpecificMethod(e, l, "sayHi"); } + +} // namespace art + diff --git a/test/004-ReferenceMap/stack_walk_refmap_jni.cc b/test/004-ReferenceMap/stack_walk_refmap_jni.cc index 284e5544fb..5304590ad0 100644 --- a/test/004-ReferenceMap/stack_walk_refmap_jni.cc +++ b/test/004-ReferenceMap/stack_walk_refmap_jni.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "art_method-inl.h" #include "check_reference_map_visitor.h" #include "jni.h" diff --git a/test/004-StackWalk/stack_walk_jni.cc b/test/004-StackWalk/stack_walk_jni.cc index 51bb68f24a..420224dd21 100644 --- a/test/004-StackWalk/stack_walk_jni.cc +++ b/test/004-StackWalk/stack_walk_jni.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "art_method-inl.h" #include "check_reference_map_visitor.h" #include "jni.h" diff --git a/test/044-proxy/expected.txt b/test/044-proxy/expected.txt index be7023e49d..2a5f0b90db 100644 --- a/test/044-proxy/expected.txt +++ b/test/044-proxy/expected.txt @@ -95,3 +95,5 @@ Proxy narrowed invocation return type passed 5.8 JNI_OnLoad called callback +Found constructor. +Found constructors with 0 exceptions diff --git a/test/044-proxy/src/ConstructorProxy.java b/test/044-proxy/src/ConstructorProxy.java new file mode 100644 index 0000000000..95d150cbbd --- /dev/null +++ b/test/044-proxy/src/ConstructorProxy.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 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.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * Tests proxies when used with constructor methods. + */ +class ConstructorProxy implements InvocationHandler { + public static void main() { + try { + new ConstructorProxy().runTest(); + } catch (Exception e) { + System.out.println("Unexpected failure occured"); + e.printStackTrace(); + } + } + + public void runTest() throws Exception { + Class<?> proxyClass = Proxy.getProxyClass( + getClass().getClassLoader(), + new Class<?>[] { Runnable.class } + ); + Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); + System.out.println("Found constructor."); + // We used to crash when asking the exception types of the constructor, because the runtime was + // not using the non-proxy ArtMethod + Object[] exceptions = constructor.getExceptionTypes(); + System.out.println("Found constructors with " + exceptions.length + " exceptions"); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return args[0]; + } +} + diff --git a/test/044-proxy/src/Main.java b/test/044-proxy/src/Main.java index 1f23b95cf0..9dadb7c6ea 100644 --- a/test/044-proxy/src/Main.java +++ b/test/044-proxy/src/Main.java @@ -31,6 +31,7 @@ public class Main { NarrowingTest.main(null); FloatSelect.main(null); NativeProxy.main(args); + ConstructorProxy.main(); } // The following code maps from the actual proxy class names (eg $Proxy2) to their test output diff --git a/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc b/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc index 074c9b189e..c9110a905d 100644 --- a/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc +++ b/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc @@ -48,7 +48,8 @@ extern "C" JNIEXPORT void JNICALL Java_Main_destroyJavaVMAndExit(JNIEnv* env, jc vm->DetachCurrentThread(); // Open ourself again to make sure the native library does not get unloaded from // underneath us due to DestroyJavaVM. b/28406866 - dlopen(kIsDebugBuild ? "libarttestd.so" : "libarttest.so", RTLD_NOW); + void* handle = dlopen(kIsDebugBuild ? "libarttestd.so" : "libarttest.so", RTLD_NOW); + CHECK(handle != nullptr); vm->DestroyJavaVM(); vm_was_shutdown.store(true); // Give threads some time to get stuck in ExceptionCheck. diff --git a/test/536-checker-intrinsic-optimization/src/Main.java b/test/536-checker-intrinsic-optimization/src/Main.java index be666e94fa..15a9504acf 100644 --- a/test/536-checker-intrinsic-optimization/src/Main.java +++ b/test/536-checker-intrinsic-optimization/src/Main.java @@ -16,9 +16,69 @@ public class Main { + public static boolean doThrow = false; + + public static void assertIntEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + public static void assertBooleanEquals(boolean expected, boolean result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + public static void main(String[] args) { stringEqualsSame(); stringArgumentNotNull("Foo"); + + assertIntEquals(0, $opt$noinline$getStringLength("")); + assertIntEquals(3, $opt$noinline$getStringLength("abc")); + assertIntEquals(10, $opt$noinline$getStringLength("0123456789")); + + assertBooleanEquals(true, $opt$noinline$isStringEmpty("")); + assertBooleanEquals(false, $opt$noinline$isStringEmpty("abc")); + assertBooleanEquals(false, $opt$noinline$isStringEmpty("0123456789")); + } + + /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (before) + /// CHECK-DAG: <<Length:i\d+>> InvokeVirtual intrinsic:StringLength + /// CHECK-DAG: Return [<<Length>>] + + /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (after) + /// CHECK-DAG: <<String:l\d+>> ParameterValue + /// CHECK-DAG: <<NullCk:l\d+>> NullCheck [<<String>>] + /// CHECK-DAG: <<Length:i\d+>> ArrayLength [<<NullCk>>] is_string_length:true + /// CHECK-DAG: Return [<<Length>>] + + /// CHECK-START: int Main.$opt$noinline$getStringLength(java.lang.String) instruction_simplifier (after) + /// CHECK-NOT: InvokeVirtual intrinsic:StringLength + + static public int $opt$noinline$getStringLength(String s) { + if (doThrow) { throw new Error(); } + return s.length(); + } + + /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (before) + /// CHECK-DAG: <<IsEmpty:z\d+>> InvokeVirtual intrinsic:StringIsEmpty + /// CHECK-DAG: Return [<<IsEmpty>>] + + /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (after) + /// CHECK-DAG: <<String:l\d+>> ParameterValue + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<NullCk:l\d+>> NullCheck [<<String>>] + /// CHECK-DAG: <<Length:i\d+>> ArrayLength [<<NullCk>>] is_string_length:true + /// CHECK-DAG: <<IsEmpty:z\d+>> Equal [<<Length>>,<<Const0>>] + /// CHECK-DAG: Return [<<IsEmpty>>] + + /// CHECK-START: boolean Main.$opt$noinline$isStringEmpty(java.lang.String) instruction_simplifier (after) + /// CHECK-NOT: InvokeVirtual intrinsic:StringIsEmpty + + static public boolean $opt$noinline$isStringEmpty(String s) { + if (doThrow) { throw new Error(); } + return s.isEmpty(); } /// CHECK-START: boolean Main.stringEqualsSame() instruction_simplifier (before) diff --git a/test/566-polymorphic-inlining/src/Main.java b/test/566-polymorphic-inlining/src/Main.java index 286f0d996e..a59ce5b344 100644 --- a/test/566-polymorphic-inlining/src/Main.java +++ b/test/566-polymorphic-inlining/src/Main.java @@ -98,6 +98,7 @@ public class Main implements Itf { public static native void ensureJittedAndPolymorphicInline(); public void increment() { + field.getClass(); // null check to ensure we get an inlined frame in the CodeInfo counter++; } public static int counter = 0; diff --git a/test/570-checker-osr/osr.cc b/test/570-checker-osr/osr.cc index bd8f0a9520..2fa5800e5c 100644 --- a/test/570-checker-osr/osr.cc +++ b/test/570-checker-osr/osr.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "art_method.h" +#include "art_method-inl.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" #include "jit/profiling_info.h" diff --git a/test/586-checker-null-array-get/src/Main.java b/test/586-checker-null-array-get/src/Main.java index 332cfb0a53..e0782bc84d 100644 --- a/test/586-checker-null-array-get/src/Main.java +++ b/test/586-checker-null-array-get/src/Main.java @@ -14,10 +14,20 @@ * limitations under the License. */ +class Test1 { + int[] iarr; +} + +class Test2 { + float[] farr; +} + public class Main { public static Object[] getObjectArray() { return null; } public static long[] getLongArray() { return null; } public static Object getNull() { return null; } + public static Test1 getNullTest1() { return null; } + public static Test2 getNullTest2() { return null; } public static void main(String[] args) { try { @@ -26,13 +36,25 @@ public class Main { } catch (NullPointerException e) { // Expected. } + try { + bar(); + throw new Error("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected. + } + try { + test1(); + throw new Error("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected. + } } /// CHECK-START: void Main.foo() load_store_elimination (after) - /// CHECK-DAG: <<Null:l\d+>> NullConstant - /// CHECK-DAG: <<Check:l\d+>> NullCheck [<<Null>>] - /// CHECK-DAG: <<Get1:j\d+>> ArrayGet [<<Check>>,{{i\d+}}] - /// CHECK-DAG: <<Get2:l\d+>> ArrayGet [<<Check>>,{{i\d+}}] + /// CHECK-DAG: <<Null:l\d+>> NullConstant + /// CHECK-DAG: <<Check:l\d+>> NullCheck [<<Null>>] + /// CHECK-DAG: <<Get1:j\d+>> ArrayGet [<<Check>>,{{i\d+}}] + /// CHECK-DAG: <<Get2:l\d+>> ArrayGet [<<Check>>,{{i\d+}}] public static void foo() { longField = getLongArray()[0]; objectField = getObjectArray()[0]; @@ -56,7 +78,7 @@ public class Main { // elimination pass to add a HDeoptimize. Not having the bounds check helped // the load store elimination think it could merge two ArrayGet with different // types. - String[] array = ((String[])getNull()); + String[] array = (String[])getNull(); objectField = array[0]; objectField = array[1]; objectField = array[2]; @@ -68,6 +90,23 @@ public class Main { longField = longArray[3]; } + /// CHECK-START: float Main.test1() load_store_elimination (after) + /// CHECK-DAG: <<Null:l\d+>> NullConstant + /// CHECK-DAG: <<Check1:l\d+>> NullCheck [<<Null>>] + /// CHECK-DAG: <<FieldGet1:l\d+>> InstanceFieldGet [<<Check1>>] field_name:Test1.iarr + /// CHECK-DAG: <<Check2:l\d+>> NullCheck [<<FieldGet1>>] + /// CHECK-DAG: <<ArrayGet1:i\d+>> ArrayGet [<<Check2>>,{{i\d+}}] + /// CHECK-DAG: <<ArrayGet2:f\d+>> ArrayGet [<<Check2>>,{{i\d+}}] + /// CHECK-DAG: Return [<<ArrayGet2>>] + public static float test1() { + Test1 test1 = getNullTest1(); + Test2 test2 = getNullTest2();; + int[] iarr = test1.iarr; + float[] farr = test2.farr; + iarr[0] = iarr[1]; + return farr[0]; + } + public static long longField; public static Object objectField; } diff --git a/test/594-invoke-super/expected.txt b/test/594-invoke-super/expected.txt new file mode 100644 index 0000000000..de26026c99 --- /dev/null +++ b/test/594-invoke-super/expected.txt @@ -0,0 +1,7 @@ +new A +I am A's foo +new B +I am B's foo +new A +new B +passed diff --git a/test/594-invoke-super/info.txt b/test/594-invoke-super/info.txt new file mode 100644 index 0000000000..440d8b8c66 --- /dev/null +++ b/test/594-invoke-super/info.txt @@ -0,0 +1 @@ +Invoke-super on various references. diff --git a/test/594-invoke-super/smali/invoke-super.smali b/test/594-invoke-super/smali/invoke-super.smali new file mode 100644 index 0000000000..6f787dd170 --- /dev/null +++ b/test/594-invoke-super/smali/invoke-super.smali @@ -0,0 +1,31 @@ +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LZ; +.super LA; + +.method public constructor <init>()V +.registers 1 + invoke-direct {v0}, LA;-><init>()V + return-void +.end method + +.method public foo()V +.registers 3 + new-instance v0, LY; + invoke-direct {v0}, LY;-><init>()V + invoke-super {v0}, LY;->foo()V + return-void +.end method diff --git a/test/594-invoke-super/src/Main.java b/test/594-invoke-super/src/Main.java new file mode 100644 index 0000000000..53f2bbf67a --- /dev/null +++ b/test/594-invoke-super/src/Main.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +// +// Two classes A and B with method foo(). +// + +class A { + A() { System.out.println("new A"); } + + public void foo() { System.out.println("I am A's foo"); } + + // We previously used to invoke this method with a Y instance, due + // to invoke-super underspecified behavior. + public void bar() { System.out.println("I am A's bar"); } +} + +class B { + B() { System.out.println("new B"); } + + public void foo() { System.out.println("I am B's foo"); } +} + +// +// Two subclasses X and Y that call foo() on super. +// + +class X extends A { + public void foo() { super.foo(); } +} + +class Y extends B { + public void foo() { super.foo(); } +} + +// +// Driver class. +// + +public class Main { + + public static void main(String[] args) throws Exception { + // The normal stuff, X's super goes to A, Y's super goes to B. + new X().foo(); + new Y().foo(); + + // And now it gets interesting. + + // In bytecode, we define a class Z that is a subclass of A, and we call + // invoke-super on an instance of Y. + Class<?> z = Class.forName("Z"); + Method m = z.getMethod("foo"); + try { + m.invoke(z.newInstance()); + throw new Error("Expected InvocationTargetException"); + } catch (InvocationTargetException e) { + if (!(e.getCause() instanceof NoSuchMethodError)) { + throw new Error("Expected NoSuchMethodError"); + } + } + + System.out.println("passed"); + } +} diff --git a/test/594-load-string-regression/expected.txt b/test/594-load-string-regression/expected.txt new file mode 100644 index 0000000000..365b0e168f --- /dev/null +++ b/test/594-load-string-regression/expected.txt @@ -0,0 +1 @@ +String: "" diff --git a/test/594-load-string-regression/info.txt b/test/594-load-string-regression/info.txt new file mode 100644 index 0000000000..6a07ace81b --- /dev/null +++ b/test/594-load-string-regression/info.txt @@ -0,0 +1,2 @@ +Regression test for LoadString listing side effects when it doesn't have any +and triggering a DCHECK() failure when merging ClinitCheck into NewInstance. diff --git a/test/594-load-string-regression/src/Main.java b/test/594-load-string-regression/src/Main.java new file mode 100644 index 0000000000..0b9f7b52a1 --- /dev/null +++ b/test/594-load-string-regression/src/Main.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 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 { + static boolean doThrow = false; + + // Note: We're not doing checker tests as we cannot do them specifically for a non-PIC + // configuration. The check here would be "prepare_for_register_allocation (before)" + // CHECK: LoadClass + // CHECK-NEXT: ClinitCheck + // CHECK-NEXT: LoadString load_kind:BootImageAddress + // CHECK-NEXT: NewInstance + // and "prepare_for_register_allocation (after)" + // CHECK: LoadString + // CHECK-NEXT: NewInstance + // but the order of instructions for non-PIC mode is different. + public static int $noinline$test() { + if (doThrow) { throw new Error(); } + + int r = 0x12345678; + do { + // LICM pulls the LoadClass and ClinitCheck out of the loop, leaves NewInstance in the loop. + Helper h = new Helper(); + // For non-PIC mode, LICM pulls the boot image LoadString out of the loop. + // (For PIC mode, the LoadString can throw and will not be moved out of the loop.) + String s = ""; // Empty string is known to be in the boot image. + r = r ^ (r >> 5); + h.$noinline$printString(s); + // During DCE after inlining, the loop back-edge disappears and the pre-header is + // merged with the body, leaving consecutive LoadClass, ClinitCheck, LoadString + // and NewInstance in non-PIC mode. The prepare_for_register_allocation pass + // merges the LoadClass and ClinitCheck with the NewInstance and checks that + // there are no instructions with side effects in between. This check used to + // fail because LoadString was always listing SideEffects::CanTriggerGC() even + // when it doesn't really have any side effects, i.e. for direct references to + // boot image Strings or for Strings known to be in the dex cache. + } while ($inline$shouldContinue()); + return r; + } + + static boolean $inline$shouldContinue() { + return false; + } + + public static void main(String[] args) { + assertIntEquals(0x12345678 ^ (0x12345678 >> 5), $noinline$test()); + } + + public static void assertIntEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} + +class Helper { + static boolean doThrow = false; + + public void $noinline$printString(String s) { + if (doThrow) { throw new Error(); } + + System.out.println("String: \"" + s + "\""); + } +} diff --git a/test/595-profile-saving/profile-saving.cc b/test/595-profile-saving/profile-saving.cc index a7e6f8ba8c..0d26f454c6 100644 --- a/test/595-profile-saving/profile-saving.cc +++ b/test/595-profile-saving/profile-saving.cc @@ -16,6 +16,7 @@ #include "dex_file.h" +#include "art_method-inl.h" #include "jit/offline_profiling_info.h" #include "jit/profile_saver.h" #include "jni.h" diff --git a/test/596-app-images/app_images.cc b/test/596-app-images/app_images.cc index 11c0f424ba..a5bbf5fbaa 100644 --- a/test/596-app-images/app_images.cc +++ b/test/596-app-images/app_images.cc @@ -14,7 +14,6 @@ * limitations under the License. */ -#include <assert.h> #include <iostream> #include <pthread.h> #include <stdio.h> diff --git a/test/598-checker-irreducible-dominance/expected.txt b/test/598-checker-irreducible-dominance/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/598-checker-irreducible-dominance/expected.txt diff --git a/test/598-checker-irreducible-dominance/info.txt b/test/598-checker-irreducible-dominance/info.txt new file mode 100644 index 0000000000..8ca4e63be9 --- /dev/null +++ b/test/598-checker-irreducible-dominance/info.txt @@ -0,0 +1,2 @@ +Regression test for HGraphBuilder which would compute wrong dominance information +in the presence of irreducible loops.
\ No newline at end of file diff --git a/test/598-checker-irreducible-dominance/smali/IrreducibleLoop.smali b/test/598-checker-irreducible-dominance/smali/IrreducibleLoop.smali new file mode 100644 index 0000000000..4d8b5152fd --- /dev/null +++ b/test/598-checker-irreducible-dominance/smali/IrreducibleLoop.smali @@ -0,0 +1,52 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LIrreducibleLoop; +.super Ljava/lang/Object; + +# Test case in which `inner_back_edge` is not dominated by `inner_header` and +# causes `outer_back_edge` to not be dominated by `outer_header`. HGraphBuilder +# not do a fix-point iteration and would miss the path to `outer_back_edge` +# through `inner_back_edge` and incorrectly label the outer loop non-irreducible. + +## CHECK-START: int IrreducibleLoop.dominance(int) builder (after) +## CHECK: Add irreducible:true + +.method public static dominance(I)I + .registers 2 + + if-eqz p0, :outer_header + goto :inner_back_edge + + :outer_header + if-eqz p0, :inner_header + + :outer_branch_exit + if-eqz p0, :outer_merge + return p0 + + :inner_header + goto :outer_merge + + :inner_back_edge + goto :inner_header + + :outer_merge + if-eqz p0, :inner_back_edge + + :outer_back_edge + add-int/2addr p0, p0 + goto :outer_header + +.end method diff --git a/test/598-checker-irreducible-dominance/src/Main.java b/test/598-checker-irreducible-dominance/src/Main.java new file mode 100644 index 0000000000..38b2ab4384 --- /dev/null +++ b/test/598-checker-irreducible-dominance/src/Main.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016 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 { + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) { + // Nothing to run. This regression test merely makes sure the smali test + // case successfully compiles. + } +} diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk index 1550cc4403..21f8141c66 100644 --- a/test/Android.libarttest.mk +++ b/test/Android.libarttest.mk @@ -93,7 +93,12 @@ define build-libarttest include $(BUILD_SHARED_LIBRARY) else # host LOCAL_CLANG := $(ART_HOST_CLANG) - LOCAL_CFLAGS := $(ART_HOST_CFLAGS) $(ART_HOST_DEBUG_CFLAGS) + LOCAL_CFLAGS := $(ART_HOST_CFLAGS) + ifeq ($$(suffix),d) + LOCAL_CFLAGS += $(ART_HOST_DEBUG_CFLAGS) + else + LOCAL_CFLAGS += $(ART_HOST_NON_DEBUG_CFLAGS) + endif LOCAL_ASFLAGS := $(ART_HOST_ASFLAGS) LOCAL_LDLIBS := $(ART_HOST_LDLIBS) -ldl -lpthread LOCAL_IS_HOST_MODULE := true diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 01114b7690..aa45d40cb4 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -473,11 +473,14 @@ if [ "$HOST" = "n" ]; then LD_LIBRARY_PATH=$ANDROID_ROOT/$LIBRARY_DIRECTORY fi + PUBLIC_LIBS=libart.so:libartd.so + # Create a script with the command. The command can get longer than the longest # allowed adb command and there is no way to get the exit status from a adb shell # command. cmdline="cd $DEX_LOCATION && \ export ANDROID_DATA=$DEX_LOCATION && \ + export ANDROID_ADDITIONAL_PUBLIC_LIBRARIES=$PUBLIC_LIBS && \ export DEX_LOCATION=$DEX_LOCATION && \ export ANDROID_ROOT=$ANDROID_ROOT && \ $mkdir_cmdline && \ diff --git a/test/run-test b/test/run-test index 424c54f2c9..047e3fb606 100755 --- a/test/run-test +++ b/test/run-test @@ -148,6 +148,7 @@ while true; do elif [ "x$1" = "x--jvm" ]; then target_mode="no" runtime="jvm" + image_args="" prebuild_mode="no" NEED_DEX="false" USE_JACK="false" @@ -467,7 +468,7 @@ elif [ "$runtime" = "art" ]; then run_args="${run_args} --runtime-option -Djava.library.path=${ANDROID_HOST_OUT}/lib${suffix64}" else guess_target_arch_name - run_args="${run_args} --runtime-option -Djava.library.path=/data/art-test/${target_arch_name}" + run_args="${run_args} --runtime-option -Djava.library.path=/data/art-test/${target_arch_name}:/system/lib${suffix64}" run_args="${run_args} --boot /data/art-test/core${image_suffix}${pic_image_suffix}${multi_image_suffix}.art" fi if [ "$relocate" = "yes" ]; then diff --git a/tools/dmtracedump/tracedump.cc b/tools/dmtracedump/tracedump.cc index f70e2c2207..3afee6fdcb 100644 --- a/tools/dmtracedump/tracedump.cc +++ b/tools/dmtracedump/tracedump.cc @@ -512,10 +512,10 @@ int32_t compareUniqueExclusive(const void* a, const void* b) { void freeDataKeys(DataKeys* pKeys) { if (pKeys == nullptr) return; - free(pKeys->fileData); - free(pKeys->threads); - free(pKeys->methods); - free(pKeys); + delete[] pKeys->fileData; + delete[] pKeys->threads; + delete[] pKeys->methods; + delete pKeys; } /* @@ -822,8 +822,8 @@ void sortMethodList(DataKeys* pKeys) { DataKeys* parseKeys(FILE* fp, int32_t verbose) { int64_t offset; DataKeys* pKeys = new DataKeys(); - memset(pKeys, 0, sizeof(DataKeys)); if (pKeys == nullptr) return nullptr; + memset(pKeys, 0, sizeof(DataKeys)); /* * We load the entire file into memory. We do this, rather than memory- @@ -865,9 +865,13 @@ DataKeys* parseKeys(FILE* fp, int32_t verbose) { return nullptr; } - /* Reduce our allocation now that we know where the end of the key section is. */ - pKeys->fileData = reinterpret_cast<char*>(realloc(pKeys->fileData, offset)); - pKeys->fileLen = offset; + /* + * Although it is tempting to reduce our allocation now that we know where the + * end of the key section is, there is a pitfall. The method names and + * signatures in the method list contain pointers into the fileData area. + * Realloc or free will result in corruption. + */ + /* Leave fp pointing to the beginning of the data section. */ fseek(fp, offset, SEEK_SET); @@ -2607,7 +2611,7 @@ int32_t main(int32_t argc, char** argv) { if (gOptions.graphFileName != nullptr) { createInclusiveProfileGraphNew(dataKeys); } - free(methods); + delete[] methods; } freeDataKeys(dataKeys); |