Merge "Add method tracing JVMTI callbacks"
diff --git a/runtime/Android.bp b/runtime/Android.bp
index d1e124f..26e52e0 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -245,7 +245,6 @@
"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/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 31a7f6a..307f9f0 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1630,8 +1630,10 @@
@ 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 @@
.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 @@
.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/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 18015b5..c9ead54 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2168,15 +2168,19 @@
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 @@
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
+ .cfi_adjust_cfa_offset -16
- POP_SAVE_REFS_ONLY_FRAME
-
+ 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/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 2222f5c..4fb3fe5 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -1948,17 +1948,23 @@
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 @@
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 @@
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 @@
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 41651d8..46d4f41 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 @@
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 @@
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 @@
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 7de8916..d591e09 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -664,7 +664,9 @@
}
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/entrypoints/quick/quick_instrumentation_entrypoints.cc b/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc
deleted file mode 100644
index 0d2f6d0..0000000
--- 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 6fcb711..b7cd39f 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 @@
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 @@
}
}
+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 9b9c70f..8120cc4 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -1123,25 +1123,40 @@
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,
+
+ // 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, 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);
-
- if (!interpreter_entry) {
- MethodEnterEvent(self, this_object, method, 0);
- }
}
-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 @@
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 @@
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 @@
// 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 @@
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 363985f..90b5def 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -432,9 +432,13 @@
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 4bc0f2f..85cf73b 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -254,6 +254,13 @@
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 32a2378..45788e7 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 @@
} \
} 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 @@
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 @@
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 @@
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 @@
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 @@
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/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 45773fd..3ec5b32 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1731,6 +1731,7 @@
}
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 4b83b7c..af85fb0 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -218,8 +218,8 @@
.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 57abf31..cb7e6a9 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 @@
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 @@
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 @@
}
}
+// 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 320c59c..90bc122 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 @@
}
}
+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 @@
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 @@
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 b9e3cf0..5f37dcf 100644
--- a/runtime/openjdkjvmti/events.h
+++ b/runtime/openjdkjvmti/events.h
@@ -29,6 +29,7 @@
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 @@
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 @@
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 @@
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/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt
new file mode 100644
index 0000000..8c67d66
--- /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 0000000..f0a200d
--- /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 0000000..51875a7
--- /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 0000000..9dd1142
--- /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 0000000..6ac7b11
--- /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 0000000..3370996
--- /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 0000000..0911bc3
--- /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 0000000..f0a200d
--- /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 0000000..554784e
--- /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 0000000..51875a7
--- /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 0000000..29b9de1
--- /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 0000000..18421bd
--- /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 0000000..3370996
--- /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 599b011..35c3d9c 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -278,6 +278,7 @@
"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 7677025..8aacc8c 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -63,6 +63,8 @@
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 @@
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 @@
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 dee3b8d..b1b767c 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 08be213..41a0dc2 100755
--- a/test/run-test
+++ b/test/run-test
@@ -137,7 +137,8 @@
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 @@
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 @@
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 @@
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 77ef25a..3445071 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -147,7 +147,7 @@
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 @@
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 @@
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 bfd4d25..6eaa5c3 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -69,6 +69,214 @@
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 e8e3cc7..497db1c 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
-// Options are ${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2}
-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;
+static std::string AdvanceOption(const std::string& ops) {
+ return ops.substr(ops.find(',') + 1);
}
-// 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) {
+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);
+ 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));
+}
+
+// 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 @@
// 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 @@
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 @@
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;
}