summaryrefslogtreecommitdiff
path: root/test/1945-proxy-method-arguments
diff options
context:
space:
mode:
Diffstat (limited to 'test/1945-proxy-method-arguments')
-rw-r--r--test/1945-proxy-method-arguments/expected.txt26
-rw-r--r--test/1945-proxy-method-arguments/get_args.cc113
-rw-r--r--test/1945-proxy-method-arguments/info.txt7
-rw-r--r--test/1945-proxy-method-arguments/src/Main.java149
4 files changed, 295 insertions, 0 deletions
diff --git a/test/1945-proxy-method-arguments/expected.txt b/test/1945-proxy-method-arguments/expected.txt
new file mode 100644
index 0000000000..1824953202
--- /dev/null
+++ b/test/1945-proxy-method-arguments/expected.txt
@@ -0,0 +1,26 @@
+JNI_OnLoad called
+proxy: $Proxy0
+Proxy for interface TestInterface.method0
+ arg0: $Proxy0
+Proxy for interface TestInterface.method1
+ arg0: $Proxy0
+ arg1: java.lang.String "a"
+Proxy for interface TestInterface.method10
+ arg0: $Proxy0
+ arg1: java.lang.String "one"
+ arg2: java.lang.String "two"
+ arg3: java.lang.String "three"
+ arg4: java.lang.String "four"
+ arg5: java.lang.String "five"
+ arg6: java.lang.String "six"
+ arg7: java.lang.String "seven"
+ arg8: java.lang.String "eight"
+ arg9: java.lang.String "nine"
+ arg10: java.lang.String "ten"
+Proxy for interface TestInterface.method10Even
+ arg0: $Proxy0
+ arg2: java.lang.String "two"
+ arg4: java.lang.String "four"
+ arg6: java.lang.String "six"
+ arg8: java.lang.String "eight"
+ arg10: java.lang.String "ten"
diff --git a/test/1945-proxy-method-arguments/get_args.cc b/test/1945-proxy-method-arguments/get_args.cc
new file mode 100644
index 0000000000..211ae10ab0
--- /dev/null
+++ b/test/1945-proxy-method-arguments/get_args.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 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 "arch/context.h"
+#include "art_method-inl.h"
+#include "jni.h"
+#include "oat_quick_method_header.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread.h"
+
+namespace art {
+
+namespace {
+
+// Visit a proxy method Quick frame at a given depth.
+class GetProxyQuickFrameVisitor FINAL : public StackVisitor {
+ public:
+ GetProxyQuickFrameVisitor(art::Thread* target, art::Context* ctx, size_t frame_depth)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ cur_depth_(0u),
+ frame_depth_(frame_depth),
+ quick_frame_(nullptr) {}
+
+ bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (GetMethod()->IsRuntimeMethod()) {
+ return true;
+ }
+ if (cur_depth_ == frame_depth_) {
+ // Found frame.
+ ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+ if (shadow_frame != nullptr) {
+ // Nothing to do.
+ } else {
+ VisitQuickFrameAtSearchedDepth();
+ }
+ return false;
+ } else {
+ ++cur_depth_;
+ return true;
+ }
+ }
+
+ void VisitQuickFrameAtSearchedDepth() REQUIRES_SHARED(Locks::mutator_lock_) {
+ quick_frame_ = GetCurrentQuickFrame();
+ CHECK(quick_frame_ != nullptr);
+ ArtMethod* method = *quick_frame_;
+ CHECK(method != nullptr);
+ CHECK(method->IsProxyMethod()) << method->PrettyMethod();
+ }
+
+ // Return the found Quick frame.
+ ArtMethod** GetQuickFrame() {
+ return quick_frame_;
+ }
+
+ private:
+ // The depth of the currently visited frame.
+ size_t cur_depth_;
+ // The depth of the currently searched frame.
+ size_t frame_depth_;
+ // The quick frame, if found.
+ ArtMethod** quick_frame_;
+ // Method name
+
+ DISALLOW_COPY_AND_ASSIGN(GetProxyQuickFrameVisitor);
+};
+
+extern "C" StackReference<mirror::Object>* artQuickGetProxyReferenceArgumentAt(size_t arg_pos,
+ ArtMethod** sp)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+jobject GetProxyReferenceArgument(size_t arg_pos, size_t proxy_method_frame_depth) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+ std::unique_ptr<Context> context(Context::Create());
+
+ GetProxyQuickFrameVisitor visitor(self, context.get(), proxy_method_frame_depth);
+ visitor.WalkStack();
+ ArtMethod** quick_frame = visitor.GetQuickFrame();
+ CHECK(quick_frame != nullptr);
+
+ // Find reference argument in frame.
+ StackReference<mirror::Object>* ref_arg =
+ artQuickGetProxyReferenceArgumentAt(arg_pos, quick_frame);
+ CHECK(ref_arg != nullptr);
+ art::ObjPtr<mirror::Object> obj = ref_arg->AsMirrorPtr();
+
+ return obj.IsNull() ? nullptr : soa.AddLocalReference<jobject>(obj);
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_TestInvocationHandler_getArgument(
+ JNIEnv* env ATTRIBUTE_UNUSED, jobject thiz ATTRIBUTE_UNUSED, int arg_pos, int frame_depth) {
+ return GetProxyReferenceArgument(arg_pos, frame_depth);
+}
+
+} // namespace
+
+} // namespace art
diff --git a/test/1945-proxy-method-arguments/info.txt b/test/1945-proxy-method-arguments/info.txt
new file mode 100644
index 0000000000..15ccc44ac9
--- /dev/null
+++ b/test/1945-proxy-method-arguments/info.txt
@@ -0,0 +1,7 @@
+Test checking that reference arguments of proxy methods are visited as
+thread stack roots when visiting Quick frames roots (b/73149739).
+Previously, if the proxy method (direcly or indirectly) executed code
+accessing one of these reference arguments in the proxy method stack
+frame, it could end up with a stale reference, as the corresponding
+object may have been moved by the garbage collector, but the stack
+reference would not have been updated.
diff --git a/test/1945-proxy-method-arguments/src/Main.java b/test/1945-proxy-method-arguments/src/Main.java
new file mode 100644
index 0000000000..b04a661a4b
--- /dev/null
+++ b/test/1945-proxy-method-arguments/src/Main.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+interface TestInterface {
+ void method0();
+ void method1(String arg);
+ void method10(String arg1, String arg2, String arg3, String arg4, String arg5,
+ String arg6, String arg7, String arg8, String arg9, String arg10);
+ void method10Even(byte arg1, String arg2, short arg3, String arg4, int arg5,
+ String arg6, long arg7, String arg8, double arg9, String arg10);
+}
+
+class TestInvocationHandler implements InvocationHandler {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) {
+ // Force garbage collection to try to make `proxy` move in memory
+ // (in the case of a moving garbage collector).
+ System.gc();
+
+ System.out.println("Proxy for " + TestInterface.class + "." + method.getName());
+ if (method.getName().equals("method0")) {
+ testMethod0(proxy, args);
+ } else if (method.getName().equals("method1")) {
+ testMethod1(proxy, args);
+ } else if (method.getName().equals("method10")) {
+ testMethod10(proxy, args);
+ } else if (method.getName().equals("method10Even")) {
+ testMethod10Even(proxy, args);
+ }
+ return null;
+ }
+
+ private void testMethod0(Object proxy, Object[] args) {
+ // Get argument 0 (method target) from the proxy method frame ($Proxy0.method0 activation).
+ Object arg0 = getProxyMethodArgument(0);
+ System.out.println(" arg0: " + arg0.getClass().getName());
+ Main.assertEquals(proxy, arg0);
+ }
+
+ private void testMethod1(Object proxy, Object[] args) {
+ // Get argument 0 (method target) from the proxy method frame ($Proxy0.method0 activation).
+ Object arg0 = getProxyMethodArgument(0);
+ System.out.println(" arg0: " + arg0.getClass().getName());
+ Main.assertEquals(proxy, arg0);
+ // Get argument 1 from the proxy method frame ($Proxy0.method1 activation).
+ String arg1 = (String) getProxyMethodArgument(1);
+ System.out.println(" arg1: " + arg1.getClass().getName() + " \"" + arg1 + "\"");
+ Main.assertEquals(args[0], arg1);
+ }
+
+ private void testMethod10(Object proxy, Object[] args) {
+ // Get argument 0 (method target) from the proxy method frame ($Proxy0.method10 activation).
+ Object arg0 = getProxyMethodArgument(0);
+ System.out.println(" arg0: " + arg0.getClass().getName());
+ Main.assertEquals(proxy, arg0);
+ // Get argument `i` from the proxy method frame ($Proxy0.method10 activation).
+ for (int i = 0; i < 10; ++i) {
+ int arg_pos = i + 1;
+ String arg = (String) getProxyMethodArgument(arg_pos);
+ System.out.println(" arg" + arg_pos + ": " + arg.getClass().getName() + " \"" + arg + "\"");
+ Main.assertEquals(args[i], arg);
+ }
+ }
+
+ private void testMethod10Even(Object proxy, Object[] args) {
+ // Get argument 0 (method target) from the proxy method frame ($Proxy0.method10Even
+ // activation).
+ Object arg0 = getProxyMethodArgument(0);
+ System.out.println(" arg0: " + arg0.getClass().getName());
+ Main.assertEquals(proxy, arg0);
+ // Get argument `i` from the proxy method frame ($Proxy0.method10Even activation).
+ for (int i = 1; i < 10; i += 2) {
+ int arg_pos = i + 1;
+ String arg = (String) getProxyMethodArgument(arg_pos);
+ System.out.println(" arg" + arg_pos + ": " + arg.getClass().getName() + " \"" + arg + "\"");
+ Main.assertEquals(args[i], arg);
+ }
+ }
+
+ // Get reference argument at position `arg_pos` in proxy frame.
+ // This method should only be called from one of the
+ // `TestInvocationHandler.testMethod*` methods via `TestInvocationHandler.invoke`.
+ private Object getProxyMethodArgument(int arg_pos) {
+ // Find proxy frame in stack (from a testMethod* method).
+ //
+ // depth method
+ // ----------------------------------------------------------------------
+ // 0 TestInvocationHandler.getArgument (outermost frame)
+ // 1 TestInvocationHandler.getProxyMethodArgument
+ // 2 TestInvocationHandler.testMethod*
+ // 3 TestInvocationHandler.invoke
+ // 4 java.lang.reflect.Proxy.invoke
+ // -> 5 TestInterface.method* (proxy method)
+ // 6 Main.main (innermost frame)
+ //
+ int proxy_method_frame_depth = 5;
+ return getArgument(arg_pos, proxy_method_frame_depth);
+ }
+
+ // Get reference argument at position `arg_pos` in frame at depth `frame_depth`.
+ private native Object getArgument(int arg_pos, int frame_depth);
+}
+
+public class Main {
+ public static void main(String[] args) {
+ System.loadLibrary(args[0]);
+
+ TestInvocationHandler invocationHandler = new TestInvocationHandler();
+ TestInterface proxy = (TestInterface) Proxy.newProxyInstance(
+ Main.class.getClassLoader(),
+ new Class<?>[] { TestInterface.class },
+ invocationHandler);
+ System.out.println("proxy: " + proxy.getClass().getName());
+
+ proxy.method0();
+ proxy.method1("a");
+ proxy.method10("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten");
+ proxy.method10Even((byte) 1, "two", (short) 3, "four", 5, "six", 7L, "eight", 9.0, "ten");
+ }
+
+ public static void assertEquals(Object expected, Object actual) {
+ if (expected != actual) {
+ throw new Error("Expected " + expected + ", got " + actual);
+ }
+ }
+
+ public static void assertEquals(String expected, String actual) {
+ if (expected != actual) {
+ throw new Error("Expected \"" + expected + "\", got \"" + actual + "\"");
+ }
+ }
+}