diff options
60 files changed, 2825 insertions, 239 deletions
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 16c9f10cce..406892e499 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -49,7 +49,6 @@ #include "globals.h" #include "image.h" #include "imt_conflict_table.h" -#include "intern_table.h" #include "jni_internal.h" #include "linear_alloc.h" #include "lock_word.h" diff --git a/compiler/image_writer.h b/compiler/image_writer.h index 3db4fab128..5e2db7d8f7 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -36,6 +36,7 @@ #include "class_table.h" #include "driver/compiler_driver.h" #include "image.h" +#include "intern_table.h" #include "lock_word.h" #include "mem_map.h" #include "mirror/dex_cache.h" diff --git a/compiler/optimizing/code_generator_vector_mips64.cc b/compiler/optimizing/code_generator_vector_mips64.cc index af9e89e791..0395db1df9 100644 --- a/compiler/optimizing/code_generator_vector_mips64.cc +++ b/compiler/optimizing/code_generator_vector_mips64.cc @@ -495,7 +495,60 @@ void LocationsBuilderMIPS64::VisitVecMin(HVecMin* instruction) { } void InstructionCodeGeneratorMIPS64::VisitVecMin(HVecMin* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + VectorRegister lhs = VectorRegisterFrom(locations->InAt(0)); + VectorRegister rhs = VectorRegisterFrom(locations->InAt(1)); + VectorRegister dst = VectorRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(16u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Min_uB(dst, lhs, rhs); + } else { + __ Min_sB(dst, lhs, rhs); + } + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(8u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Min_uH(dst, lhs, rhs); + } else { + __ Min_sH(dst, lhs, rhs); + } + break; + case Primitive::kPrimInt: + DCHECK_EQ(4u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Min_uW(dst, lhs, rhs); + } else { + __ Min_sW(dst, lhs, rhs); + } + break; + case Primitive::kPrimLong: + DCHECK_EQ(2u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Min_uD(dst, lhs, rhs); + } else { + __ Min_sD(dst, lhs, rhs); + } + break; + // When one of arguments is NaN, fmin.df returns other argument, but Java expects a NaN value. + // TODO: Fix min(x, NaN) cases for float and double. + case Primitive::kPrimFloat: + DCHECK_EQ(4u, instruction->GetVectorLength()); + DCHECK(!instruction->IsUnsigned()); + __ FminW(dst, lhs, rhs); + break; + case Primitive::kPrimDouble: + DCHECK_EQ(2u, instruction->GetVectorLength()); + DCHECK(!instruction->IsUnsigned()); + __ FminD(dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderMIPS64::VisitVecMax(HVecMax* instruction) { @@ -503,7 +556,60 @@ void LocationsBuilderMIPS64::VisitVecMax(HVecMax* instruction) { } void InstructionCodeGeneratorMIPS64::VisitVecMax(HVecMax* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = instruction->GetLocations(); + VectorRegister lhs = VectorRegisterFrom(locations->InAt(0)); + VectorRegister rhs = VectorRegisterFrom(locations->InAt(1)); + VectorRegister dst = VectorRegisterFrom(locations->Out()); + switch (instruction->GetPackedType()) { + case Primitive::kPrimByte: + DCHECK_EQ(16u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Max_uB(dst, lhs, rhs); + } else { + __ Max_sB(dst, lhs, rhs); + } + break; + case Primitive::kPrimChar: + case Primitive::kPrimShort: + DCHECK_EQ(8u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Max_uH(dst, lhs, rhs); + } else { + __ Max_sH(dst, lhs, rhs); + } + break; + case Primitive::kPrimInt: + DCHECK_EQ(4u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Max_uW(dst, lhs, rhs); + } else { + __ Max_sW(dst, lhs, rhs); + } + break; + case Primitive::kPrimLong: + DCHECK_EQ(2u, instruction->GetVectorLength()); + if (instruction->IsUnsigned()) { + __ Max_uD(dst, lhs, rhs); + } else { + __ Max_sD(dst, lhs, rhs); + } + break; + // When one of arguments is NaN, fmax.df returns other argument, but Java expects a NaN value. + // TODO: Fix max(x, NaN) cases for float and double. + case Primitive::kPrimFloat: + DCHECK_EQ(4u, instruction->GetVectorLength()); + DCHECK(!instruction->IsUnsigned()); + __ FmaxW(dst, lhs, rhs); + break; + case Primitive::kPrimDouble: + DCHECK_EQ(2u, instruction->GetVectorLength()); + DCHECK(!instruction->IsUnsigned()); + __ FmaxD(dst, lhs, rhs); + break; + default: + LOG(FATAL) << "Unsupported SIMD type"; + UNREACHABLE(); + } } void LocationsBuilderMIPS64::VisitVecAnd(HVecAnd* instruction) { diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index 68ee272d25..9c8a632d40 100644 --- a/compiler/optimizing/loop_optimization.cc +++ b/compiler/optimizing/loop_optimization.cc @@ -1085,23 +1085,23 @@ bool HLoopOptimization::TrySetVectorType(Primitive::Type type, uint64_t* restric switch (type) { case Primitive::kPrimBoolean: case Primitive::kPrimByte: - *restrictions |= kNoDiv | kNoMinMax; + *restrictions |= kNoDiv; return TrySetVectorLength(16); case Primitive::kPrimChar: case Primitive::kPrimShort: - *restrictions |= kNoDiv | kNoMinMax | kNoStringCharAt; + *restrictions |= kNoDiv | kNoStringCharAt; return TrySetVectorLength(8); case Primitive::kPrimInt: - *restrictions |= kNoDiv | kNoMinMax; + *restrictions |= kNoDiv; return TrySetVectorLength(4); case Primitive::kPrimLong: - *restrictions |= kNoDiv | kNoMinMax; + *restrictions |= kNoDiv; return TrySetVectorLength(2); case Primitive::kPrimFloat: - *restrictions |= kNoMinMax; + *restrictions |= kNoMinMax; // min/max(x, NaN) return TrySetVectorLength(4); case Primitive::kPrimDouble: - *restrictions |= kNoMinMax; + *restrictions |= kNoMinMax; // min/max(x, NaN) return TrySetVectorLength(2); default: break; diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc index e750ede8fa..ec3481b622 100644 --- a/patchoat/patchoat.cc +++ b/patchoat/patchoat.cc @@ -40,6 +40,7 @@ #include "elf_file_impl.h" #include "gc/space/image_space.h" #include "image-inl.h" +#include "intern_table.h" #include "mirror/dex_cache.h" #include "mirror/executable.h" #include "mirror/object-inl.h" diff --git a/runtime/Android.bp b/runtime/Android.bp index d1e124fd35..26e52e012e 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -245,7 +245,6 @@ cc_defaults { "entrypoints/quick/quick_entrypoints_enum.cc", "entrypoints/quick/quick_field_entrypoints.cc", "entrypoints/quick/quick_fillarray_entrypoints.cc", - "entrypoints/quick/quick_instrumentation_entrypoints.cc", "entrypoints/quick/quick_jni_entrypoints.cc", "entrypoints/quick/quick_lock_entrypoints.cc", "entrypoints/quick/quick_math_entrypoints.cc", diff --git a/runtime/arch/arch_test.cc b/runtime/arch/arch_test.cc index ce575f72c1..838ae40838 100644 --- a/runtime/arch/arch_test.cc +++ b/runtime/arch/arch_test.cc @@ -20,9 +20,27 @@ #include "base/callee_save_type.h" #include "common_runtime_test.h" #include "quick/quick_method_frame_info.h" -// Common tests are declared next to the constants. -#define ADD_TEST_EQ(x, y) EXPECT_EQ(x, y); -#include "asm_support.h" + + +// asm_support.h declares tests next to the #defines. We use asm_support_check.h to (safely) +// generate CheckAsmSupportOffsetsAndSizes using gtest's EXPECT for the tests. We also use the +// RETURN_TYPE, HEADER and FOOTER defines from asm_support_check.h to try to ensure that any +// tests are actually generated. + +// Let CheckAsmSupportOffsetsAndSizes return a size_t (the count). +#define ASM_SUPPORT_CHECK_RETURN_TYPE size_t + +// Declare the counter that will be updated per test. +#define ASM_SUPPORT_CHECK_HEADER size_t count = 0; + +// Use EXPECT_EQ for tests, and increment the counter. +#define ADD_TEST_EQ(x, y) EXPECT_EQ(x, y); count++; + +// Return the counter at the end of CheckAsmSupportOffsetsAndSizes. +#define ASM_SUPPORT_CHECK_FOOTER return count; + +// Generate CheckAsmSupportOffsetsAndSizes(). +#include "asm_support_check.h" namespace art { @@ -58,7 +76,8 @@ class ArchTest : public CommonRuntimeTest { }; TEST_F(ArchTest, CheckCommonOffsetsAndSizes) { - CheckAsmSupportOffsetsAndSizes(); + size_t test_count = CheckAsmSupportOffsetsAndSizes(); + EXPECT_GT(test_count, 0u); } // Grab architecture specific constants. diff --git a/runtime/arch/arm/entrypoints_init_arm.cc b/runtime/arch/arm/entrypoints_init_arm.cc index 919b0afc40..8a8d26466f 100644 --- a/runtime/arch/arm/entrypoints_init_arm.cc +++ b/runtime/arch/arm/entrypoints_init_arm.cc @@ -18,6 +18,7 @@ #include <string.h> #include "arch/arm/asm_support_arm.h" +#include "base/bit_utils.h" #include "entrypoints/jni/jni_entrypoints.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "entrypoints/quick/quick_default_externs.h" diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index 31a7f6ae8e..307f9f0893 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -1630,8 +1630,10 @@ ENTRY art_quick_instrumentation_entry @ preserve r0 (not normally an arg) knowing there is a spare slot in kSaveRefsAndArgs. str r0, [sp, #4] mov r2, r9 @ pass Thread::Current - mov r3, lr @ pass LR - blx artInstrumentationMethodEntryFromCode @ (Method*, Object*, Thread*, LR) + mov r3, sp @ pass SP + blx artInstrumentationMethodEntryFromCode @ (Method*, Object*, Thread*, SP) + cbz r0, .Ldeliver_instrumentation_entry_exception + @ Deliver exception if we got nullptr as function. mov r12, r0 @ r12 holds reference to code ldr r0, [sp, #4] @ restore r0 RESTORE_SAVE_REFS_AND_ARGS_FRAME @@ -1647,19 +1649,13 @@ art_quick_instrumentation_exit: .cfi_adjust_cfa_offset 8 .cfi_rel_offset r0, 0 .cfi_rel_offset r1, 4 + mov r2, sp @ store gpr_res pointer. vpush {d0} @ save fp return value .cfi_adjust_cfa_offset 8 - sub sp, #8 @ space for return value argument. Note: AAPCS stack alignment is 8B, no - @ need to align by 16. - .cfi_adjust_cfa_offset 8 - vstr d0, [sp] @ d0 -> [sp] for fpr_res - mov r2, r0 @ pass return value as gpr_res - mov r3, r1 - mov r0, r9 @ pass Thread::Current + mov r3, sp @ store fpr_res pointer mov r1, r12 @ pass SP - blx artInstrumentationMethodExitFromCode @ (Thread*, SP, gpr_res, fpr_res) - add sp, #8 - .cfi_adjust_cfa_offset -8 + mov r0, r9 @ pass Thread::Current + blx artInstrumentationMethodExitFromCode @ (Thread*, SP, gpr_res*, fpr_res*) mov r2, r0 @ link register saved by instrumentation mov lr, r1 @ r1 is holding link register if we're to bounce to deoptimize @@ -1669,9 +1665,16 @@ art_quick_instrumentation_exit: .cfi_adjust_cfa_offset -8 .cfi_restore r0 .cfi_restore r1 - add sp, #32 @ remove callee save frame - .cfi_adjust_cfa_offset -32 - bx r2 @ return + RESTORE_SAVE_REFS_ONLY_FRAME + cbz r2, .Ldo_deliver_instrumentation_exception + @ Deliver exception if we got nullptr as function. + bx r2 @ Otherwise, return +.Ldeliver_instrumentation_entry_exception: + @ Deliver exception for art_quick_instrumentation_entry placed after + @ art_quick_instrumentation_exit so that the fallthrough works. + RESTORE_SAVE_REFS_AND_ARGS_FRAME +.Ldo_deliver_instrumentation_exception: + DELIVER_PENDING_EXCEPTION END art_quick_instrumentation_entry /* diff --git a/runtime/arch/arm64/entrypoints_init_arm64.cc b/runtime/arch/arm64/entrypoints_init_arm64.cc index 610cdee683..9bbcef307e 100644 --- a/runtime/arch/arm64/entrypoints_init_arm64.cc +++ b/runtime/arch/arm64/entrypoints_init_arm64.cc @@ -18,6 +18,7 @@ #include <string.h> #include "arch/arm64/asm_support_arm64.h" +#include "base/bit_utils.h" #include "entrypoints/jni/jni_entrypoints.h" #include "entrypoints/quick/quick_alloc_entrypoints.h" #include "entrypoints/quick/quick_default_externs.h" diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index 18015b572e..c9ead54d35 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -2168,15 +2168,19 @@ ENTRY art_quick_instrumentation_entry mov x20, x0 // Preserve method reference in a callee-save. mov x2, xSELF - mov x3, xLR - bl artInstrumentationMethodEntryFromCode // (Method*, Object*, Thread*, LR) + mov x3, sp // Pass SP + bl artInstrumentationMethodEntryFromCode // (Method*, Object*, Thread*, SP) mov xIP0, x0 // x0 = result of call. mov x0, x20 // Reload method reference. RESTORE_SAVE_REFS_AND_ARGS_FRAME // Note: will restore xSELF + cbz xIP0, 1f // Deliver the pending exception if method is null. adr xLR, art_quick_instrumentation_exit br xIP0 // Tail-call method with lr set to art_quick_instrumentation_exit. + +1: + DELIVER_PENDING_EXCEPTION END art_quick_instrumentation_entry .extern artInstrumentationMethodExitFromCode @@ -2185,30 +2189,28 @@ ENTRY art_quick_instrumentation_exit SETUP_SAVE_REFS_ONLY_FRAME - // We need to save x0 and d0. We could use a callee-save from SETUP_REF_ONLY, but then - // we would need to fully restore it. As there are a lot of callee-save registers, it seems - // easier to have an extra small stack area. - str x0, [sp, #-16]! // Save integer result. .cfi_adjust_cfa_offset 16 - str d0, [sp, #8] // Save floating-point result. + str d0, [sp, #8] // Save floating-point result. + add x3, sp, #8 // Pass floating-point result pointer. + mov x2, sp // Pass integer result pointer. add x1, sp, #16 // Pass SP. - mov x2, x0 // Pass integer result. - fmov x3, d0 // Pass floating-point result. mov x0, xSELF // Pass Thread. - bl artInstrumentationMethodExitFromCode // (Thread*, SP, gpr_res, fpr_res) + bl artInstrumentationMethodExitFromCode // (Thread*, SP, gpr_res*, fpr_res*) mov xIP0, x0 // Return address from instrumentation call. mov xLR, x1 // r1 is holding link register if we're to bounce to deoptimize ldr d0, [sp, #8] // Restore floating-point result. ldr x0, [sp], #16 // Restore integer result, and drop stack area. - .cfi_adjust_cfa_offset 16 - - POP_SAVE_REFS_ONLY_FRAME + .cfi_adjust_cfa_offset -16 + RESTORE_SAVE_REFS_ONLY_FRAME + cbz xIP0, 1f // Handle error br xIP0 // Tail-call out. +1: + DELIVER_PENDING_EXCEPTION END art_quick_instrumentation_exit /* diff --git a/runtime/arch/x86/instruction_set_features_x86.cc b/runtime/arch/x86/instruction_set_features_x86.cc index 578812297a..cc0bdf2a29 100644 --- a/runtime/arch/x86/instruction_set_features_x86.cc +++ b/runtime/arch/x86/instruction_set_features_x86.cc @@ -33,23 +33,28 @@ using android::base::StringPrintf; static constexpr const char* x86_known_variants[] = { "atom", + "sandybridge", "silvermont", }; static constexpr const char* x86_variants_with_ssse3[] = { "atom", + "sandybridge", "silvermont", }; static constexpr const char* x86_variants_with_sse4_1[] = { + "sandybridge", "silvermont", }; static constexpr const char* x86_variants_with_sse4_2[] = { + "sandybridge", "silvermont", }; static constexpr const char* x86_variants_with_popcnt[] = { + "sandybridge", "silvermont", }; diff --git a/runtime/arch/x86/instruction_set_features_x86_test.cc b/runtime/arch/x86/instruction_set_features_x86_test.cc index 7e6ad3ecbf..c67b4ddfe0 100644 --- a/runtime/arch/x86/instruction_set_features_x86_test.cc +++ b/runtime/arch/x86/instruction_set_features_x86_test.cc @@ -69,6 +69,43 @@ TEST(X86InstructionSetFeaturesTest, X86FeaturesFromAtomVariant) { EXPECT_FALSE(x86_features->Equals(x86_default_features.get())); } +TEST(X86InstructionSetFeaturesTest, X86FeaturesFromSandybridgeVariant) { + // Build features for a 32-bit x86 sandybridge processor. + std::string error_msg; + std::unique_ptr<const InstructionSetFeatures> x86_features( + InstructionSetFeatures::FromVariant(kX86, "sandybridge", &error_msg)); + ASSERT_TRUE(x86_features.get() != nullptr) << error_msg; + EXPECT_EQ(x86_features->GetInstructionSet(), kX86); + EXPECT_TRUE(x86_features->Equals(x86_features.get())); + EXPECT_STREQ("ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt", + x86_features->GetFeatureString().c_str()); + EXPECT_EQ(x86_features->AsBitmap(), 39U); + + // Build features for a 32-bit x86 default processor. + std::unique_ptr<const InstructionSetFeatures> x86_default_features( + InstructionSetFeatures::FromVariant(kX86, "default", &error_msg)); + ASSERT_TRUE(x86_default_features.get() != nullptr) << error_msg; + EXPECT_EQ(x86_default_features->GetInstructionSet(), kX86); + EXPECT_TRUE(x86_default_features->Equals(x86_default_features.get())); + EXPECT_STREQ("-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-popcnt", + x86_default_features->GetFeatureString().c_str()); + EXPECT_EQ(x86_default_features->AsBitmap(), 0U); + + // Build features for a 64-bit x86-64 sandybridge processor. + std::unique_ptr<const InstructionSetFeatures> x86_64_features( + InstructionSetFeatures::FromVariant(kX86_64, "sandybridge", &error_msg)); + ASSERT_TRUE(x86_64_features.get() != nullptr) << error_msg; + EXPECT_EQ(x86_64_features->GetInstructionSet(), kX86_64); + EXPECT_TRUE(x86_64_features->Equals(x86_64_features.get())); + EXPECT_STREQ("ssse3,sse4.1,sse4.2,-avx,-avx2,popcnt", + x86_64_features->GetFeatureString().c_str()); + EXPECT_EQ(x86_64_features->AsBitmap(), 39U); + + EXPECT_FALSE(x86_64_features->Equals(x86_features.get())); + EXPECT_FALSE(x86_64_features->Equals(x86_default_features.get())); + EXPECT_FALSE(x86_features->Equals(x86_default_features.get())); +} + TEST(X86InstructionSetFeaturesTest, X86FeaturesFromSilvermontVariant) { // Build features for a 32-bit x86 silvermont processor. std::string error_msg; diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index 2222f5cc3b..4fb3fe5128 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -1948,17 +1948,23 @@ ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMeth DEFINE_FUNCTION art_quick_instrumentation_entry SETUP_SAVE_REFS_AND_ARGS_FRAME ebx, edx PUSH eax // Save eax which will be clobbered by the callee-save method. - subl LITERAL(12), %esp // Align stack. - CFI_ADJUST_CFA_OFFSET(12) - pushl FRAME_SIZE_SAVE_REFS_AND_ARGS-4+16(%esp) // Pass LR. - CFI_ADJUST_CFA_OFFSET(4) + subl LITERAL(16), %esp // Align stack (12 bytes) and reserve space for the SP argument + CFI_ADJUST_CFA_OFFSET(16) // (4 bytes). We lack the scratch registers to calculate the SP + // right now, so we will just fill it in later. pushl %fs:THREAD_SELF_OFFSET // Pass Thread::Current(). CFI_ADJUST_CFA_OFFSET(4) PUSH ecx // Pass receiver. PUSH eax // Pass Method*. - call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, LR) + leal 32(%esp), %eax // Put original SP into eax + movl %eax, 12(%esp) // set SP + call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, SP) + addl LITERAL(28), %esp // Pop arguments upto saved Method*. CFI_ADJUST_CFA_OFFSET(-28) + + testl %eax, %eax + jz 1f // Test for null return (indicating exception) and handle it. + movl 60(%esp), %edi // Restore edi. movl %eax, 60(%esp) // Place code* over edi, just under return pc. movl SYMBOL(art_quick_instrumentation_exit)@GOT(%ebx), %ebx @@ -1980,6 +1986,12 @@ DEFINE_FUNCTION art_quick_instrumentation_entry addl LITERAL(60), %esp // Wind stack back upto code*. CFI_ADJUST_CFA_OFFSET(-60) ret // Call method (and pop). +1: + // Make caller handle exception + addl LITERAL(4), %esp + CFI_ADJUST_CFA_OFFSET(-4) + RESTORE_SAVE_REFS_AND_ARGS_FRAME + DELIVER_PENDING_EXCEPTION END_FUNCTION art_quick_instrumentation_entry DEFINE_FUNCTION art_quick_instrumentation_exit @@ -1992,18 +2004,19 @@ DEFINE_FUNCTION art_quick_instrumentation_exit movq %xmm0, (%esp) PUSH edx // Save gpr return value. PUSH eax - subl LITERAL(16), %esp // Align stack - CFI_ADJUST_CFA_OFFSET(16) - movq %xmm0, (%esp) // Pass float return value. - PUSH edx // Pass gpr return value. - PUSH eax + leal 8(%esp), %eax // Get pointer to fpr_result + movl %esp, %edx // Get pointer to gpr_result + PUSH eax // Pass fpr_result + PUSH edx // Pass gpr_result PUSH ecx // Pass SP. pushl %fs:THREAD_SELF_OFFSET // Pass Thread::Current. CFI_ADJUST_CFA_OFFSET(4) - call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_result, fpr_result) + call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_result*, fpr_result*) + testl %eax, %eax // Check if we returned error. + jz 1f mov %eax, %ecx // Move returned link register. - addl LITERAL(32), %esp // Pop arguments. - CFI_ADJUST_CFA_OFFSET(-32) + addl LITERAL(16), %esp // Pop arguments. + CFI_ADJUST_CFA_OFFSET(-16) movl %edx, %ebx // Move returned link register for deopt // (ebx is pretending to be our LR). POP eax // Restore gpr return value. @@ -2015,6 +2028,11 @@ DEFINE_FUNCTION art_quick_instrumentation_exit addl LITERAL(4), %esp // Remove fake return pc. CFI_ADJUST_CFA_OFFSET(-4) jmp *%ecx // Return. +1: + addl LITERAL(32), %esp + CFI_ADJUST_CFA_OFFSET(-32) + RESTORE_SAVE_REFS_ONLY_FRAME + DELIVER_PENDING_EXCEPTION END_FUNCTION art_quick_instrumentation_exit /* diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 41651d8f1a..46d4f416a5 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -1920,19 +1920,24 @@ DEFINE_FUNCTION art_quick_instrumentation_entry movq %rdi, %r12 // Preserve method pointer in a callee-save. movq %gs:THREAD_SELF_OFFSET, %rdx // Pass thread. - movq FRAME_SIZE_SAVE_REFS_AND_ARGS-8(%rsp), %rcx // Pass return PC. + movq %rsp, %rcx // Pass SP. - call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, LR) + call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, SP) // %rax = result of call. - movq %r12, %rdi // Reload method pointer. + testq %rax, %rax + jz 1f + movq %r12, %rdi // Reload method pointer. leaq art_quick_instrumentation_exit(%rip), %r12 // Set up return through instrumentation movq %r12, FRAME_SIZE_SAVE_REFS_AND_ARGS-8(%rsp) // exit. RESTORE_SAVE_REFS_AND_ARGS_FRAME jmp *%rax // Tail call to intended method. +1: + RESTORE_SAVE_REFS_AND_ARGS_FRAME + DELIVER_PENDING_EXCEPTION #endif // __APPLE__ END_FUNCTION art_quick_instrumentation_entry @@ -1948,15 +1953,16 @@ DEFINE_FUNCTION art_quick_instrumentation_exit movq %rsp, %rsi // Pass SP. PUSH rax // Save integer result. + movq %rsp, %rdx // Pass integer result pointer. + subq LITERAL(8), %rsp // Save floating-point result. CFI_ADJUST_CFA_OFFSET(8) movq %xmm0, (%rsp) + movq %rsp, %rcx // Pass floating-point result pointer. movq %gs:THREAD_SELF_OFFSET, %rdi // Pass Thread. - movq %rax, %rdx // Pass integer result. - movq %xmm0, %rcx // Pass floating-point result. - call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_res, fpr_res) + call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_res*, fpr_res*) movq %rax, %rdi // Store return PC movq %rdx, %rsi // Store second return PC in hidden arg. @@ -1968,9 +1974,15 @@ DEFINE_FUNCTION art_quick_instrumentation_exit RESTORE_SAVE_REFS_ONLY_FRAME + testq %rdi, %rdi // Check if we have a return-pc to go to. If we don't then there was + // an exception + jz 1f + addq LITERAL(8), %rsp // Drop fake return pc. jmp *%rdi // Return. +1: + DELIVER_PENDING_EXCEPTION END_FUNCTION art_quick_instrumentation_exit /* diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 7de8916ad5..d591e0992c 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -664,7 +664,9 @@ const OatQuickMethodHeader* ArtMethod::GetOatQuickMethodHeader(uintptr_t pc) { } if (existing_entry_point == GetQuickInstrumentationEntryPoint()) { // We are running the generic jni stub, but the method is being instrumented. - DCHECK_EQ(pc, 0u) << "Should be a downcall"; + // NB We would normally expect the pc to be zero but we can have non-zero pc's if + // instrumentation is installed or removed during the call which is using the generic jni + // trampoline. DCHECK(IsNative()); return nullptr; } diff --git a/runtime/asm_support.h b/runtime/asm_support.h index f8096feeb9..44c0661e3f 100644 --- a/runtime/asm_support.h +++ b/runtime/asm_support.h @@ -17,24 +17,6 @@ #ifndef ART_RUNTIME_ASM_SUPPORT_H_ #define ART_RUNTIME_ASM_SUPPORT_H_ -#if defined(__cplusplus) -#include "art_method.h" -#include "base/bit_utils.h" -#include "base/callee_save_type.h" -#include "gc/accounting/card_table.h" -#include "gc/allocator/rosalloc.h" -#include "gc/heap.h" -#include "jit/jit.h" -#include "lock_word.h" -#include "mirror/class.h" -#include "mirror/dex_cache.h" -#include "mirror/string.h" -#include "utils/dex_cache_arrays_layout.h" -#include "runtime.h" -#include "stack.h" -#include "thread.h" -#endif - #include "read_barrier_c.h" #if defined(__arm__) || defined(__mips__) @@ -51,14 +33,10 @@ #define SUSPEND_CHECK_INTERVAL 96 #endif -#if defined(__cplusplus) - +// To generate tests related to the constants in this header, either define ADD_TEST_EQ before +// including, or use asm_support_check.h. #ifndef ADD_TEST_EQ // Allow #include-r to replace with their own. -#define ADD_TEST_EQ(x, y) CHECK_EQ(x, y); -#endif - -static inline void CheckAsmSupportOffsetsAndSizes() { -#else +#define DEFINED_ADD_TEST_EQ 1 #define ADD_TEST_EQ(x, y) #endif @@ -76,6 +54,7 @@ ADD_TEST_EQ(static_cast<size_t>(1U << POINTER_SIZE_SHIFT), // Export new defines (for assembly use) by editing cpp-define-generator def files. #define DEFINE_CHECK_EQ ADD_TEST_EQ #include "asm_support_gen.h" +#undef DEFINE_CHECK_EQ // Offset of field Thread::tlsPtr_.exception. #define THREAD_EXCEPTION_OFFSET (THREAD_CARD_TABLE_OFFSET + __SIZEOF_POINTER__) @@ -252,8 +231,9 @@ ADD_TEST_EQ(MIRROR_STRING_VALUE_OFFSET, art::mirror::String::ValueOffset().Int32 #define STRING_COMPRESSION_FEATURE 1 ADD_TEST_EQ(STRING_COMPRESSION_FEATURE, art::mirror::kUseStringCompression); -#if defined(__cplusplus) -} // End of CheckAsmSupportOffsets. +#ifdef DEFINED_ADD_TEST_EQ +#undef ADD_TEST_EQ +#undef DEFINED_ADD_TEST_EQ #endif #endif // ART_RUNTIME_ASM_SUPPORT_H_ diff --git a/runtime/asm_support_check.h b/runtime/asm_support_check.h new file mode 100644 index 0000000000..cc6a578313 --- /dev/null +++ b/runtime/asm_support_check.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_ASM_SUPPORT_CHECK_H_ +#define ART_RUNTIME_ASM_SUPPORT_CHECK_H_ + +#include "art_method.h" +#include "base/bit_utils.h" +#include "base/callee_save_type.h" +#include "gc/accounting/card_table.h" +#include "gc/allocator/rosalloc.h" +#include "gc/heap.h" +#include "jit/jit.h" +#include "lock_word.h" +#include "mirror/class.h" +#include "mirror/dex_cache.h" +#include "mirror/string.h" +#include "utils/dex_cache_arrays_layout.h" +#include "runtime.h" +#include "stack.h" +#include "thread.h" + +#ifndef ADD_TEST_EQ +#define ADD_TEST_EQ(x, y) CHECK_EQ(x, y); +#endif + +#ifndef ASM_SUPPORT_CHECK_RETURN_TYPE +#define ASM_SUPPORT_CHECK_RETURN_TYPE void +#endif + +// Prepare for re-include of asm_support.h. +#ifdef ART_RUNTIME_ASM_SUPPORT_H_ +#undef ART_RUNTIME_ASM_SUPPORT_H_ +#endif + +namespace art { + +static inline ASM_SUPPORT_CHECK_RETURN_TYPE CheckAsmSupportOffsetsAndSizes() { +#ifdef ASM_SUPPORT_CHECK_HEADER + ASM_SUPPORT_CHECK_HEADER +#endif + +#include "asm_support.h" + +#ifdef ASM_SUPPORT_CHECK_FOOTER + ASM_SUPPORT_CHECK_FOOTER +#endif +} + +} // namespace art + +#endif // ART_RUNTIME_ASM_SUPPORT_CHECK_H_ diff --git a/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc b/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc deleted file mode 100644 index 0d2f6d02b6..0000000000 --- a/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -#include "art_method.h" -#include "base/callee_save_type.h" -#include "base/enums.h" -#include "callee_save_frame.h" -#include "entrypoints/runtime_asm_entrypoints.h" -#include "instrumentation.h" -#include "mirror/object-inl.h" -#include "runtime.h" -#include "thread-current-inl.h" - -namespace art { - -extern "C" const void* artInstrumentationMethodEntryFromCode(ArtMethod* method, - mirror::Object* this_object, - Thread* self, - uintptr_t lr) - REQUIRES_SHARED(Locks::mutator_lock_) { - // Instrumentation changes the stack. Thus, when exiting, the stack cannot be verified, so skip - // that part. - ScopedQuickEntrypointChecks sqec(self, kIsDebugBuild, false); - instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); - const void* result; - if (instrumentation->IsDeoptimized(method)) { - result = GetQuickToInterpreterBridge(); - } else { - result = instrumentation->GetQuickCodeFor(method, kRuntimePointerSize); - DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(result)); - } - bool interpreter_entry = (result == GetQuickToInterpreterBridge()); - instrumentation->PushInstrumentationStackFrame(self, method->IsStatic() ? nullptr : this_object, - method, lr, interpreter_entry); - CHECK(result != nullptr) << method->PrettyMethod(); - return result; -} - -extern "C" TwoWordReturn artInstrumentationMethodExitFromCode(Thread* self, ArtMethod** sp, - uint64_t gpr_result, - uint64_t fpr_result) - REQUIRES_SHARED(Locks::mutator_lock_) { - // Instrumentation exit stub must not be entered with a pending exception. - CHECK(!self->IsExceptionPending()) << "Enter instrumentation exit stub with pending exception " - << self->GetException()->Dump(); - // Compute address of return PC and sanity check that it currently holds 0. - size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, CalleeSaveType::kSaveRefsOnly); - uintptr_t* return_pc = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) + - return_pc_offset); - CHECK_EQ(*return_pc, 0U); - - // Pop the frame filling in the return pc. The low half of the return value is 0 when - // deoptimization shouldn't be performed with the high-half having the return address. When - // deoptimization should be performed the low half is zero and the high-half the address of the - // deoptimization entry point. - instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); - TwoWordReturn return_or_deoptimize_pc = instrumentation->PopInstrumentationStackFrame( - self, return_pc, gpr_result, fpr_result); - return return_or_deoptimize_pc; -} - -} // namespace art diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 6fcb711716..b7cd39f107 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -28,6 +28,7 @@ #include "imt_conflict_table.h" #include "imtable-inl.h" #include "interpreter/interpreter.h" +#include "instrumentation.h" #include "linear_alloc.h" #include "method_bss_mapping.h" #include "method_handles.h" @@ -895,7 +896,6 @@ void BuildQuickArgumentVisitor::FixupReferences() { soa_->Env()->DeleteLocalRef(pair.first); } } - // Handler for invocation on proxy methods. On entry a frame will exist for the proxy object method // which is responsible for recording callee save registers. We explicitly place into jobjects the // incoming reference arguments (so they survive GC). We invoke the invocation handler, which is a @@ -988,6 +988,77 @@ void RememberForGcArgumentVisitor::FixupReferences() { } } +extern "C" const void* artInstrumentationMethodEntryFromCode(ArtMethod* method, + mirror::Object* this_object, + Thread* self, + ArtMethod** sp) + REQUIRES_SHARED(Locks::mutator_lock_) { + const void* result; + // Instrumentation changes the stack. Thus, when exiting, the stack cannot be verified, so skip + // that part. + ScopedQuickEntrypointChecks sqec(self, kIsDebugBuild, false); + instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); + if (instrumentation->IsDeoptimized(method)) { + result = GetQuickToInterpreterBridge(); + } else { + result = instrumentation->GetQuickCodeFor(method, kRuntimePointerSize); + DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(result)); + } + + bool interpreter_entry = (result == GetQuickToInterpreterBridge()); + bool is_static = method->IsStatic(); + uint32_t shorty_len; + const char* shorty = + method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(&shorty_len); + + ScopedObjectAccessUnchecked soa(self); + RememberForGcArgumentVisitor visitor(sp, is_static, shorty, shorty_len, &soa); + visitor.VisitArguments(); + + instrumentation->PushInstrumentationStackFrame(self, + is_static ? nullptr : this_object, + method, + QuickArgumentVisitor::GetCallingPc(sp), + interpreter_entry); + + visitor.FixupReferences(); + if (UNLIKELY(self->IsExceptionPending())) { + return nullptr; + } + CHECK(result != nullptr) << method->PrettyMethod(); + return result; +} + +extern "C" TwoWordReturn artInstrumentationMethodExitFromCode(Thread* self, + ArtMethod** sp, + uint64_t* gpr_result, + uint64_t* fpr_result) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK_EQ(reinterpret_cast<uintptr_t>(self), reinterpret_cast<uintptr_t>(Thread::Current())); + CHECK(gpr_result != nullptr); + CHECK(fpr_result != nullptr); + // Instrumentation exit stub must not be entered with a pending exception. + CHECK(!self->IsExceptionPending()) << "Enter instrumentation exit stub with pending exception " + << self->GetException()->Dump(); + // Compute address of return PC and sanity check that it currently holds 0. + size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, CalleeSaveType::kSaveRefsOnly); + uintptr_t* return_pc = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) + + return_pc_offset); + CHECK_EQ(*return_pc, 0U); + + // Pop the frame filling in the return pc. The low half of the return value is 0 when + // deoptimization shouldn't be performed with the high-half having the return address. When + // deoptimization should be performed the low half is zero and the high-half the address of the + // deoptimization entry point. + instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); + TwoWordReturn return_or_deoptimize_pc = instrumentation->PopInstrumentationStackFrame( + self, return_pc, gpr_result, fpr_result); + if (self->IsExceptionPending()) { + return GetTwoWordFailureValue(); + } + return return_or_deoptimize_pc; +} + // Lazily resolve a method for quick. Called by stub code. extern "C" const void* artQuickResolutionTrampoline( ArtMethod* called, mirror::Object* receiver, Thread* self, ArtMethod** sp) diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 9b9c70f040..8120cc484e 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -1123,25 +1123,40 @@ static void CheckStackDepth(Thread* self, const InstrumentationStackFrame& instr void Instrumentation::PushInstrumentationStackFrame(Thread* self, mirror::Object* this_object, ArtMethod* method, uintptr_t lr, bool interpreter_entry) { - // We have a callee-save frame meaning this value is guaranteed to never be 0. - size_t frame_id = StackVisitor::ComputeNumFrames(self, kInstrumentationStackWalk); + DCHECK(!self->IsExceptionPending()); std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack(); if (kVerboseInstrumentation) { LOG(INFO) << "Entering " << ArtMethod::PrettyMethod(method) << " from PC " << reinterpret_cast<void*>(lr); } - instrumentation::InstrumentationStackFrame instrumentation_frame(this_object, method, lr, - frame_id, interpreter_entry); - stack->push_front(instrumentation_frame); + // We send the enter event before pushing the instrumentation frame to make cleanup easier. If the + // event causes an exception we can simply send the unwind event and return. + StackHandleScope<1> hs(self); + Handle<mirror::Object> h_this(hs.NewHandle(this_object)); if (!interpreter_entry) { - MethodEnterEvent(self, this_object, method, 0); + MethodEnterEvent(self, h_this.Get(), method, 0); + if (self->IsExceptionPending()) { + MethodUnwindEvent(self, h_this.Get(), method, 0); + return; + } } + + // We have a callee-save frame meaning this value is guaranteed to never be 0. + DCHECK(!self->IsExceptionPending()); + size_t frame_id = StackVisitor::ComputeNumFrames(self, kInstrumentationStackWalk); + + instrumentation::InstrumentationStackFrame instrumentation_frame(h_this.Get(), method, lr, + frame_id, interpreter_entry); + stack->push_front(instrumentation_frame); } -TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintptr_t* return_pc, - uint64_t gpr_result, - uint64_t fpr_result) { +TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, + uintptr_t* return_pc, + uint64_t* gpr_result, + uint64_t* fpr_result) { + DCHECK(gpr_result != nullptr); + DCHECK(fpr_result != nullptr); // Do the pop. std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack(); CHECK_GT(stack->size(), 0U); @@ -1157,13 +1172,20 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintpt uint32_t length; const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); char return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0]; + bool is_ref = return_shorty == '[' || return_shorty == 'L'; + StackHandleScope<1> hs(self); + MutableHandle<mirror::Object> res(hs.NewHandle<mirror::Object>(nullptr)); JValue return_value; if (return_shorty == 'V') { return_value.SetJ(0); } else if (return_shorty == 'F' || return_shorty == 'D') { - return_value.SetJ(fpr_result); + return_value.SetJ(*fpr_result); } else { - return_value.SetJ(gpr_result); + return_value.SetJ(*gpr_result); + } + if (is_ref) { + // Take a handle to the return value so we won't lose it if we suspend. + res.Assign(return_value.GetL()); } // TODO: improve the dex pc information here, requires knowledge of current PC as opposed to // return_pc. @@ -1180,6 +1202,10 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintpt bool deoptimize = (visitor.caller != nullptr) && (interpreter_stubs_installed_ || IsDeoptimized(visitor.caller) || Dbg::IsForcedInterpreterNeededForUpcall(self, visitor.caller)); + if (is_ref) { + // Restore the return value if it's a reference since it might have moved. + *reinterpret_cast<mirror::Object**>(gpr_result) = res.Get(); + } if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) { if (kVerboseInstrumentation) { LOG(INFO) << "Deoptimizing " @@ -1214,9 +1240,8 @@ uintptr_t Instrumentation::PopMethodForUnwind(Thread* self, bool is_deoptimizati // Do the pop. std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack(); CHECK_GT(stack->size(), 0U); + size_t idx = stack->size(); InstrumentationStackFrame instrumentation_frame = stack->front(); - // TODO: bring back CheckStackDepth(self, instrumentation_frame, 2); - stack->pop_front(); ArtMethod* method = instrumentation_frame.method_; if (is_deoptimization) { @@ -1234,6 +1259,10 @@ uintptr_t Instrumentation::PopMethodForUnwind(Thread* self, bool is_deoptimizati uint32_t dex_pc = DexFile::kDexNoIndex; MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc); } + // TODO: bring back CheckStackDepth(self, instrumentation_frame, 2); + CHECK_EQ(stack->size(), idx); + DCHECK(instrumentation_frame.method_ == stack->front().method_); + stack->pop_front(); return instrumentation_frame.return_pc_; } diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index 363985fd62..90b5def9fe 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -432,9 +432,13 @@ class Instrumentation { REQUIRES_SHARED(Locks::mutator_lock_); // Called when an instrumented method is exited. Removes the pushed instrumentation frame - // returning the intended link register. Generates method exit events. + // returning the intended link register. Generates method exit events. The gpr_result and + // fpr_result pointers are pointers to the locations where the integer/pointer and floating point + // result values of the function are stored. Both pointers must always be valid but the values + // held there will only be meaningful if interpreted as the appropriate type given the function + // being returned from. TwoWordReturn PopInstrumentationStackFrame(Thread* self, uintptr_t* return_pc, - uint64_t gpr_result, uint64_t fpr_result) + uint64_t* gpr_result, uint64_t* fpr_result) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_); // Pops an instrumentation frame from the current thread and generate an unwind event. diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index 4bc0f2fa12..85cf73b044 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -254,6 +254,13 @@ static inline JValue Execute( if (UNLIKELY(instrumentation->HasMethodEntryListeners())) { instrumentation->MethodEnterEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), method, 0); + if (UNLIKELY(self->IsExceptionPending())) { + instrumentation->MethodUnwindEvent(self, + shadow_frame.GetThisObject(code_item->ins_size_), + method, + 0); + return JValue(); + } } if (!stay_in_interpreter) { diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 32a23783b7..45788e7617 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -26,13 +26,13 @@ namespace art { namespace interpreter { -#define HANDLE_PENDING_EXCEPTION() \ +#define HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instr) \ do { \ DCHECK(self->IsExceptionPending()); \ self->AllowThreadSuspension(); \ uint32_t found_dex_pc = FindNextInstructionFollowingException(self, shadow_frame, \ inst->GetDexPc(insns), \ - instrumentation); \ + instr); \ if (found_dex_pc == DexFile::kDexNoIndex) { \ /* Structured locking is to be enforced for abnormal termination, too. */ \ DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame); \ @@ -47,6 +47,8 @@ namespace interpreter { } \ } while (false) +#define HANDLE_PENDING_EXCEPTION() HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instrumentation) + #define POSSIBLY_HANDLE_PENDING_EXCEPTION(_is_exception_pending, _next_function) \ do { \ if (UNLIKELY(_is_exception_pending)) { \ @@ -218,6 +220,10 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), shadow_frame.GetMethod(), inst->GetDexPc(insns), result); + if (UNLIKELY(self->IsExceptionPending())) { + // Don't send another method exit event. + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); + } } if (interpret_one_instruction) { /* Signal mterp to return to caller */ @@ -235,6 +241,10 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), shadow_frame.GetMethod(), inst->GetDexPc(insns), result); + if (UNLIKELY(self->IsExceptionPending())) { + // Don't send another method exit event. + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); + } } if (interpret_one_instruction) { /* Signal mterp to return to caller */ @@ -253,6 +263,10 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), shadow_frame.GetMethod(), inst->GetDexPc(insns), result); + if (UNLIKELY(self->IsExceptionPending())) { + // Don't send another method exit event. + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); + } } if (interpret_one_instruction) { /* Signal mterp to return to caller */ @@ -270,6 +284,10 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), shadow_frame.GetMethod(), inst->GetDexPc(insns), result); + if (UNLIKELY(self->IsExceptionPending())) { + // Don't send another method exit event. + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); + } } if (interpret_one_instruction) { /* Signal mterp to return to caller */ @@ -307,6 +325,10 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), shadow_frame.GetMethod(), inst->GetDexPc(insns), result); + if (UNLIKELY(self->IsExceptionPending())) { + // Don't send another method exit event. + HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr); + } // Re-load since it might have moved during the MethodExitEvent. result.SetL(shadow_frame.GetVRegReference(ref_idx)); } diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 0cafac7380..6d20b49c37 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -29,6 +29,7 @@ #include "entrypoints/runtime_asm_entrypoints.h" #include "gc/accounting/bitmap-inl.h" #include "gc/scoped_gc_critical_section.h" +#include "intern_table.h" #include "jit/jit.h" #include "jit/profiling_info.h" #include "linear_alloc.h" diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h index 57b20a193b..75606391ad 100644 --- a/runtime/mirror/string-inl.h +++ b/runtime/mirror/string-inl.h @@ -26,7 +26,6 @@ #include "common_throws.h" #include "gc/heap-inl.h" #include "globals.h" -#include "intern_table.h" #include "runtime.h" #include "thread.h" #include "utf.h" @@ -161,10 +160,6 @@ class SetStringCountAndValueVisitorFromString { const int32_t offset_; }; -inline ObjPtr<String> String::Intern() { - return Runtime::Current()->GetInternTable()->InternWeak(this); -} - inline uint16_t String::CharAt(int32_t index) { int32_t count = GetLength(); if (UNLIKELY((index < 0) || (index >= count))) { diff --git a/runtime/mirror/string.cc b/runtime/mirror/string.cc index d0636932cb..82ff6ddead 100644 --- a/runtime/mirror/string.cc +++ b/runtime/mirror/string.cc @@ -421,5 +421,9 @@ std::string String::PrettyStringDescriptor() { return PrettyDescriptor(ToModifiedUtf8().c_str()); } +ObjPtr<String> String::Intern() { + return Runtime::Current()->GetInternTable()->InternWeak(this); +} + } // namespace mirror } // namespace art diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 45773fdfbf..3ec5b323c8 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -1731,6 +1731,7 @@ extern "C" bool ArtPlugin_Initialize() { } extern "C" bool ArtPlugin_Deinitialize() { + gEventHandler.Shutdown(); PhaseUtil::Unregister(); ThreadUtil::Unregister(); ClassUtil::Unregister(); diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 4b83b7c873..af85fb0f4c 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -218,8 +218,8 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_redefine_any_class = 0, .can_get_current_thread_cpu_time = 0, .can_get_thread_cpu_time = 0, - .can_generate_method_entry_events = 0, - .can_generate_method_exit_events = 0, + .can_generate_method_entry_events = 1, + .can_generate_method_exit_events = 1, .can_generate_all_class_hook_events = 0, .can_generate_compiled_method_load_events = 0, .can_generate_monitor_events = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index 57abf3142d..cb7e6a9ad0 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -20,6 +20,7 @@ #include <array> #include "events.h" +#include "ScopedLocalRef.h" #include "art_jvmti.h" @@ -135,6 +136,8 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, continue; } if (ShouldDispatch<kEvent>(env, thread)) { + ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); + jnienv->ExceptionClear(); jint new_len = 0; unsigned char* new_data = nullptr; auto callback = impl::GetCallback<kEvent>(env); @@ -148,6 +151,9 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, current_class_data, &new_len, &new_data); + if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { + jnienv->Throw(thr.get()); + } if (new_data != nullptr && new_data != current_class_data) { // Destroy the data the last transformer made. We skip this if the previous state was the // initial one since we don't know here which jvmtiEnv allocated it. @@ -180,6 +186,25 @@ inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const } } +// Events with JNIEnvs need to stash pending exceptions since they can cause new ones to be thrown. +// In accordance with the JVMTI specification we allow exceptions originating from events to +// overwrite the current exception, including exceptions originating from earlier events. +// TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list of the +// newest exception. +template <ArtJvmtiEvent kEvent, typename ...Args> +inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const { + for (ArtJvmTiEnv* env : envs) { + if (env != nullptr) { + ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); + jnienv->ExceptionClear(); + DispatchEvent<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...); + if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { + jnienv->Throw(thr.get()); + } + } + } +} + template <ArtJvmtiEvent kEvent, typename ...Args> inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const { using FnType = void(jvmtiEnv*, Args...); diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index 320c59c810..90bc122220 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -32,19 +32,24 @@ #include "events-inl.h" #include "art_jvmti.h" +#include "art_method-inl.h" #include "base/logging.h" #include "gc/allocation_listener.h" #include "gc/gc_pause_listener.h" #include "gc/heap.h" +#include "gc/scoped_gc_critical_section.h" #include "handle_scope-inl.h" #include "instrumentation.h" #include "jni_env_ext-inl.h" +#include "jni_internal.h" #include "mirror/class.h" #include "mirror/object-inl.h" #include "runtime.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" -#include "thread-current-inl.h" +#include "thread-inl.h" +#include "thread_list.h" +#include "ti_phase.h" namespace openjdkjvmti { @@ -294,6 +299,222 @@ static void SetupGcPauseTracking(JvmtiGcPauseListener* listener, ArtJvmtiEvent e } } +template<typename Type> +static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj); +} + +class JvmtiMethodTraceListener FINAL : public art::instrumentation::InstrumentationListener { + public: + explicit JvmtiMethodTraceListener(EventHandler* handler) : event_handler_(handler) {} + + template<ArtJvmtiEvent kEvent, typename ...Args> + void RunEventCallback(art::Thread* self, art::JNIEnvExt* jnienv, Args... args) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer())); + // Just give the event a good sized JNI frame. 100 should be fine. + jnienv->PushFrame(100); + { + // Need to do trampoline! :( + art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); + event_handler_->DispatchEvent<kEvent>(self, + static_cast<JNIEnv*>(jnienv), + thread_jni.get(), + args...); + } + jnienv->PopFrame(); + } + + // Call-back for when a method is entered. + void MethodEntered(art::Thread* self, + art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, + art::ArtMethod* method, + uint32_t dex_pc ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + if (!method->IsRuntimeMethod() && + event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodEntry)) { + art::JNIEnvExt* jnienv = self->GetJniEnv(); + RunEventCallback<ArtJvmtiEvent::kMethodEntry>(self, + jnienv, + art::jni::EncodeArtMethod(method)); + } + } + + // Callback for when a method is exited with a reference return value. + void MethodExited(art::Thread* self, + art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, + art::ArtMethod* method, + uint32_t dex_pc ATTRIBUTE_UNUSED, + art::Handle<art::mirror::Object> return_value) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + if (!method->IsRuntimeMethod() && + event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) { + DCHECK_EQ(method->GetReturnTypePrimitive(), art::Primitive::kPrimNot) + << method->PrettyMethod(); + DCHECK(!self->IsExceptionPending()); + jvalue val; + art::JNIEnvExt* jnienv = self->GetJniEnv(); + ScopedLocalRef<jobject> return_jobj(jnienv, AddLocalRef<jobject>(jnienv, return_value.Get())); + val.l = return_jobj.get(); + RunEventCallback<ArtJvmtiEvent::kMethodExit>( + self, + jnienv, + art::jni::EncodeArtMethod(method), + /*was_popped_by_exception*/ static_cast<jboolean>(JNI_FALSE), + val); + } + } + + // Call-back for when a method is exited. + void MethodExited(art::Thread* self, + art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, + art::ArtMethod* method, + uint32_t dex_pc ATTRIBUTE_UNUSED, + const art::JValue& return_value) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + if (!method->IsRuntimeMethod() && + event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) { + DCHECK_NE(method->GetReturnTypePrimitive(), art::Primitive::kPrimNot) + << method->PrettyMethod(); + DCHECK(!self->IsExceptionPending()); + jvalue val; + art::JNIEnvExt* jnienv = self->GetJniEnv(); + // 64bit integer is the largest value in the union so we should be fine simply copying it into + // the union. + val.j = return_value.GetJ(); + RunEventCallback<ArtJvmtiEvent::kMethodExit>( + self, + jnienv, + art::jni::EncodeArtMethod(method), + /*was_popped_by_exception*/ static_cast<jboolean>(JNI_FALSE), + val); + } + } + + // Call-back for when a method is popped due to an exception throw. A method will either cause a + // MethodExited call-back or a MethodUnwind call-back when its activation is removed. + void MethodUnwind(art::Thread* self, + art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, + art::ArtMethod* method, + uint32_t dex_pc ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + if (!method->IsRuntimeMethod() && + event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) { + jvalue val; + // Just set this to 0xffffffffffffffff so it's not uninitialized. + val.j = static_cast<jlong>(-1); + art::JNIEnvExt* jnienv = self->GetJniEnv(); + art::StackHandleScope<1> hs(self); + art::Handle<art::mirror::Throwable> old_exception(hs.NewHandle(self->GetException())); + CHECK(!old_exception.IsNull()); + self->ClearException(); + RunEventCallback<ArtJvmtiEvent::kMethodExit>( + self, + jnienv, + art::jni::EncodeArtMethod(method), + /*was_popped_by_exception*/ static_cast<jboolean>(JNI_TRUE), + val); + // Match RI behavior of just throwing away original exception if a new one is thrown. + if (LIKELY(!self->IsExceptionPending())) { + self->SetException(old_exception.Get()); + } + } + } + + // Call-back for when the dex pc moves in a method. We don't currently have any events associated + // with this. + void DexPcMoved(art::Thread* self ATTRIBUTE_UNUSED, + art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, + art::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t new_dex_pc ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + return; + } + + // Call-back for when we read from a field. + void FieldRead(art::Thread* self ATTRIBUTE_UNUSED, + art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, + art::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED, + art::ArtField* field ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + return; + } + + // Call-back for when we write into a field. + void FieldWritten(art::Thread* self ATTRIBUTE_UNUSED, + art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, + art::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED, + art::ArtField* field ATTRIBUTE_UNUSED, + const art::JValue& field_value ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + return; + } + + // Call-back when an exception is caught. + void ExceptionCaught(art::Thread* self ATTRIBUTE_UNUSED, + art::Handle<art::mirror::Throwable> exception_object ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + return; + } + + // Call-back for when we execute a branch. + void Branch(art::Thread* self ATTRIBUTE_UNUSED, + art::ArtMethod* method ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED, + int32_t dex_pc_offset ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + return; + } + + // Call-back for when we get an invokevirtual or an invokeinterface. + void InvokeVirtualOrInterface(art::Thread* self ATTRIBUTE_UNUSED, + art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, + art::ArtMethod* caller ATTRIBUTE_UNUSED, + uint32_t dex_pc ATTRIBUTE_UNUSED, + art::ArtMethod* callee ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + return; + } + + private: + EventHandler* const event_handler_; +}; + +static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { + switch (event) { + case ArtJvmtiEvent::kMethodEntry: + return art::instrumentation::Instrumentation::kMethodEntered; + case ArtJvmtiEvent::kMethodExit: + return art::instrumentation::Instrumentation::kMethodExited | + art::instrumentation::Instrumentation::kMethodUnwind; + default: + LOG(FATAL) << "Unknown event "; + return 0; + } +} + +static void SetupMethodTraceListener(JvmtiMethodTraceListener* listener, + ArtJvmtiEvent event, + bool enable) { + uint32_t new_events = GetInstrumentationEventsFor(event); + art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation(); + art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(), + art::gc::kGcCauseInstrumentation, + art::gc::kCollectorTypeInstrumentation); + art::ScopedSuspendAll ssa("jvmti method tracing installation"); + if (enable) { + if (!instr->AreAllMethodsDeoptimized()) { + instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true); + } + instr->AddListener(listener, new_events); + } else { + instr->RemoveListener(listener, new_events); + } +} + // Handle special work for the given event type, if necessary. void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { switch (event) { @@ -306,6 +527,11 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { SetupGcPauseTracking(gc_pause_listener_.get(), event, enable); return; + case ArtJvmtiEvent::kMethodEntry: + case ArtJvmtiEvent::kMethodExit: + SetupMethodTraceListener(method_trace_listener_.get(), event, enable); + return; + default: break; } @@ -419,9 +645,21 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, return ERR(NONE); } +void EventHandler::Shutdown() { + // Need to remove the method_trace_listener_ if it's there. + art::Thread* self = art::Thread::Current(); + art::gc::ScopedGCCriticalSection gcs(self, + art::gc::kGcCauseInstrumentation, + art::gc::kCollectorTypeInstrumentation); + art::ScopedSuspendAll ssa("jvmti method tracing uninstallation"); + // Just remove every possible event. + art::Runtime::Current()->GetInstrumentation()->RemoveListener(method_trace_listener_.get(), ~0); +} + EventHandler::EventHandler() { alloc_listener_.reset(new JvmtiAllocationListener(this)); gc_pause_listener_.reset(new JvmtiGcPauseListener(this)); + method_trace_listener_.reset(new JvmtiMethodTraceListener(this)); } EventHandler::~EventHandler() { diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h index b9e3cf0b09..5f37dcf0a7 100644 --- a/runtime/openjdkjvmti/events.h +++ b/runtime/openjdkjvmti/events.h @@ -29,6 +29,7 @@ namespace openjdkjvmti { struct ArtJvmTiEnv; class JvmtiAllocationListener; class JvmtiGcPauseListener; +class JvmtiMethodTraceListener; // an enum for ArtEvents. This differs from the JVMTI events only in that we distinguish between // retransformation capable and incapable loading @@ -137,6 +138,9 @@ class EventHandler { EventHandler(); ~EventHandler(); + // do cleanup for the event handler. + void Shutdown(); + // Register an env. It is assumed that this happens on env creation, that is, no events are // enabled, yet. void RegisterArtJvmTiEnv(ArtJvmTiEnv* env); @@ -160,6 +164,12 @@ class EventHandler { template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE inline void DispatchEvent(art::Thread* thread, Args... args) const; + // Dispatch event to all registered environments stashing exceptions as needed. This works since + // JNIEnv* is always the second argument if it is passed to an event. Needed since C++ does not + // allow partial template function specialization. + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const; // Dispatch event to the given environment, only. template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE @@ -211,6 +221,7 @@ class EventHandler { std::unique_ptr<JvmtiAllocationListener> alloc_listener_; std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_; + std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_; }; } // namespace openjdkjvmti diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 743d6f428b..c11e4bd448 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -57,6 +57,7 @@ #include "art_field-inl.h" #include "art_method-inl.h" #include "asm_support.h" +#include "asm_support_check.h" #include "atomic.h" #include "base/arena_allocator.h" #include "base/dumpable.h" diff --git a/test/651-checker-byte-simd-minmax/src/Main.java b/test/651-checker-byte-simd-minmax/src/Main.java index fe4580784a..4711214c9d 100644 --- a/test/651-checker-byte-simd-minmax/src/Main.java +++ b/test/651-checker-byte-simd-minmax/src/Main.java @@ -33,6 +33,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMin(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none private static void doitMin(byte[] x, byte[] y, byte[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -57,6 +64,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMinUnsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none private static void doitMinUnsigned(byte[] x, byte[] y, byte[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -78,6 +92,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMax(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none private static void doitMax(byte[] x, byte[] y, byte[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -102,6 +123,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMaxUnsigned(byte[], byte[], byte[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none private static void doitMaxUnsigned(byte[] x, byte[] y, byte[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { diff --git a/test/651-checker-char-simd-minmax/src/Main.java b/test/651-checker-char-simd-minmax/src/Main.java index e2998dadf6..79795ee0bd 100644 --- a/test/651-checker-char-simd-minmax/src/Main.java +++ b/test/651-checker-char-simd-minmax/src/Main.java @@ -33,6 +33,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMin(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none private static void doitMin(char[] x, char[] y, char[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -54,6 +61,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMax(char[], char[], char[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none private static void doitMax(char[] x, char[] y, char[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { diff --git a/test/651-checker-double-simd-minmax/src/Main.java b/test/651-checker-double-simd-minmax/src/Main.java index cf04f85906..23a6d54d9e 100644 --- a/test/651-checker-double-simd-minmax/src/Main.java +++ b/test/651-checker-double-simd-minmax/src/Main.java @@ -27,6 +27,7 @@ public class Main { /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none // // TODO x86: 0.0 vs -0.0? + // TODO MIPS64: min(x, NaN)? // /// CHECK-START-ARM64: void Main.doitMin(double[], double[], double[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none @@ -49,6 +50,7 @@ public class Main { /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none // // TODO x86: 0.0 vs -0.0? + // TODO MIPS64: max(x, NaN)? // /// CHECK-START-ARM64: void Main.doitMax(double[], double[], double[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none diff --git a/test/651-checker-float-simd-minmax/src/Main.java b/test/651-checker-float-simd-minmax/src/Main.java index bd412e02e9..3959c821c4 100644 --- a/test/651-checker-float-simd-minmax/src/Main.java +++ b/test/651-checker-float-simd-minmax/src/Main.java @@ -27,6 +27,7 @@ public class Main { /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none // // TODO x86: 0.0 vs -0.0? + // TODO MIPS64: min(x, NaN)? // /// CHECK-START-ARM64: void Main.doitMin(float[], float[], float[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none @@ -49,6 +50,7 @@ public class Main { /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none // // TODO x86: 0.0 vs -0.0? + // TODO MIPS64: max(x, NaN)? // /// CHECK-START-ARM64: void Main.doitMax(float[], float[], float[]) loop_optimization (after) /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none diff --git a/test/651-checker-int-simd-minmax/src/Main.java b/test/651-checker-int-simd-minmax/src/Main.java index 6cee7b5484..2a97009ae9 100644 --- a/test/651-checker-int-simd-minmax/src/Main.java +++ b/test/651-checker-int-simd-minmax/src/Main.java @@ -32,6 +32,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMin(int[], int[], int[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none private static void doitMin(int[] x, int[] y, int[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -52,6 +59,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMax(int[], int[], int[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none private static void doitMax(int[] x, int[] y, int[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { diff --git a/test/651-checker-long-simd-minmax/src/Main.java b/test/651-checker-long-simd-minmax/src/Main.java index 51cf67ee00..6289a1e3bb 100644 --- a/test/651-checker-long-simd-minmax/src/Main.java +++ b/test/651-checker-long-simd-minmax/src/Main.java @@ -28,8 +28,16 @@ public class Main { // // Not directly supported for longs. // - /// CHECK-START: void Main.doitMin(long[], long[], long[]) loop_optimization (after) + /// CHECK-START-ARM64: void Main.doitMin(long[], long[], long[]) loop_optimization (after) /// CHECK-NOT: VecMin + // + /// CHECK-START-MIPS64: void Main.doitMin(long[], long[], long[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + private static void doitMin(long[] x, long[] y, long[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -46,8 +54,15 @@ public class Main { // // Not directly supported for longs. // - /// CHECK-START: void Main.doitMax(long[], long[], long[]) loop_optimization (after) + /// CHECK-START-ARM64: void Main.doitMax(long[], long[], long[]) loop_optimization (after) /// CHECK-NOT: VecMax + // + /// CHECK-START-MIPS64: void Main.doitMax(long[], long[], long[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none private static void doitMax(long[] x, long[] y, long[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { diff --git a/test/651-checker-short-simd-minmax/src/Main.java b/test/651-checker-short-simd-minmax/src/Main.java index 09485a2d8a..3bd1305e3e 100644 --- a/test/651-checker-short-simd-minmax/src/Main.java +++ b/test/651-checker-short-simd-minmax/src/Main.java @@ -33,6 +33,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMin(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none private static void doitMin(short[] x, short[] y, short[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -57,6 +64,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMinUnsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Min:d\d+>> VecMin [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Min>>] loop:<<Loop>> outer_loop:none private static void doitMinUnsigned(short[] x, short[] y, short[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -78,6 +92,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMax(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:false loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none private static void doitMax(short[] x, short[] y, short[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { @@ -102,6 +123,13 @@ public class Main { /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-MIPS64: void Main.doitMaxUnsigned(short[], short[], short[]) loop_optimization (after) + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Max:d\d+>> VecMax [<<Get1>>,<<Get2>>] unsigned:true loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Max>>] loop:<<Loop>> outer_loop:none private static void doitMaxUnsigned(short[] x, short[] y, short[] z) { int min = Math.min(x.length, Math.min(y.length, z.length)); for (int i = 0; i < min; i++) { diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt new file mode 100644 index 0000000000..8c67d66b7c --- /dev/null +++ b/test/988-method-trace/expected.txt @@ -0,0 +1,276 @@ +<= public static native void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> +=> art.Test988$IterOp() +.=> public java.lang.Object() +.<= public java.lang.Object() -> <null: null> +<= art.Test988$IterOp() -> <null: null> +=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) +.=> public int art.Test988$IterOp.applyAsInt(int) +..=> static int art.Test988.iter_fibonacci(int) +..<= static int art.Test988.iter_fibonacci(int) -> <class java.lang.Integer: 832040> +.<= public int art.Test988$IterOp.applyAsInt(int) -> <class java.lang.Integer: 832040> +.=> public art.Test988$FibResult(java.lang.String,int,int) +..=> public java.lang.Object() +..<= public java.lang.Object() -> <null: null> +.<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null> +.=> public boolean java.util.ArrayList.add(java.lang.Object) +..=> private void java.util.ArrayList.ensureCapacityInternal(int) +...=> private void java.util.ArrayList.ensureExplicitCapacity(int) +...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> +..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> +fibonacci(30)=832040 +.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> +<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> +=> art.Test988$RecurOp() +.=> public java.lang.Object() +.<= public java.lang.Object() -> <null: null> +<= art.Test988$RecurOp() -> <null: null> +=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) +.=> public int art.Test988$RecurOp.applyAsInt(int) +..=> static int art.Test988.fibonacci(int) +...=> static int art.Test988.fibonacci(int) +....=> static int art.Test988.fibonacci(int) +.....=> static int art.Test988.fibonacci(int) +......=> static int art.Test988.fibonacci(int) +......<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1> +......=> static int art.Test988.fibonacci(int) +......<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 0> +.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1> +.....=> static int art.Test988.fibonacci(int) +.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1> +....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 2> +....=> static int art.Test988.fibonacci(int) +.....=> static int art.Test988.fibonacci(int) +.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1> +.....=> static int art.Test988.fibonacci(int) +.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 0> +....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1> +...<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 3> +...=> static int art.Test988.fibonacci(int) +....=> static int art.Test988.fibonacci(int) +.....=> static int art.Test988.fibonacci(int) +.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1> +.....=> static int art.Test988.fibonacci(int) +.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 0> +....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1> +....=> static int art.Test988.fibonacci(int) +....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1> +...<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 2> +..<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 5> +.<= public int art.Test988$RecurOp.applyAsInt(int) -> <class java.lang.Integer: 5> +.=> public art.Test988$FibResult(java.lang.String,int,int) +..=> public java.lang.Object() +..<= public java.lang.Object() -> <null: null> +.<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null> +.=> public boolean java.util.ArrayList.add(java.lang.Object) +..=> private void java.util.ArrayList.ensureCapacityInternal(int) +...=> private void java.util.ArrayList.ensureExplicitCapacity(int) +...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> +..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> +fibonacci(5)=5 +.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> +<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> +=> art.Test988$IterOp() +.=> public java.lang.Object() +.<= public java.lang.Object() -> <null: null> +<= art.Test988$IterOp() -> <null: null> +=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) +.=> public int art.Test988$IterOp.applyAsInt(int) +..=> static int art.Test988.iter_fibonacci(int) +...=> public java.lang.StringBuilder() +....=> java.lang.AbstractStringBuilder(int) +.....=> public java.lang.Object() +.....<= public java.lang.Object() -> <null: null> +....<= java.lang.AbstractStringBuilder(int) -> <null: null> +...<= public java.lang.StringBuilder() -> <null: null> +...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) +....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) +.....=> public int java.lang.String.length() +.....<= public int java.lang.String.length() -> <class java.lang.Integer: 14> +.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) +.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null> +.....=> public void java.lang.String.getChars(int,int,char[],int) +......=> public int java.lang.String.length() +......<= public int java.lang.String.length() -> <class java.lang.Integer: 14> +......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int) +......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null> +.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null> +....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...=> public java.lang.StringBuilder java.lang.StringBuilder.append(int) +....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int) +.....=> static int java.lang.Integer.stringSize(int) +.....<= static int java.lang.Integer.stringSize(int) -> <class java.lang.Integer: 2> +.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) +......=> private int java.lang.AbstractStringBuilder.newCapacity(int) +......<= private int java.lang.AbstractStringBuilder.newCapacity(int) -> <class java.lang.Integer: 34> +......=> public static char[] java.util.Arrays.copyOf(char[],int) +.......=> public static int java.lang.Math.min(int,int) +.......<= public static int java.lang.Math.min(int,int) -> <class java.lang.Integer: 16> +.......=> public static void java.lang.System.arraycopy(char[],int,char[],int,int) +.......<= public static void java.lang.System.arraycopy(char[],int,char[],int,int) -> <null: null> +......<= public static char[] java.util.Arrays.copyOf(char[],int) -> <class [C: [B, a, d, , a, r, g, u, m, e, n, t, :, , -, 1, 9, , <, , 0, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>]> +.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null> +.....=> static void java.lang.Integer.getChars(int,int,char[]) +.....<= static void java.lang.Integer.getChars(int,int,char[]) -> <null: null> +....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...<= public java.lang.StringBuilder java.lang.StringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) +....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) +.....=> public int java.lang.String.length() +.....<= public int java.lang.String.length() -> <class java.lang.Integer: 4> +.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) +.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null> +.....=> public void java.lang.String.getChars(int,int,char[],int) +......=> public int java.lang.String.length() +......<= public int java.lang.String.length() -> <class java.lang.Integer: 4> +......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int) +......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null> +.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null> +....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...=> public java.lang.String java.lang.StringBuilder.toString() +....=> static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) +....<= static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> +...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0> +...=> public java.lang.Error(java.lang.String) +....=> public java.lang.Throwable(java.lang.String) +.....=> public java.lang.Object() +.....<= public java.lang.Object() -> <null: null> +.....=> public static final java.util.List java.util.Collections.emptyList() +.....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []> +.....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() +......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() +......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> +.....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 + at art.Test988.iter_fibonacci(Test988.java:203) + at art.Test988$IterOp.applyAsInt(Test988.java:198) + at art.Test988.doFibTest(Test988.java:291) + at art.Test988.run(Test988.java:261) + at Main.main(Main.java:19) +> +....<= public java.lang.Throwable(java.lang.String) -> <null: null> +...<= public java.lang.Error(java.lang.String) -> <null: null> +..<= static int art.Test988.iter_fibonacci(int) EXCEPTION +.<= public int art.Test988$IterOp.applyAsInt(int) EXCEPTION +.=> public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) +..=> public java.lang.Object() +..<= public java.lang.Object() -> <null: null> +.<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null> +.=> public boolean java.util.ArrayList.add(java.lang.Object) +..=> private void java.util.ArrayList.ensureCapacityInternal(int) +...=> private void java.util.ArrayList.ensureExplicitCapacity(int) +...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> +..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> +fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 + at art.Test988.iter_fibonacci(Test988.java:203) + at art.Test988$IterOp.applyAsInt(Test988.java:198) + at art.Test988.doFibTest(Test988.java:291) + at art.Test988.run(Test988.java:261) + at Main.main(Main.java:19) + +.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> +<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> +=> art.Test988$RecurOp() +.=> public java.lang.Object() +.<= public java.lang.Object() -> <null: null> +<= art.Test988$RecurOp() -> <null: null> +=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) +.=> public int art.Test988$RecurOp.applyAsInt(int) +..=> static int art.Test988.fibonacci(int) +...=> public java.lang.StringBuilder() +....=> java.lang.AbstractStringBuilder(int) +.....=> public java.lang.Object() +.....<= public java.lang.Object() -> <null: null> +....<= java.lang.AbstractStringBuilder(int) -> <null: null> +...<= public java.lang.StringBuilder() -> <null: null> +...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) +....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) +.....=> public int java.lang.String.length() +.....<= public int java.lang.String.length() -> <class java.lang.Integer: 14> +.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) +.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null> +.....=> public void java.lang.String.getChars(int,int,char[],int) +......=> public int java.lang.String.length() +......<= public int java.lang.String.length() -> <class java.lang.Integer: 14> +......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int) +......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null> +.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null> +....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...=> public java.lang.StringBuilder java.lang.StringBuilder.append(int) +....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int) +.....=> static int java.lang.Integer.stringSize(int) +.....<= static int java.lang.Integer.stringSize(int) -> <class java.lang.Integer: 2> +.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) +......=> private int java.lang.AbstractStringBuilder.newCapacity(int) +......<= private int java.lang.AbstractStringBuilder.newCapacity(int) -> <class java.lang.Integer: 34> +......=> public static char[] java.util.Arrays.copyOf(char[],int) +.......=> public static int java.lang.Math.min(int,int) +.......<= public static int java.lang.Math.min(int,int) -> <class java.lang.Integer: 16> +.......=> public static void java.lang.System.arraycopy(char[],int,char[],int,int) +.......<= public static void java.lang.System.arraycopy(char[],int,char[],int,int) -> <null: null> +......<= public static char[] java.util.Arrays.copyOf(char[],int) -> <class [C: [B, a, d, , a, r, g, u, m, e, n, t, :, , -, 1, 9, , <, , 0, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>]> +.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null> +.....=> static void java.lang.Integer.getChars(int,int,char[]) +.....<= static void java.lang.Integer.getChars(int,int,char[]) -> <null: null> +....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...<= public java.lang.StringBuilder java.lang.StringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) +....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) +.....=> public int java.lang.String.length() +.....<= public int java.lang.String.length() -> <class java.lang.Integer: 4> +.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) +.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null> +.....=> public void java.lang.String.getChars(int,int,char[],int) +......=> public int java.lang.String.length() +......<= public int java.lang.String.length() -> <class java.lang.Integer: 4> +......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int) +......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null> +.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null> +....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0> +...=> public java.lang.String java.lang.StringBuilder.toString() +....=> static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) +....<= static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0> +...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0> +...=> public java.lang.Error(java.lang.String) +....=> public java.lang.Throwable(java.lang.String) +.....=> public java.lang.Object() +.....<= public java.lang.Object() -> <null: null> +.....=> public static final java.util.List java.util.Collections.emptyList() +.....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []> +.....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() +......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() +......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> +.....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 + at art.Test988.fibonacci(Test988.java:225) + at art.Test988$RecurOp.applyAsInt(Test988.java:220) + at art.Test988.doFibTest(Test988.java:291) + at art.Test988.run(Test988.java:262) + at Main.main(Main.java:19) +> +....<= public java.lang.Throwable(java.lang.String) -> <null: null> +...<= public java.lang.Error(java.lang.String) -> <null: null> +..<= static int art.Test988.fibonacci(int) EXCEPTION +.<= public int art.Test988$RecurOp.applyAsInt(int) EXCEPTION +.=> public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) +..=> public java.lang.Object() +..<= public java.lang.Object() -> <null: null> +.<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null> +.=> public boolean java.util.ArrayList.add(java.lang.Object) +..=> private void java.util.ArrayList.ensureCapacityInternal(int) +...=> private void java.util.ArrayList.ensureExplicitCapacity(int) +...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> +..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> +fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 + at art.Test988.fibonacci(Test988.java:225) + at art.Test988$RecurOp.applyAsInt(Test988.java:220) + at art.Test988.doFibTest(Test988.java:291) + at art.Test988.run(Test988.java:262) + at Main.main(Main.java:19) + +.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> +<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> +=> public static native java.lang.Thread java.lang.Thread.currentThread() +<= public static native java.lang.Thread java.lang.Thread.currentThread() -> <class java.lang.Thread: <non-deterministic>> +=> public static native void art.Trace.disableMethodTracing(java.lang.Thread) diff --git a/test/988-method-trace/info.txt b/test/988-method-trace/info.txt new file mode 100644 index 0000000000..f0a200dc18 --- /dev/null +++ b/test/988-method-trace/info.txt @@ -0,0 +1,15 @@ +Tests method tracing in JVMTI + +This test is sensitive to the internal implementations of: + * java.lang.Error + * java.lang.Integer + * java.lang.Math + * java.lang.String + * java.lang.System + * java.util.ArrayList + * java.util.Arrays + * java.util.StringBuilder + * all super-classes and super-interfaces of the above types. + +Changes to the internal implementation of these classes might (or might not) +change the output of this test. diff --git a/test/988-method-trace/run b/test/988-method-trace/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/988-method-trace/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/988-method-trace/src/Main.java b/test/988-method-trace/src/Main.java new file mode 100644 index 0000000000..9dd1142bb6 --- /dev/null +++ b/test/988-method-trace/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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 { + public static void main(String[] args) throws Exception { + art.Test988.run(); + } +} diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java new file mode 100644 index 0000000000..6ac7b11313 --- /dev/null +++ b/test/988-method-trace/src/art/Test988.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.function.IntUnaryOperator; +import java.util.function.Function; + +public class Test988 { + + // Methods with non-deterministic output that should not be printed. + static Set<Method> NON_DETERMINISTIC_OUTPUT_METHODS = new HashSet<>(); + + static { + try { + NON_DETERMINISTIC_OUTPUT_METHODS.add( + Throwable.class.getDeclaredMethod("nativeFillInStackTrace")); + } catch (Exception e) {} + try { + NON_DETERMINISTIC_OUTPUT_METHODS.add(Thread.class.getDeclaredMethod("currentThread")); + } catch (Exception e) {} + } + + static interface Printable { + public void Print(); + } + + static final class MethodEntry implements Printable { + private Object m; + private int cnt; + public MethodEntry(Object m, int cnt) { + this.m = m; + this.cnt = cnt; + } + @Override + public void Print() { + System.out.println(whitespace(cnt) + "=> " + m); + } + } + + private static String genericToString(Object val) { + if (val == null) { + return "null"; + } else if (val.getClass().isArray()) { + return arrayToString(val); + } else if (val instanceof Throwable) { + StringWriter w = new StringWriter(); + ((Throwable) val).printStackTrace(new PrintWriter(w)); + return w.toString(); + } else { + return val.toString(); + } + } + + private static String charArrayToString(char[] src) { + String[] res = new String[src.length]; + for (int i = 0; i < src.length; i++) { + if (Character.isISOControl(src[i])) { + res[i] = Character.getName(src[i]); + } else { + res[i] = Character.toString(src[i]); + } + } + return Arrays.toString(res); + } + + private static String arrayToString(Object val) { + Class<?> klass = val.getClass(); + if ((new Object[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString( + Arrays.stream((Object[])val).map(Test988::genericToString).toArray()); + } else if ((new byte[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((byte[])val); + } else if ((new char[0]).getClass().isAssignableFrom(klass)) { + return charArrayToString((char[])val); + } else if ((new short[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((short[])val); + } else if ((new int[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((int[])val); + } else if ((new long[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((long[])val); + } else if ((new float[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((float[])val); + } else if ((new double[0]).getClass().isAssignableFrom(klass)) { + return Arrays.toString((double[])val); + } else { + throw new Error("Unknown type " + klass); + } + } + + static final class MethodReturn implements Printable { + private Object m; + private Object val; + private int cnt; + public MethodReturn(Object m, Object val, int cnt) { + this.m = m; + this.val = val; + this.cnt = cnt; + } + @Override + public void Print() { + String print; + if (NON_DETERMINISTIC_OUTPUT_METHODS.contains(m)) { + print = "<non-deterministic>"; + } else { + print = genericToString(val); + } + Class<?> klass = null; + if (val != null) { + klass = val.getClass(); + } + System.out.println( + whitespace(cnt) + "<= " + m + " -> <" + klass + ": " + print + ">"); + } + } + + static final class MethodThrownThrough implements Printable { + private Object m; + private int cnt; + public MethodThrownThrough(Object m, int cnt) { + this.m = m; + this.cnt = cnt; + } + @Override + public void Print() { + System.out.println(whitespace(cnt) + "<= " + m + " EXCEPTION"); + } + } + + private static String whitespace(int n) { + String out = ""; + while (n > 0) { + n--; + out += "."; + } + return out; + } + + static final class FibThrow implements Printable { + private String format; + private int arg; + private Throwable res; + public FibThrow(String format, int arg, Throwable res) { + this.format = format; + this.arg = arg; + this.res = res; + } + + @Override + public void Print() { + System.out.printf(format, arg, genericToString(res)); + } + } + + static final class FibResult implements Printable { + private String format; + private int arg; + private int res; + public FibResult(String format, int arg, int res) { + this.format = format; + this.arg = arg; + this.res = res; + } + + @Override + public void Print() { + System.out.printf(format, arg, res); + } + } + + private static List<Printable> results = new ArrayList<>(); + private static int cnt = 1; + + // Iterative version + static final class IterOp implements IntUnaryOperator { + public int applyAsInt(int x) { + return iter_fibonacci(x); + } + } + static int iter_fibonacci(int n) { + if (n < 0) { + throw new Error("Bad argument: " + n + " < 0"); + } else if (n == 0) { + return 0; + } + int x = 1; + int y = 1; + for (int i = 3; i <= n; i++) { + int z = x + y; + x = y; + y = z; + } + return y; + } + + // Recursive version + static final class RecurOp implements IntUnaryOperator { + public int applyAsInt(int x) { + return fibonacci(x); + } + } + static int fibonacci(int n) { + if (n < 0) { + throw new Error("Bad argument: " + n + " < 0"); + } else if ((n == 0) || (n == 1)) { + return n; + } else { + return fibonacci(n - 1) + (fibonacci(n - 2)); + } + } + + public static void notifyMethodEntry(Object m) { + // Called by native code when a method is entered. This method is ignored by the native + // entry and exit hooks. + results.add(new MethodEntry(m, cnt)); + cnt++; + } + + public static void notifyMethodExit(Object m, boolean exception, Object result) { + cnt--; + if (exception) { + results.add(new MethodThrownThrough(m, cnt)); + } else { + results.add(new MethodReturn(m, result, cnt)); + } + } + + public static void run() throws Exception { + // call this here so it is linked. It doesn't actually do anything here. + loadAllClasses(); + Trace.disableMethodTracing(Thread.currentThread()); + Trace.enableMethodTracing( + Test988.class, + Test988.class.getDeclaredMethod("notifyMethodEntry", Object.class), + Test988.class.getDeclaredMethod( + "notifyMethodExit", Object.class, Boolean.TYPE, Object.class), + Thread.currentThread()); + doFibTest(30, new IterOp()); + doFibTest(5, new RecurOp()); + doFibTest(-19, new IterOp()); + doFibTest(-19, new RecurOp()); + // Turn off method tracing so we don't have to deal with print internals. + Trace.disableMethodTracing(Thread.currentThread()); + printResults(); + } + + // This ensures that all classes we touch are loaded before we start recording traces. This + // eliminates a major source of divergence between the RI and ART. + public static void loadAllClasses() { + MethodThrownThrough.class.toString(); + MethodEntry.class.toString(); + MethodReturn.class.toString(); + FibResult.class.toString(); + FibThrow.class.toString(); + Printable.class.toString(); + ArrayList.class.toString(); + RecurOp.class.toString(); + IterOp.class.toString(); + StringBuilder.class.toString(); + } + + public static void printResults() { + for (Printable p : results) { + p.Print(); + } + } + + public static void doFibTest(int x, IntUnaryOperator op) { + try { + int y = op.applyAsInt(x); + results.add(new FibResult("fibonacci(%d)=%d\n", x, y)); + } catch (Throwable t) { + results.add(new FibThrow("fibonacci(%d) -> %s\n", x, t)); + } + } +} diff --git a/test/988-method-trace/src/art/Trace.java b/test/988-method-trace/src/art/Trace.java new file mode 100644 index 0000000000..3370996df3 --- /dev/null +++ b/test/988-method-trace/src/art/Trace.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Method; + +public class Trace { + public static native void enableMethodTracing( + Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr); + public static native void disableMethodTracing(Thread thr); +} diff --git a/test/989-method-trace-throw/expected.txt b/test/989-method-trace-throw/expected.txt new file mode 100644 index 0000000000..0911bc35e8 --- /dev/null +++ b/test/989-method-trace-throw/expected.txt @@ -0,0 +1,188 @@ +Normal: Entering public static void art.Test989.doNothing() +Normal: Leaving public static void art.Test989.doNothing() returned null +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$doNothingClass]. +Normal: Entering public static native void art.Test989.doNothingNative() +Normal: Leaving public static native void art.Test989.doNothingNative() returned null +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$doNothingNativeClass]. +Normal: Entering public static void art.Test989.throwA() +Normal: Leaving public static void art.Test989.throwA() returned <exception> +Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwAClass] - art.Test989$ErrorA: Throwing Error A +Normal: Entering public static native void art.Test989.throwANative() +Normal: Leaving public static native void art.Test989.throwANative() returned <exception> +Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorA: Throwing Error A +Normal: Entering public static java.lang.Object art.Test989.returnValue() +Normal: Leaving public static java.lang.Object art.Test989.returnValue() returned TestObject(0) +returnValue returned: TestObject(0) +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnValueClass]. +Normal: Entering public static native java.lang.Object art.Test989.returnValueNative() +Normal: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(1) +returnValueNative returned: TestObject(1) +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnValueNativeClass]. +Normal: Entering public static void art.Test989.acceptValue(java.lang.Object) +Recieved TestObject(2) +Normal: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned null +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$acceptValueClass]. +Normal: Entering public static native void art.Test989.acceptValueNative(java.lang.Object) +Recieved TestObject(3) +Normal: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned null +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$acceptValueNativeClass]. +Normal: Entering public static void art.Test989.tryCatchExit() +Normal: Leaving public static void art.Test989.tryCatchExit() returned null +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$tryCatchExitClass]. +Normal: Entering public static float art.Test989.returnFloat() +Normal: Leaving public static float art.Test989.returnFloat() returned 1.618 +returnFloat returned: 1.618 +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnFloatClass]. +Normal: Entering public static native float art.Test989.returnFloatNative() +Normal: Leaving public static native float art.Test989.returnFloatNative() returned 1.618 +returnFloatNative returned: 1.618 +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnFloatNativeClass]. +Normal: Entering public static double art.Test989.returnDouble() +Normal: Leaving public static double art.Test989.returnDouble() returned 3.14159628 +returnDouble returned: 3.14159628 +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnDoubleClass]. +Normal: Entering public static native double art.Test989.returnDoubleNative() +Normal: Leaving public static native double art.Test989.returnDoubleNative() returned 3.14159628 +returnDoubleNative returned: 3.14159628 +Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnDoubleNativeClass]. +ThrowEnter: Entering public static void art.Test989.doNothing() +ThrowEnter: Leaving public static void art.Test989.doNothing() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$doNothingClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.doNothing() +ThrowEnter: Entering public static native void art.Test989.doNothingNative() +ThrowEnter: Leaving public static native void art.Test989.doNothingNative() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.doNothingNative() +ThrowEnter: Entering public static void art.Test989.throwA() +ThrowEnter: Leaving public static void art.Test989.throwA() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwAClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.throwA() +ThrowEnter: Entering public static native void art.Test989.throwANative() +ThrowEnter: Leaving public static native void art.Test989.throwANative() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.throwANative() +ThrowEnter: Entering public static java.lang.Object art.Test989.returnValue() +ThrowEnter: Leaving public static java.lang.Object art.Test989.returnValue() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnValueClass] - art.Test989$ErrorB: Throwing error while entering public static java.lang.Object art.Test989.returnValue() +ThrowEnter: Entering public static native java.lang.Object art.Test989.returnValueNative() +ThrowEnter: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native java.lang.Object art.Test989.returnValueNative() +ThrowEnter: Entering public static void art.Test989.acceptValue(java.lang.Object) +ThrowEnter: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.acceptValue(java.lang.Object) +ThrowEnter: Entering public static native void art.Test989.acceptValueNative(java.lang.Object) +ThrowEnter: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.acceptValueNative(java.lang.Object) +ThrowEnter: Entering public static void art.Test989.tryCatchExit() +ThrowEnter: Leaving public static void art.Test989.tryCatchExit() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.tryCatchExit() +ThrowEnter: Entering public static float art.Test989.returnFloat() +ThrowEnter: Leaving public static float art.Test989.returnFloat() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorB: Throwing error while entering public static float art.Test989.returnFloat() +ThrowEnter: Entering public static native float art.Test989.returnFloatNative() +ThrowEnter: Leaving public static native float art.Test989.returnFloatNative() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native float art.Test989.returnFloatNative() +ThrowEnter: Entering public static double art.Test989.returnDouble() +ThrowEnter: Leaving public static double art.Test989.returnDouble() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorB: Throwing error while entering public static double art.Test989.returnDouble() +ThrowEnter: Entering public static native double art.Test989.returnDoubleNative() +ThrowEnter: Leaving public static native double art.Test989.returnDoubleNative() returned <exception> +Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native double art.Test989.returnDoubleNative() +ThrowExit: Entering public static void art.Test989.doNothing() +ThrowExit: Leaving public static void art.Test989.doNothing() returned null +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$doNothingClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.doNothing() returned null +ThrowExit: Entering public static native void art.Test989.doNothingNative() +ThrowExit: Leaving public static native void art.Test989.doNothingNative() returned null +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.doNothingNative() returned null +ThrowExit: Entering public static void art.Test989.throwA() +ThrowExit: Leaving public static void art.Test989.throwA() returned <exception> +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwAClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.throwA() returned <exception> +ThrowExit: Entering public static native void art.Test989.throwANative() +ThrowExit: Leaving public static native void art.Test989.throwANative() returned <exception> +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.throwANative() returned <exception> +ThrowExit: Entering public static java.lang.Object art.Test989.returnValue() +ThrowExit: Leaving public static java.lang.Object art.Test989.returnValue() returned TestObject(7) +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnValueClass] - art.Test989$ErrorB: Throwing error while exit public static java.lang.Object art.Test989.returnValue() returned TestObject(7) +ThrowExit: Entering public static native java.lang.Object art.Test989.returnValueNative() +ThrowExit: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(8) +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(8) +ThrowExit: Entering public static void art.Test989.acceptValue(java.lang.Object) +Recieved TestObject(9) +ThrowExit: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned null +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.acceptValue(java.lang.Object) returned null +ThrowExit: Entering public static native void art.Test989.acceptValueNative(java.lang.Object) +Recieved TestObject(10) +ThrowExit: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned null +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.acceptValueNative(java.lang.Object) returned null +ThrowExit: Entering public static void art.Test989.tryCatchExit() +ThrowExit: Leaving public static void art.Test989.tryCatchExit() returned null +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.tryCatchExit() returned null +ThrowExit: Entering public static float art.Test989.returnFloat() +ThrowExit: Leaving public static float art.Test989.returnFloat() returned 1.618 +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorB: Throwing error while exit public static float art.Test989.returnFloat() returned 1.618 +ThrowExit: Entering public static native float art.Test989.returnFloatNative() +ThrowExit: Leaving public static native float art.Test989.returnFloatNative() returned 1.618 +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native float art.Test989.returnFloatNative() returned 1.618 +ThrowExit: Entering public static double art.Test989.returnDouble() +ThrowExit: Leaving public static double art.Test989.returnDouble() returned 3.14159628 +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorB: Throwing error while exit public static double art.Test989.returnDouble() returned 3.14159628 +ThrowExit: Entering public static native double art.Test989.returnDoubleNative() +ThrowExit: Leaving public static native double art.Test989.returnDoubleNative() returned 3.14159628 +Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native double art.Test989.returnDoubleNative() returned 3.14159628 +ThrowBoth: Entering public static void art.Test989.doNothing() +ThrowBoth: Leaving public static void art.Test989.doNothing() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$doNothingClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.doNothing() returned <exception> +ThrowBoth: Entering public static native void art.Test989.doNothingNative() +ThrowBoth: Leaving public static native void art.Test989.doNothingNative() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.doNothingNative() returned <exception> +ThrowBoth: Entering public static void art.Test989.throwA() +ThrowBoth: Leaving public static void art.Test989.throwA() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwAClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.throwA() returned <exception> +ThrowBoth: Entering public static native void art.Test989.throwANative() +ThrowBoth: Leaving public static native void art.Test989.throwANative() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.throwANative() returned <exception> +ThrowBoth: Entering public static java.lang.Object art.Test989.returnValue() +ThrowBoth: Leaving public static java.lang.Object art.Test989.returnValue() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnValueClass] - art.Test989$ErrorC: Throwing error while exit public static java.lang.Object art.Test989.returnValue() returned <exception> +ThrowBoth: Entering public static native java.lang.Object art.Test989.returnValueNative() +ThrowBoth: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native java.lang.Object art.Test989.returnValueNative() returned <exception> +ThrowBoth: Entering public static void art.Test989.acceptValue(java.lang.Object) +ThrowBoth: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.acceptValue(java.lang.Object) returned <exception> +ThrowBoth: Entering public static native void art.Test989.acceptValueNative(java.lang.Object) +ThrowBoth: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception> +ThrowBoth: Entering public static void art.Test989.tryCatchExit() +ThrowBoth: Leaving public static void art.Test989.tryCatchExit() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.tryCatchExit() returned <exception> +ThrowBoth: Entering public static float art.Test989.returnFloat() +ThrowBoth: Leaving public static float art.Test989.returnFloat() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorC: Throwing error while exit public static float art.Test989.returnFloat() returned <exception> +ThrowBoth: Entering public static native float art.Test989.returnFloatNative() +ThrowBoth: Leaving public static native float art.Test989.returnFloatNative() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native float art.Test989.returnFloatNative() returned <exception> +ThrowBoth: Entering public static double art.Test989.returnDouble() +ThrowBoth: Leaving public static double art.Test989.returnDouble() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorC: Throwing error while exit public static double art.Test989.returnDouble() returned <exception> +ThrowBoth: Entering public static native double art.Test989.returnDoubleNative() +ThrowBoth: Leaving public static native double art.Test989.returnDoubleNative() returned <exception> +Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native double art.Test989.returnDoubleNative() returned <exception> +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$doNothingClass]. +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$doNothingNativeClass]. +Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwAClass] - art.Test989$ErrorA: Throwing Error A +Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorA: Throwing Error A +returnValue returned: TestObject(14) +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnValueClass]. +returnValueNative returned: TestObject(15) +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnValueNativeClass]. +Recieved TestObject(16) +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$acceptValueClass]. +Recieved TestObject(17) +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$acceptValueNativeClass]. +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$tryCatchExitClass]. +returnFloat returned: 1.618 +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnFloatClass]. +returnFloatNative returned: 1.618 +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnFloatNativeClass]. +returnDouble returned: 3.14159628 +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnDoubleClass]. +returnDoubleNative returned: 3.14159628 +Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnDoubleNativeClass]. +Finished! diff --git a/test/989-method-trace-throw/info.txt b/test/989-method-trace-throw/info.txt new file mode 100644 index 0000000000..f0a200dc18 --- /dev/null +++ b/test/989-method-trace-throw/info.txt @@ -0,0 +1,15 @@ +Tests method tracing in JVMTI + +This test is sensitive to the internal implementations of: + * java.lang.Error + * java.lang.Integer + * java.lang.Math + * java.lang.String + * java.lang.System + * java.util.ArrayList + * java.util.Arrays + * java.util.StringBuilder + * all super-classes and super-interfaces of the above types. + +Changes to the internal implementation of these classes might (or might not) +change the output of this test. diff --git a/test/989-method-trace-throw/method_trace.cc b/test/989-method-trace-throw/method_trace.cc new file mode 100644 index 0000000000..554784effe --- /dev/null +++ b/test/989-method-trace-throw/method_trace.cc @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 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. + */ + +#include <inttypes.h> +#include <memory> +#include <stdio.h> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "ti_macros.h" + +namespace art { +namespace Test989StackTraceThrow { + +extern "C" JNIEXPORT +jfloat JNICALL Java_art_Test989_returnFloatNative(JNIEnv* env, jclass klass) { + jmethodID targetMethod = env->GetStaticMethodID(klass, "doGetFloat", "()F"); + return env->CallStaticFloatMethod(klass, targetMethod); +} +extern "C" JNIEXPORT +jdouble JNICALL Java_art_Test989_returnDoubleNative(JNIEnv* env, jclass klass) { + jmethodID targetMethod = env->GetStaticMethodID(klass, "doGetDouble", "()D"); + return env->CallStaticDoubleMethod(klass, targetMethod); +} + +extern "C" JNIEXPORT jobject JNICALL Java_art_Test989_returnValueNative(JNIEnv* env, jclass klass) { + jmethodID targetMethod = env->GetStaticMethodID(klass, "mkTestObject", "()Ljava/lang/Object;"); + return env->CallStaticObjectMethod(klass, targetMethod); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test989_doNothingNative(JNIEnv* env ATTRIBUTE_UNUSED, + jclass klass ATTRIBUTE_UNUSED) { + return; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test989_throwANative(JNIEnv* env, + jclass klass) { + jmethodID targetMethod = env->GetStaticMethodID(klass, "doThrowA", "()V"); + env->CallStaticVoidMethod(klass, targetMethod); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test989_acceptValueNative(JNIEnv* env, + jclass klass, + jobject arg) { + jmethodID targetMethod = env->GetStaticMethodID(klass, "printObject", "(Ljava/lang/Object;)V"); + env->CallStaticVoidMethod(klass, targetMethod, arg); +} + +} // namespace Test989StackTraceThrow +} // namespace art + diff --git a/test/989-method-trace-throw/run b/test/989-method-trace-throw/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/989-method-trace-throw/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/989-method-trace-throw/src/Main.java b/test/989-method-trace-throw/src/Main.java new file mode 100644 index 0000000000..29b9de1027 --- /dev/null +++ b/test/989-method-trace-throw/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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 { + public static void main(String[] args) throws Exception { + art.Test989.run(); + } +} diff --git a/test/989-method-trace-throw/src/art/Test989.java b/test/989-method-trace-throw/src/art/Test989.java new file mode 100644 index 0000000000..18421bd08b --- /dev/null +++ b/test/989-method-trace-throw/src/art/Test989.java @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2011 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. + */ + +package art; + +import java.lang.reflect.Method; +import java.util.Set; +import java.util.HashSet; + +public class Test989 { + static boolean PRINT_STACK_TRACE = false; + static Set<Method> testMethods = new HashSet<>(); + + static MethodTracer currentTracer = new MethodTracer() { + public void methodEntry(Object o) { return; } + public void methodExited(Object o, boolean e, Object r) { return; } + }; + + private static boolean DISABLE_TRACING = false; + + static { + try { + testMethods.add(Test989.class.getDeclaredMethod("doNothing")); + testMethods.add(Test989.class.getDeclaredMethod("doNothingNative")); + testMethods.add(Test989.class.getDeclaredMethod("throwA")); + testMethods.add(Test989.class.getDeclaredMethod("throwANative")); + testMethods.add(Test989.class.getDeclaredMethod("returnFloat")); + testMethods.add(Test989.class.getDeclaredMethod("returnFloatNative")); + testMethods.add(Test989.class.getDeclaredMethod("returnDouble")); + testMethods.add(Test989.class.getDeclaredMethod("returnDoubleNative")); + testMethods.add(Test989.class.getDeclaredMethod("returnValue")); + testMethods.add(Test989.class.getDeclaredMethod("returnValueNative")); + testMethods.add(Test989.class.getDeclaredMethod("acceptValue", Object.class)); + testMethods.add(Test989.class.getDeclaredMethod("acceptValueNative", Object.class)); + testMethods.add(Test989.class.getDeclaredMethod("tryCatchExit")); + } catch (Exception e) { + throw new Error("Bad static!", e); + } + } + + // Disables tracing only on RI. Used to work around an annoying piece of behavior where in the + // RI throwing an exception in an exit hook causes the exit hook to be re-executed. This leads + // to an infinite loop on the RI. + private static void disableTraceForRI() { + if (!System.getProperty("java.vm.name").equals("Dalvik")) { + Trace.disableMethodTracing(Thread.currentThread()); + } + } + + private static String getInfo(Object m, boolean exception, Object result) { + String out = m.toString() + " returned "; + if (exception) { + out += "<exception>"; + } else { + out += result; + } + return out; + } + + public static interface MethodTracer { + public void methodEntry(Object m); + public void methodExited(Object m, boolean exception, Object result); + public default Class<?> entryException() { return null; } + public default Class<?> exitException() { return null; } + } + + public static class NormalTracer implements MethodTracer { + public void methodEntry(Object m) { + if (testMethods.contains(m)) { + System.out.println("Normal: Entering " + m); + } + } + public void methodExited(Object m, boolean exception, Object result) { + if (testMethods.contains(m)) { + System.out.println("Normal: Leaving " + getInfo(m, exception, result)); + } + } + } + + public static class ThrowEnterTracer implements MethodTracer { + public void methodEntry(Object m) { + if (testMethods.contains(m)) { + System.out.println("ThrowEnter: Entering " + m); + throw new ErrorB("Throwing error while entering " + m); + } + } + public void methodExited(Object m, boolean exception, Object result) { + if (testMethods.contains(m)) { + System.out.println("ThrowEnter: Leaving " + getInfo(m, exception, result)); + } + } + public Class<?> entryException() { return ErrorB.class; } + } + + public static class ThrowExitTracer implements MethodTracer { + public void methodEntry(Object m) { + if (testMethods.contains(m)) { + System.out.println("ThrowExit: Entering " + m); + } + } + public void methodExited(Object m, boolean exception, Object result) { + if (testMethods.contains(m)) { + // The RI goes into an infinite loop if we throw exceptions in an ExitHook. See + // disableTraceForRI for explanation. + disableTraceForRI(); + System.out.println("ThrowExit: Leaving " + getInfo(m, exception, result)); + throw new ErrorB("Throwing error while exit " + getInfo(m, exception, result)); + } + } + public Class<?> exitException() { return ErrorB.class; } + } + + public static class ThrowBothTracer implements MethodTracer { + public void methodEntry(Object m) { + if (testMethods.contains(m)) { + System.out.println("ThrowBoth: Entering " + m); + throw new ErrorB("Throwing error while entering " + m); + } + } + public void methodExited(Object m, boolean exception, Object result) { + if (testMethods.contains(m)) { + // The RI goes into an infinite loop if we throw exceptions in an ExitHook. See + // disableTraceForRI for explanation. + disableTraceForRI(); + System.out.println("ThrowBoth: Leaving " + getInfo(m, exception, result)); + throw new ErrorC("Throwing error while exit " + getInfo(m, exception, result)); + } + } + public Class<?> entryException() { return ErrorB.class; } + public Class<?> exitException() { return ErrorC.class; } + } + + public static class ForceGCTracer implements MethodTracer { + public void methodEntry(Object m) { + if (System.getProperty("java.vm.name").equals("Dalvik")) { + System.gc(); + } + } + public void methodExited(Object m, boolean exception, Object result) { + if (System.getProperty("java.vm.name").equals("Dalvik")) { + System.gc(); + } + } + } + + private static void maybeDisableTracing() throws Exception { + if (DISABLE_TRACING) { + Trace.disableMethodTracing(Thread.currentThread()); + } + } + + public static void baseNotifyMethodEntry(Object o) { + currentTracer.methodEntry(o); + } + public static void baseNotifyMethodExit(Object o, boolean exception, Object res) { + currentTracer.methodExited(o, exception, res); + } + + private static void setupTracing() throws Exception { + Trace.enableMethodTracing( + Test989.class, + Test989.class.getDeclaredMethod("baseNotifyMethodEntry", Object.class), + Test989.class.getDeclaredMethod( + "baseNotifyMethodExit", Object.class, Boolean.TYPE, Object.class), + Thread.currentThread()); + } + private static void setEntry(MethodTracer type) throws Exception { + if (DISABLE_TRACING || !System.getProperty("java.vm.name").equals("Dalvik")) { + Trace.disableMethodTracing(Thread.currentThread()); + setupTracing(); + } + currentTracer = type; + } + + private static String testDescription(MethodTracer type, Runnable test) { + return "test[" + type.getClass() + ", " + test.getClass() + "]"; + } + + private static Class<?> getExpectedError(MethodTracer t, MyRunnable r) { + if (t.exitException() != null) { + return t.exitException(); + } else if (t.entryException() != null) { + return t.entryException(); + } else { + return r.expectedThrow(); + } + } + + private static void doTest(MethodTracer type, MyRunnable test) throws Exception { + Class<?> expected = getExpectedError(type, test); + + setEntry(type); + try { + test.run(); + // Disabling method tracing just makes this test somewhat faster. + maybeDisableTracing(); + if (expected == null) { + System.out.println( + "Received no exception as expected for " + testDescription(type, test) + "."); + return; + } + } catch (Error t) { + // Disabling method tracing just makes this test somewhat faster. + maybeDisableTracing(); + if (expected == null) { + throw new Error("Unexpected error occured: " + t + " for " + testDescription(type, test), t); + } else if (!expected.isInstance(t)) { + throw new Error("Expected error of type " + expected + " not " + t + + " for " + testDescription(type, test), t); + } else { + System.out.println( + "Received expected error for " + testDescription(type, test) + " - " + t); + if (PRINT_STACK_TRACE) { + t.printStackTrace(); + } + return; + } + } + System.out.println("Expected an error of type " + expected + " but got no exception for " + + testDescription(type, test)); + // throw new Error("Expected an error of type " + expected + " but got no exception for " + // + testDescription(type, test)); + } + + public static interface MyRunnable extends Runnable { + public default Class<?> expectedThrow() { + return null; + } + } + + public static void run() throws Exception { + MyRunnable[] testCases = new MyRunnable[] { + new doNothingClass(), + new doNothingNativeClass(), + new throwAClass(), + new throwANativeClass(), + new returnValueClass(), + new returnValueNativeClass(), + new acceptValueClass(), + new acceptValueNativeClass(), + new tryCatchExitClass(), + new returnFloatClass(), + new returnFloatNativeClass(), + new returnDoubleClass(), + new returnDoubleNativeClass(), + }; + MethodTracer[] tracers = new MethodTracer[] { + new NormalTracer(), + new ThrowEnterTracer(), + new ThrowExitTracer(), + new ThrowBothTracer(), + new ForceGCTracer(), + }; + + setupTracing(); + for (MethodTracer t : tracers) { + for (MyRunnable r : testCases) { + doTest(t, r); + } + } + + maybeDisableTracing(); + System.out.println("Finished!"); + Trace.disableMethodTracing(Thread.currentThread()); + } + + private static final class throwAClass implements MyRunnable { + public void run() { + throwA(); + } + @Override + public Class<?> expectedThrow() { + return ErrorA.class; + } + } + + private static final class throwANativeClass implements MyRunnable { + public void run() { + throwANative(); + } + @Override + public Class<?> expectedThrow() { + return ErrorA.class; + } + } + + private static final class tryCatchExitClass implements MyRunnable { + public void run() { + tryCatchExit(); + } + } + + private static final class doNothingClass implements MyRunnable { + public void run() { + doNothing(); + } + } + + private static final class doNothingNativeClass implements MyRunnable { + public void run() { + doNothingNative(); + } + } + + private static final class acceptValueClass implements MyRunnable { + public void run() { + acceptValue(mkTestObject()); + } + } + + private static final class acceptValueNativeClass implements MyRunnable { + public void run() { + acceptValueNative(mkTestObject()); + } + } + + private static final class returnValueClass implements MyRunnable { + public void run() { + Object o = returnValue(); + System.out.println("returnValue returned: " + o); + } + } + + private static final class returnValueNativeClass implements MyRunnable { + public void run() { + Object o = returnValueNative(); + System.out.println("returnValueNative returned: " + o); + } + } + + private static final class returnFloatClass implements MyRunnable { + public void run() { + float d = returnFloat(); + System.out.println("returnFloat returned: " + d); + } + } + + private static final class returnFloatNativeClass implements MyRunnable { + public void run() { + float d = returnFloatNative(); + System.out.println("returnFloatNative returned: " + d); + } + } + + private static final class returnDoubleClass implements MyRunnable { + public void run() { + double d = returnDouble(); + System.out.println("returnDouble returned: " + d); + } + } + + private static final class returnDoubleNativeClass implements MyRunnable { + public void run() { + double d = returnDoubleNative(); + System.out.println("returnDoubleNative returned: " + d); + } + } + + private static class ErrorA extends Error { + private static final long serialVersionUID = 0; + public ErrorA(String s) { super(s); } + } + + private static class ErrorB extends Error { + private static final long serialVersionUID = 1; + public ErrorB(String s) { super(s); } + } + + private static class ErrorC extends Error { + private static final long serialVersionUID = 2; + public ErrorC(String s) { super(s); } + } + + // Does nothing. + public static void doNothing() { } + + public static void tryCatchExit() { + try { + Object o = mkTestObject(); + return; + } catch (ErrorB b) { + System.out.println("ERROR: Caught " + b); + b.printStackTrace(); + } catch (ErrorC c) { + System.out.println("ERROR: Caught " + c); + c.printStackTrace(); + } + } + + public static float returnFloat() { + return doGetFloat(); + } + + public static double returnDouble() { + return doGetDouble(); + } + + // Throws an ErrorA. + public static void throwA() { + doThrowA(); + } + + public static void doThrowA() { + throw new ErrorA("Throwing Error A"); + } + + static final class TestObject { + private int idx; + public TestObject(int v) { + this.idx = v; + } + @Override + public String toString() { + return "TestObject(" + idx + ")"; + } + } + + static int counter = 0; + public static Object mkTestObject() { + return new TestObject(counter++); + } + + public static void printObject(Object o) { + System.out.println("Recieved " + o); + } + + // Returns a newly allocated value. + public static Object returnValue() { + return mkTestObject(); + } + + public static void acceptValue(Object o) { + printObject(o); + } + + public static float doGetFloat() { + return 1.618f; + } + + public static double doGetDouble() { + return 3.14159628; + } + + // Calls mkTestObject from native code and returns it. + public static native Object returnValueNative(); + // Calls printObject from native code. + public static native void acceptValueNative(Object t); + public static native void doNothingNative(); + public static native void throwANative(); + public static native float returnFloatNative(); + public static native double returnDoubleNative(); +} diff --git a/test/989-method-trace-throw/src/art/Trace.java b/test/989-method-trace-throw/src/art/Trace.java new file mode 100644 index 0000000000..3370996df3 --- /dev/null +++ b/test/989-method-trace-throw/src/art/Trace.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Method; + +public class Trace { + public static native void enableMethodTracing( + Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr); + public static native void disableMethodTracing(Thread thr); +} diff --git a/test/Android.bp b/test/Android.bp index 599b0115b4..35c3d9c332 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -278,6 +278,7 @@ art_cc_defaults { "984-obsolete-invoke/obsolete_invoke.cc", "986-native-method-bind/native_bind.cc", "987-agent-bind/agent_bind.cc", + "989-method-trace-throw/method_trace.cc", ], shared_libs: [ "libbase", diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 7677025c29..8aacc8c9b7 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -63,6 +63,8 @@ TEST_VDEX="n" TEST_IS_NDEBUG="n" APP_IMAGE="y" JVMTI_STRESS="n" +JVMTI_TRACE_STRESS="n" +JVMTI_REDEFINE_STRESS="n" VDEX_FILTER="" PROFILE="n" RANDOM_PROFILE="n" @@ -151,10 +153,15 @@ while true; do elif [ "x$1" = "x--prebuild" ]; then PREBUILD="y" shift - elif [ "x$1" = "x--jvmti-stress" ]; then - # APP_IMAGE doesn't really work with jvmti-torture + elif [ "x$1" = "x--jvmti-redefine-stress" ]; then + # APP_IMAGE doesn't really work with jvmti redefine stress APP_IMAGE="n" JVMTI_STRESS="y" + JVMTI_REDEFINE_STRESS="y" + shift + elif [ "x$1" = "x--jvmti-trace-stress" ]; then + JVMTI_STRESS="y" + JVMTI_TRACE_STRESS="y" shift elif [ "x$1" = "x--no-app-image" ]; then APP_IMAGE="n" @@ -397,13 +404,25 @@ if [[ "$JVMTI_STRESS" = "y" ]]; then plugin=libopenjdkjvmti.so fi - file_1=$(mktemp --tmpdir=${DEX_LOCATION}) - file_2=$(mktemp --tmpdir=${DEX_LOCATION}) + # Just give it a default start so we can always add ',' to it. + agent_args="jvmti-stress" + if [[ "$JVMTI_REDEFINE_STRESS" = "y" ]]; then + # We really cannot do this on RI so don't both passing it in that case. + if [[ "$USE_JVM" = "n" ]]; then + file_1=$(mktemp --tmpdir=${DEX_LOCATION}) + file_2=$(mktemp --tmpdir=${DEX_LOCATION}) + # TODO Remove need for DEXTER_BINARY! + agent_args="${agent_args},redefine,${DEXTER_BINARY},${file_1},${file_2}" + fi + fi + if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then + agent_args="${agent_args},trace" + fi + # In the future add onto this; if [[ "$USE_JVM" = "y" ]]; then - FLAGS="${FLAGS} -agentpath:${ANDROID_HOST_OUT}/nativetest64/${agent}=/bin/false,${file_1},${file_2}" + FLAGS="${FLAGS} -agentpath:${ANDROID_HOST_OUT}/nativetest64/${agent}=${agent_args}" else - # TODO Remove need for DEXTER_BINARY! - FLAGS="${FLAGS} -agentpath:${agent}=${DEXTER_BINARY},${file_1},${file_2}" + FLAGS="${FLAGS} -agentpath:${agent}=${agent_args}" if [ "$IS_JVMTI_TEST" = "n" ]; then FLAGS="${FLAGS} -Xplugin:${plugin}" FLAGS="${FLAGS} -Xcompiler-option --debuggable" diff --git a/test/knownfailures.json b/test/knownfailures.json index dee3b8dbc9..b1b767c277 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -511,7 +511,7 @@ "645-checker-abs-simd", "706-checker-scheduler"], "description": ["Checker tests are not compatible with jvmti."], - "variant": "jvmti-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress" }, { "tests": [ @@ -519,7 +519,7 @@ "964-default-iface-init-gen" ], "description": ["Tests that just take too long with jvmti-stress"], - "variant": "jvmti-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress" }, { "tests": [ @@ -539,7 +539,7 @@ "dexter/slicer." ], "bug": "b/37272822", - "variant": "jvmti-stress" + "variant": "jvmti-stress | redefine-stress" }, { "tests": [ @@ -550,7 +550,7 @@ "981-dedup-original-dex" ], "description": ["Tests that require exact knowledge of the number of plugins and agents."], - "variant": "jvmti-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress" }, { "tests": [ @@ -564,7 +564,7 @@ "description": [ "Tests that use illegal dex files or otherwise break dexter assumptions" ], - "variant": "jvmti-stress" + "variant": "jvmti-stress | redefine-stress" }, { "tests": [ @@ -581,7 +581,7 @@ "Tests that use custom class loaders or other features not supported ", "by our JVMTI implementation" ], - "variant": "jvmti-stress" + "variant": "jvmti-stress | redefine-stress" }, { "tests": [ @@ -592,7 +592,7 @@ "Tests that use annotations and debug data that is not kept around by dexter." ], "bug": "b/37239009", - "variant": "jvmti-stress" + "variant": "jvmti-stress | redefine-stress" }, { "tests": [ @@ -701,6 +701,11 @@ "env_vars": {"SANITIZE_HOST": "address"} }, { + "tests": ["988-method-trace"], + "variant": "redefine-stress | jvmti-stress", + "description": "Test disabled due to redefine-stress disabling intrinsics which changes the trace output slightly." + }, + { "tests": "137-cfi", "description": [ "ASan is reporting out-of-bounds reads in libunwind."], "variant": "host", diff --git a/test/run-test b/test/run-test index 08be213c89..41a0dc2a84 100755 --- a/test/run-test +++ b/test/run-test @@ -137,7 +137,8 @@ trace_stream="false" basic_verify="false" gc_verify="false" gc_stress="false" -jvmti_stress="false" +jvmti_trace_stress="false" +jvmti_redefine_stress="false" strace="false" always_clean="no" never_clean="no" @@ -234,8 +235,11 @@ while true; do basic_verify="true" gc_stress="true" shift - elif [ "x$1" = "x--jvmti-stress" ]; then - jvmti_stress="true" + elif [ "x$1" = "x--jvmti-redefine-stress" ]; then + jvmti_redefine_stress="true" + shift + elif [ "x$1" = "x--jvmti-trace-stress" ]; then + jvmti_trace_stress="true" shift elif [ "x$1" = "x--suspend-timeout" ]; then shift @@ -447,8 +451,11 @@ fi if [ "$gc_stress" = "true" ]; then run_args="${run_args} --gc-stress --runtime-option -Xgc:gcstress --runtime-option -Xms2m --runtime-option -Xmx16m" fi -if [ "$jvmti_stress" = "true" ]; then - run_args="${run_args} --no-app-image --jvmti-stress" +if [ "$jvmti_redefine_stress" = "true" ]; then + run_args="${run_args} --no-app-image --jvmti-redefine-stress" +fi +if [ "$jvmti_trace_stress" = "true" ]; then + run_args="${run_args} --no-app-image --jvmti-trace-stress" fi if [ "$trace" = "true" ]; then run_args="${run_args} --runtime-option -Xmethod-trace --runtime-option -Xmethod-trace-file-size:2000000" @@ -658,7 +665,9 @@ if [ "$usage" = "yes" ]; then echo " --stream Run method tracing in streaming mode (requires --trace)" echo " --gcstress Run with gc stress testing" echo " --gcverify Run with gc verification" - echo " --jvmti-stress Run with jvmti stress testing" + echo " --jvmti-trace-stress Run with jvmti method tracing stress testing" + echo " --jvmti-redefine-stress" + echo " Run with jvmti method redefinition stress testing" echo " --always-clean Delete the test files even if the test fails." echo " --never-clean Keep the test files even if the test succeeds." echo " --android-root [path] The path on target for the android root. (/system by default)." diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index 77ef25a75b..344507115b 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -147,7 +147,7 @@ def gather_test_info(): VARIANT_TYPE_DICT['relocate'] = {'relocate-npatchoat', 'relocate', 'no-relocate'} VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'} VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'} - VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress'} + VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress'} VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'optimizing', 'regalloc_gc', 'speed-profile'} @@ -437,7 +437,11 @@ def run_tests(tests): options_test += ' --debuggable' if jvmti == 'jvmti-stress': - options_test += ' --jvmti-stress' + options_test += ' --jvmti-trace-stress --jvmti-redefine-stress' + elif jvmti == 'trace-stress': + options_test += ' --jvmti-trace-stress' + elif jvmti == 'redefine-stress': + options_test += ' --jvmti-redefine-stress' if address_size == '64': options_test += ' --64' @@ -954,6 +958,10 @@ def parse_option(): IMAGE_TYPES.add('multipicimage') if options['jvmti_stress']: JVMTI_TYPES.add('jvmti-stress') + if options['redefine_stress']: + JVMTI_TYPES.add('redefine-stress') + if options['trace_stress']: + JVMTI_TYPES.add('trace-stress') if options['no_jvmti']: JVMTI_TYPES.add('no-jvmti') if options['verbose']: diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index bfd4d254f4..6eaa5c37df 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -69,6 +69,214 @@ static void throwCommonRedefinitionError(jvmtiEnv* jvmti, env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); } +namespace common_trace { + +// Taken from art/runtime/modifiers.h +static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic + +struct TraceData { + jclass test_klass; + jmethodID enter_method; + jmethodID exit_method; + bool in_callback; +}; + +static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) { + jint mods = 0; + if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodModifiers(m, &mods))) { + return nullptr; + } + + bool is_static = (mods & kAccStatic) != 0; + jclass method_klass = nullptr; + if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodDeclaringClass(m, &method_klass))) { + return nullptr; + } + jobject res = env->ToReflectedMethod(method_klass, m, is_static); + env->DeleteLocalRef(method_klass); + return res; +} + +static jobject GetJavaValue(jvmtiEnv* jvmtienv, + JNIEnv* env, + jmethodID m, + jvalue value) { + char *fname, *fsig, *fgen; + if (JvmtiErrorToException(env, jvmtienv, jvmtienv->GetMethodName(m, &fname, &fsig, &fgen))) { + return nullptr; + } + std::string type(fsig); + type = type.substr(type.find(")") + 1); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen)); + std::string name; + switch (type[0]) { + case 'V': + return nullptr; + case '[': + case 'L': + return value.l; + case 'Z': + name = "java/lang/Boolean"; + break; + case 'B': + name = "java/lang/Byte"; + break; + case 'C': + name = "java/lang/Character"; + break; + case 'S': + name = "java/lang/Short"; + break; + case 'I': + name = "java/lang/Integer"; + break; + case 'J': + name = "java/lang/Long"; + break; + case 'F': + name = "java/lang/Float"; + break; + case 'D': + name = "java/lang/Double"; + break; + default: + LOG(FATAL) << "Unable to figure out type!"; + return nullptr; + } + std::ostringstream oss; + oss << "(" << type[0] << ")L" << name << ";"; + std::string args = oss.str(); + jclass target = env->FindClass(name.c_str()); + jmethodID valueOfMethod = env->GetStaticMethodID(target, "valueOf", args.c_str()); + + CHECK(valueOfMethod != nullptr) << args; + jobject res = env->CallStaticObjectMethodA(target, valueOfMethod, &value); + env->DeleteLocalRef(target); + return res; +} + +static void methodExitCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jmethodID method, + jboolean was_popped_by_exception, + jvalue return_value) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (method == data->exit_method || method == data->enter_method || data->in_callback) { + // Don't do callback for either of these to prevent an infinite loop. + return; + } + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jobject result = + was_popped_by_exception ? nullptr : GetJavaValue(jvmti, jnienv, method, return_value); + if (jnienv->ExceptionCheck()) { + data->in_callback = false; + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, + data->exit_method, + method_arg, + was_popped_by_exception, + result); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + +static void methodEntryCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thr ATTRIBUTE_UNUSED, + jmethodID method) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (method == data->exit_method || method == data->enter_method || data->in_callback) { + // Don't do callback for either of these to prevent an infinite loop. + return; + } + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + if (jnienv->ExceptionCheck()) { + return; + } + jnienv->CallStaticVoidMethod(data->test_klass, data->enter_method, method_arg); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableMethodTracing( + JNIEnv* env, + jclass trace ATTRIBUTE_UNUSED, + jclass klass, + jobject enter, + jobject exit, + jthread thr) { + TraceData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(TraceData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(TraceData)); + data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass)); + data->enter_method = env->FromReflectedMethod(enter); + data->exit_method = env->FromReflectedMethod(exit); + data->in_callback = false; + + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { + return; + } + + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.MethodEntry = methodEntryCB; + cb.MethodExit = methodExitCB; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_METHOD_ENTRY, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_METHOD_EXIT, + thr))) { + return; + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableMethodTracing( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) { + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_METHOD_ENTRY, + thr))) { + return; + } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_METHOD_EXIT, + thr))) { + return; + } +} + +} // namespace common_trace + namespace common_redefine { static void throwRedefinitionError(jvmtiEnv* jvmti, diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc index e8e3cc7fa2..497db1cd3e 100644 --- a/test/ti-stress/stress.cc +++ b/test/ti-stress/stress.cc @@ -20,6 +20,7 @@ #include <fstream> #include <stdio.h> #include <sstream> +#include <strstream> #include "jvmti.h" #include "exec_utils.h" @@ -35,6 +36,8 @@ struct StressData { std::string out_temp_dex; std::string in_temp_dex; bool vm_class_loader_initialized; + bool trace_stress; + bool redefine_stress; }; static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) { @@ -95,7 +98,6 @@ static void doJvmtiMethodBind(jvmtiEnv* jvmtienv, if (thread == nullptr) { info.name = const_cast<char*>("<NULLPTR>"); } else if (jvmtienv->GetThreadInfo(thread, &info) != JVMTI_ERROR_NONE) { - LOG(WARNING) << "Unable to get thread info!"; info.name = const_cast<char*>("<UNKNOWN THREAD>"); } char *fname, *fsig, *fgen; @@ -115,8 +117,8 @@ static void doJvmtiMethodBind(jvmtiEnv* jvmtienv, env->DeleteLocalRef(klass); return; } - LOG(INFO) << "Loading native method \"" << cname << "->" << fname << fsig << "\". Thread is " - << info.name; + LOG(INFO) << "Loading native method \"" << cname << "->" << fname << fsig << "\". Thread is \"" + << info.name << "\""; jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname)); jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen)); jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname)); @@ -126,6 +128,151 @@ static void doJvmtiMethodBind(jvmtiEnv* jvmtienv, return; } +static std::string GetName(jvmtiEnv* jvmtienv, JNIEnv* jnienv, jobject obj) { + jclass klass = jnienv->GetObjectClass(obj); + char *cname, *cgen; + if (jvmtienv->GetClassSignature(klass, &cname, &cgen) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to get class name!"; + jnienv->DeleteLocalRef(klass); + return "<UNKNOWN>"; + } + std::string name(cname); + if (name == "Ljava/lang/String;") { + jstring str = reinterpret_cast<jstring>(obj); + const char* val = jnienv->GetStringUTFChars(str, nullptr); + if (val == nullptr) { + name += " (unable to get value)"; + } else { + std::ostringstream oss; + oss << name << " (value: \"" << val << "\")"; + name = oss.str(); + jnienv->ReleaseStringUTFChars(str, val); + } + } + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen)); + jnienv->DeleteLocalRef(klass); + return name; +} + +static std::string GetValOf(jvmtiEnv* env, JNIEnv* jnienv, std::string type, jvalue val) { + std::ostringstream oss; + switch (type[0]) { + case '[': + case 'L': + return val.l != nullptr ? GetName(env, jnienv, val.l) : "null"; + case 'Z': + return val.z == JNI_TRUE ? "true" : "false"; + case 'B': + oss << val.b; + return oss.str(); + case 'C': + oss << val.c; + return oss.str(); + case 'S': + oss << val.s; + return oss.str(); + case 'I': + oss << val.i; + return oss.str(); + case 'J': + oss << val.j; + return oss.str(); + case 'F': + oss << val.f; + return oss.str(); + case 'D': + oss << val.d; + return oss.str(); + case 'V': + return "<void>"; + default: + return "<ERROR Found type " + type + ">"; + } +} + +void JNICALL MethodExitHook(jvmtiEnv* jvmtienv, + JNIEnv* env, + jthread thread, + jmethodID m, + jboolean was_popped_by_exception, + jvalue val) { + jvmtiThreadInfo info; + if (thread == nullptr) { + info.name = const_cast<char*>("<NULLPTR>"); + } else if (jvmtienv->GetThreadInfo(thread, &info) != JVMTI_ERROR_NONE) { + // LOG(WARNING) << "Unable to get thread info!"; + info.name = const_cast<char*>("<UNKNOWN THREAD>"); + } + char *fname, *fsig, *fgen; + char *cname, *cgen; + jclass klass = nullptr; + if (jvmtienv->GetMethodDeclaringClass(m, &klass) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to get method declaring class!"; + return; + } + if (jvmtienv->GetMethodName(m, &fname, &fsig, &fgen) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to get method name!"; + env->DeleteLocalRef(klass); + return; + } + if (jvmtienv->GetClassSignature(klass, &cname, &cgen) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to get class name!"; + env->DeleteLocalRef(klass); + return; + } + std::string type(fsig); + type = type.substr(type.find(")") + 1); + std::string out_val(was_popped_by_exception ? "" : GetValOf(jvmtienv, env, type, val)); + LOG(INFO) << "Leaving method \"" << cname << "->" << fname << fsig << "\". Thread is \"" + << info.name << "\"." << std::endl + << " Cause: " << (was_popped_by_exception ? "exception" : "return ") + << out_val << "."; + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen)); + env->DeleteLocalRef(klass); +} + +void JNICALL MethodEntryHook(jvmtiEnv* jvmtienv, + JNIEnv* env, + jthread thread, + jmethodID m) { + jvmtiThreadInfo info; + if (thread == nullptr) { + info.name = const_cast<char*>("<NULLPTR>"); + } else if (jvmtienv->GetThreadInfo(thread, &info) != JVMTI_ERROR_NONE) { + info.name = const_cast<char*>("<UNKNOWN THREAD>"); + } + char *fname, *fsig, *fgen; + char *cname, *cgen; + jclass klass = nullptr; + if (jvmtienv->GetMethodDeclaringClass(m, &klass) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to get method declaring class!"; + return; + } + if (jvmtienv->GetMethodName(m, &fname, &fsig, &fgen) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to get method name!"; + env->DeleteLocalRef(klass); + return; + } + if (jvmtienv->GetClassSignature(klass, &cname, &cgen) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to get class name!"; + env->DeleteLocalRef(klass); + return; + } + LOG(INFO) << "Entering method \"" << cname << "->" << fname << fsig << "\". Thread is \"" + << info.name << "\""; + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig)); + jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen)); + env->DeleteLocalRef(klass); +} + // The hook we are using. void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti, JNIEnv* jni_env ATTRIBUTE_UNUSED, @@ -163,27 +310,57 @@ void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti, } } -// Options are ${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2} +static std::string AdvanceOption(const std::string& ops) { + return ops.substr(ops.find(',') + 1); +} + +static bool HasNextOption(const std::string& ops) { + return ops.find(',') != std::string::npos; +} + +static std::string GetOption(const std::string& in) { + return in.substr(0, in.find(',')); +} + +// Options are +// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace] static void ReadOptions(StressData* data, char* options) { std::string ops(options); - data->dexter_cmd = ops.substr(0, ops.find(',')); - ops = ops.substr(ops.find(',') + 1); - data->in_temp_dex = ops.substr(0, ops.find(',')); - ops = ops.substr(ops.find(',') + 1); - data->out_temp_dex = ops; + CHECK_EQ(GetOption(ops), "jvmti-stress") << "Options should start with jvmti-stress"; + do { + ops = AdvanceOption(ops); + std::string cur = GetOption(ops); + if (cur == "trace") { + data->trace_stress = true; + } else if (cur == "redefine") { + data->redefine_stress = true; + ops = AdvanceOption(ops); + data->dexter_cmd = GetOption(ops); + ops = AdvanceOption(ops); + data->in_temp_dex = GetOption(ops); + ops = AdvanceOption(ops); + data->out_temp_dex = GetOption(ops); + } else { + LOG(FATAL) << "Unknown option: " << GetOption(ops); + } + } while (HasNextOption(ops)); } -// We need to make sure that VMClassLoader is initialized before we start redefining anything since -// it can give (non-fatal) error messages if it's initialized after we've redefined BCP classes. -// These error messages are expected and no problem but they will mess up our testing -// infrastructure. -static void JNICALL EnsureVMClassloaderInitializedCB(jvmtiEnv *jvmti_env, - JNIEnv* jni_env, - jthread thread ATTRIBUTE_UNUSED) { +// Do final setup during the VMInit callback. By this time most things are all setup. +static void JNICALL PerformFinalSetupVMInit(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread ATTRIBUTE_UNUSED) { // Load the VMClassLoader class. We will get a ClassNotFound exception because we don't have // visibility but the class will be loaded behind the scenes. LOG(INFO) << "manual load & initialization of class java/lang/VMClassLoader!"; jclass klass = jni_env->FindClass("java/lang/VMClassLoader"); + StressData* data = nullptr; + CHECK_EQ(jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)), + JVMTI_ERROR_NONE); + // We need to make sure that VMClassLoader is initialized before we start redefining anything + // since it can give (non-fatal) error messages if it's initialized after we've redefined BCP + // classes. These error messages are expected and no problem but they will mess up our testing + // infrastructure. if (klass == nullptr) { // Probably on RI. Clear the exception so we can continue but don't mark vmclassloader as // initialized. @@ -193,11 +370,20 @@ static void JNICALL EnsureVMClassloaderInitializedCB(jvmtiEnv *jvmti_env, // GetMethodID is spec'd to cause the class to be initialized. jni_env->GetMethodID(klass, "hashCode", "()I"); jni_env->DeleteLocalRef(klass); - StressData* data = nullptr; - CHECK_EQ(jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)), - JVMTI_ERROR_NONE); data->vm_class_loader_initialized = true; } + if (data->trace_stress) { + if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_METHOD_ENTRY, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!"; + } + if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_METHOD_EXIT, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!"; + } + } } extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, @@ -233,7 +419,9 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, memset(&cb, 0, sizeof(cb)); cb.ClassFileLoadHook = ClassFileLoadHookSecretNoOp; cb.NativeMethodBind = doJvmtiMethodBind; - cb.VMInit = EnsureVMClassloaderInitializedCB; + cb.VMInit = PerformFinalSetupVMInit; + cb.MethodEntry = MethodEntryHook; + cb.MethodExit = MethodExitHook; if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set class file load hook cb!"; return 1; @@ -250,11 +438,13 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, LOG(ERROR) << "Unable to enable JVMTI_EVENT_VM_INIT event!"; return 1; } - if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, - nullptr) != JVMTI_ERROR_NONE) { - LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!"; - return 1; + if (data->redefine_stress) { + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, + nullptr) != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!"; + return 1; + } } return 0; } |