Ensure proxy methods are reported by instrumentation
Previously we would not report proxy methods to instrumentation in all
cases. Specifically if the instrumentation trampolines were not
installed we would never report proxy method entry or exit. This fixes
that and ensures that proxy methods are always reported to
instrumentation regardless of what instrumentation method is being
used. It also ensures that these methods will only ever be reported
once per entry.
Test: ./test.py --host
Test: ./test/testrunner/testrunner.py \
--host \
--run-test-option='--with-agent libtifast.so=MethodEntry,MethodExit'
Change-Id: I78f1d4b3b3f242e8915989ab26f7c204baab08e5
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 5cb4299..f71a5dc 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -504,8 +504,9 @@
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_EQ(
+ method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize)->GetReturnTypePrimitive(),
+ art::Primitive::kPrimNot) << method->PrettyMethod();
DCHECK(!self->IsExceptionPending());
jvalue val;
art::JNIEnvExt* jnienv = self->GetJniEnv();
@@ -530,8 +531,9 @@
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_NE(
+ method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize)->GetReturnTypePrimitive(),
+ art::Primitive::kPrimNot) << method->PrettyMethod();
DCHECK(!self->IsExceptionPending());
jvalue val;
art::JNIEnvExt* jnienv = self->GetJniEnv();
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index a4dea37..379292d 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -948,8 +948,36 @@
jobject interface_method_jobj = soa.AddLocalReference<jobject>(interface_reflect_method);
// All naked Object*s should now be in jobjects, so its safe to go into the main invoke code
- // that performs allocations.
+ // that performs allocations or instrumentation events.
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ if (instr->HasMethodEntryListeners()) {
+ instr->MethodEnterEvent(soa.Self(),
+ soa.Decode<mirror::Object>(rcvr_jobj).Ptr(),
+ proxy_method,
+ 0);
+ if (soa.Self()->IsExceptionPending()) {
+ instr->MethodUnwindEvent(self,
+ soa.Decode<mirror::Object>(rcvr_jobj).Ptr(),
+ proxy_method,
+ 0);
+ return 0;
+ }
+ }
JValue result = InvokeProxyInvocationHandler(soa, shorty, rcvr_jobj, interface_method_jobj, args);
+ if (soa.Self()->IsExceptionPending()) {
+ if (instr->HasMethodUnwindListeners()) {
+ instr->MethodUnwindEvent(self,
+ soa.Decode<mirror::Object>(rcvr_jobj).Ptr(),
+ proxy_method,
+ 0);
+ }
+ } else if (instr->HasMethodExitListeners()) {
+ instr->MethodExitEvent(self,
+ soa.Decode<mirror::Object>(rcvr_jobj).Ptr(),
+ proxy_method,
+ 0,
+ result);
+ }
return result.GetJ();
}
diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt
index 6e16722..75ee112 100644
--- a/test/988-method-trace/expected.txt
+++ b/test/988-method-trace/expected.txt
@@ -145,10 +145,10 @@
......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
......<= private static 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
- art.Test988.iter_fibonacci(Test988.java:235)
- art.Test988$IterOp.applyAsInt(Test988.java:230)
- art.Test988.doFibTest(Test988.java:339)
- art.Test988.run(Test988.java:304)
+ art.Test988.iter_fibonacci(Test988.java:255)
+ art.Test988$IterOp.applyAsInt(Test988.java:250)
+ art.Test988.doFibTest(Test988.java:378)
+ art.Test988.run(Test988.java:336)
<additional hidden frames>
>
....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -165,10 +165,10 @@
...<= 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
- art.Test988.iter_fibonacci(Test988.java:235)
- art.Test988$IterOp.applyAsInt(Test988.java:230)
- art.Test988.doFibTest(Test988.java:339)
- art.Test988.run(Test988.java:304)
+ art.Test988.iter_fibonacci(Test988.java:255)
+ art.Test988$IterOp.applyAsInt(Test988.java:250)
+ art.Test988.doFibTest(Test988.java:378)
+ art.Test988.run(Test988.java:336)
<additional hidden frames>
.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
@@ -248,10 +248,10 @@
......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
......<= private static 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
- art.Test988.fibonacci(Test988.java:257)
- art.Test988$RecurOp.applyAsInt(Test988.java:252)
- art.Test988.doFibTest(Test988.java:339)
- art.Test988.run(Test988.java:305)
+ art.Test988.fibonacci(Test988.java:277)
+ art.Test988$RecurOp.applyAsInt(Test988.java:272)
+ art.Test988.doFibTest(Test988.java:378)
+ art.Test988.run(Test988.java:337)
<additional hidden frames>
>
....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -268,14 +268,20 @@
...<= 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
- art.Test988.fibonacci(Test988.java:257)
- art.Test988$RecurOp.applyAsInt(Test988.java:252)
- art.Test988.doFibTest(Test988.java:339)
- art.Test988.run(Test988.java:305)
+ art.Test988.fibonacci(Test988.java:277)
+ art.Test988$RecurOp.applyAsInt(Test988.java:272)
+ art.Test988.doFibTest(Test988.java:378)
+ art.Test988.run(Test988.java:337)
<additional hidden frames>
.<= 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 final void <non-deterministic-type 0>.run()
+.=> private static java.lang.Object java.lang.reflect.Proxy.invoke(java.lang.reflect.Proxy,java.lang.reflect.Method,java.lang.Object[]) throws java.lang.Throwable
+..=> public java.lang.Object art.Test988$TestRunnableInvokeHandler.invoke(java.lang.Object,java.lang.reflect.Method,java.lang.Object[]) throws java.lang.Throwable
+..<= public java.lang.Object art.Test988$TestRunnableInvokeHandler.invoke(java.lang.Object,java.lang.reflect.Method,java.lang.Object[]) throws java.lang.Throwable -> <null: null>
+.<= private static java.lang.Object java.lang.reflect.Proxy.invoke(java.lang.reflect.Proxy,java.lang.reflect.Method,java.lang.Object[]) throws java.lang.Throwable -> <null: null>
+<= public final void <non-deterministic-type 0>.run() -> <null: null>
=> static void art.Test988$IntrinsicsTest.doTest()
.=> static void art.Test988Intrinsics.test()
..=> public static long java.lang.Double.doubleToRawLongBits(double)
diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java
index d7eda52..5720d1d 100644
--- a/test/988-method-trace/src/art/Test988.java
+++ b/test/988-method-trace/src/art/Test988.java
@@ -18,20 +18,25 @@
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
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;
+import java.util.function.IntUnaryOperator;
public class Test988 {
// Methods with non-deterministic output that should not be printed.
static Set<Method> NON_DETERMINISTIC_OUTPUT_METHODS = new HashSet<>();
static Set<Method> NON_DETERMINISTIC_OUTPUT_TYPE_METHODS = new HashSet<>();
+ static List<Class<?>> NON_DETERMINISTIC_TYPE_NAMES = new ArrayList<>();
static {
try {
@@ -42,6 +47,10 @@
NON_DETERMINISTIC_OUTPUT_METHODS.add(Thread.class.getDeclaredMethod("currentThread"));
NON_DETERMINISTIC_OUTPUT_TYPE_METHODS.add(Thread.class.getDeclaredMethod("currentThread"));
} catch (Exception e) {}
+ try {
+ NON_DETERMINISTIC_TYPE_NAMES.add(
+ Proxy.getProxyClass(Test988.class.getClassLoader(), new Class[] { Runnable.class }));
+ } catch (Exception e) {}
}
static interface Printable {
@@ -49,9 +58,9 @@
}
static final class MethodEntry implements Printable {
- private Object m;
+ private Executable m;
private int cnt;
- public MethodEntry(Object m, int cnt) {
+ public MethodEntry(Executable m, int cnt) {
this.m = m;
this.cnt = cnt;
}
@@ -124,18 +133,26 @@
}
}
- static String methodToString(Object m) {
+ static String methodToString(Executable m) {
// Make the output more similar between ART and RI,
// by removing the 'native' specifier from methods.
- String methodStr = m.toString();
+ String methodStr;
+ if (NON_DETERMINISTIC_TYPE_NAMES.contains(m.getDeclaringClass())) {
+ methodStr = m.toString().replace(m.getDeclaringClass().getName(),
+ "<non-deterministic-type " +
+ NON_DETERMINISTIC_TYPE_NAMES.indexOf(m.getDeclaringClass()) +
+ ">");
+ } else {
+ methodStr = m.toString();
+ }
return methodStr.replaceFirst(" native", "");
}
static final class MethodReturn implements Printable {
- private Object m;
+ private Executable m;
private Object val;
private int cnt;
- public MethodReturn(Object m, Object val, int cnt) {
+ public MethodReturn(Executable m, Object val, int cnt) {
this.m = m;
this.val = val;
this.cnt = cnt;
@@ -155,6 +172,9 @@
String klass_print;
if (klass == null) {
klass_print = "null";
+ } else if (NON_DETERMINISTIC_TYPE_NAMES.contains(klass)) {
+ klass_print = "<non-deterministic-class " +
+ NON_DETERMINISTIC_TYPE_NAMES.indexOf(klass) + ">";
} else if (NON_DETERMINISTIC_OUTPUT_TYPE_METHODS.contains(m)) {
klass_print = "<non-deterministic>";
} else {
@@ -166,9 +186,9 @@
}
static final class MethodThrownThrough implements Printable {
- private Object m;
+ private Executable m;
private int cnt;
- public MethodThrownThrough(Object m, int cnt) {
+ public MethodThrownThrough(Executable m, int cnt) {
this.m = m;
this.cnt = cnt;
}
@@ -262,10 +282,16 @@
}
}
+ static final class TestRunnableInvokeHandler implements InvocationHandler {
+ public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
+ return null;
+ }
+ }
+
static final int METHOD_TRACING_IGNORE_DEPTH = 2;
static boolean sMethodTracingIgnore = false;
- public static void notifyMethodEntry(Object m) {
+ public static void notifyMethodEntry(Executable m) {
// Called by native code when a method is entered. This method is ignored by the native
// entry and exit hooks.
cnt++;
@@ -275,7 +301,7 @@
results.add(new MethodEntry(m, cnt - 1));
}
- public static void notifyMethodExit(Object m, boolean exception, Object result) {
+ public static void notifyMethodExit(Executable m, boolean exception, Object result) {
cnt--;
if (cnt > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) {
@@ -293,17 +319,25 @@
// call this here so it is linked. It doesn't actually do anything here.
loadAllClasses();
Trace.disableTracing(Thread.currentThread());
+ // Call this prior to starting tracing since its implementation is so deep into reflection
+ // that it will be changing all the time and difficult to keep up with.
+ Runnable runnable = (Runnable)Proxy.newProxyInstance(
+ Test988.class.getClassLoader(),
+ new Class[]{ Runnable.class },
+ new TestRunnableInvokeHandler());
Trace.enableMethodTracing(
Test988.class,
- Test988.class.getDeclaredMethod("notifyMethodEntry", Object.class),
+ Test988.class.getDeclaredMethod("notifyMethodEntry", Executable.class),
Test988.class.getDeclaredMethod(
- "notifyMethodExit", Object.class, Boolean.TYPE, Object.class),
+ "notifyMethodExit", Executable.class, Boolean.TYPE, Object.class),
Thread.currentThread());
doFibTest(30, new IterOp());
doFibTest(5, new RecurOp());
doFibTest(-19, new IterOp());
doFibTest(-19, new RecurOp());
+ runnable.run();
+
sMethodTracingIgnore = true;
IntrinsicsTest.doTest();
sMethodTracingIgnore = false;
@@ -325,6 +359,11 @@
RecurOp.class.toString();
IterOp.class.toString();
StringBuilder.class.toString();
+ Runnable.class.toString();
+ TestRunnableInvokeHandler.class.toString();
+ Proxy.class.toString();
+ Proxy.getProxyClass(
+ Test988.class.getClassLoader(), new Class[] { Runnable.class }).toString();
IntrinsicsTest.initialize(); // ensure <clinit> is executed prior to tracing.
}