Ensure Proxy frames work with JVMTI stack frame functions

We were incorrectly returning errors and crashing when stack frame
JVMTI functions were used with Proxy methods. This fixes those issues
and adds a test for them.

Bug: 66903662
Test: ./test.py --host -j40
Change-Id: Ia7c768d631c5ac4d8339d70a9d4761d5b4bc284e
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
index f05977a..50402a0 100644
--- a/openjdkjvmti/ti_method.cc
+++ b/openjdkjvmti/ti_method.cc
@@ -572,8 +572,9 @@
       return;
     }
     art::ArtMethod* method = visitor.GetMethod();
-    if (method->IsNative()) {
-      // TODO We really should support get/set for non-shadow frames.
+    // Native and 'art' proxy methods don't have registers.
+    if (method->IsNative() || method->IsProxyMethod()) {
+      // TODO It might be useful to fake up support for get at least on proxy frames.
       result_ = ERR(OPAQUE_FRAME);
       return;
     } else if (method->GetCodeItem()->registers_size_ <= slot_) {
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
index d4cc42a..e0c1399 100644
--- a/openjdkjvmti/ti_stack.cc
+++ b/openjdkjvmti/ti_stack.cc
@@ -789,7 +789,7 @@
   }
 
   *method_ptr = art::jni::EncodeArtMethod(closure.method);
-  if (closure.method->IsNative()) {
+  if (closure.method->IsNative() || closure.method->IsProxyMethod()) {
     *location_ptr = -1;
   } else {
     if (closure.dex_pc == art::dex::kDexNoIndex) {
diff --git a/test/1914-get-local-instance/expected.txt b/test/1914-get-local-instance/expected.txt
index 4117942..09f0df1 100644
--- a/test/1914-get-local-instance/expected.txt
+++ b/test/1914-get-local-instance/expected.txt
@@ -10,3 +10,6 @@
 Running public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) with "GetThis" on remote thread.
 "GetThis" on public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) got value: TargetClass("NativeInstanceMethodObject")
 	Value is 'TargetClass("NativeInstanceMethodObject")' (class: class art.Test1914$TargetClass)
+Running public abstract void art.Test1914$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public abstract void art.Test1914$Foo.InterfaceProxyMethod(java.lang.Runnable) got value: Proxy for [interface art.Test1914$Foo]
+	Value is 'Proxy for [interface art.Test1914$Foo]' (class: PROXY CLASS)
diff --git a/test/1914-get-local-instance/src/art/Test1914.java b/test/1914-get-local-instance/src/art/Test1914.java
index c09f519..e47f9cb 100644
--- a/test/1914-get-local-instance/src/art/Test1914.java
+++ b/test/1914-get-local-instance/src/art/Test1914.java
@@ -18,7 +18,9 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Executable;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.nio.ByteBuffer;
 import java.util.concurrent.Semaphore;
 import java.util.Arrays;
@@ -35,7 +37,7 @@
 
   public static void reportValue(Object val) {
     System.out.println("\tValue is '" + val + "' (class: "
-        + (val != null ? val.getClass() : "NULL") + ")");
+        + (val != null ? (val instanceof Proxy ? "PROXY CLASS" : val.getClass()) : "NULL") + ")");
   }
 
   public static void StaticMethod(Runnable safepoint) {
@@ -151,7 +153,10 @@
 
     private StackTrace.StackFrameData findStackFrame(Thread thr) {
       for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
-        if (frame.method.equals(target)) {
+        if (frame.method.equals(target) ||
+            (frame.method.getName().equals(target.getName()) &&
+             Arrays.deepEquals(frame.method.getParameterTypes(), target.getParameterTypes()) &&
+             ((Method)frame.method).getReturnType().equals(target.getReturnType()))) {
           return frame;
         }
       }
@@ -163,6 +168,25 @@
     return klass.getDeclaredMethod(name, Runnable.class);
   }
 
+  public static interface Foo {
+    public void InterfaceProxyMethod(Runnable r);
+  }
+
+  public static Object getProxyObject(final Class... k) {
+    return Proxy.newProxyInstance(
+        Test1914.class.getClassLoader(),
+        k,
+        (p, m, a) -> {
+          if (m.getName().equals("toString")) {
+            return "Proxy for " + Arrays.toString(k);
+          } else {
+            ((Runnable)a[0]).run();
+            reportValue(p);
+            return null;
+          }
+        });
+  }
+
   public static void run() throws Exception {
     Locals.EnableLocalVariableAccess();
     final TestCase[] MAIN_TEST_CASES = new TestCase[] {
@@ -172,6 +196,8 @@
                    getMethod(TargetClass.class, "InstanceMethod")),
       new TestCase(new TargetClass("NativeInstanceMethodObject"),
                    getMethod(TargetClass.class, "NativeInstanceMethod")),
+      new TestCase(getProxyObject(Foo.class),
+                   getMethod(Foo.class, "InterfaceProxyMethod")),
     };
 
     for (TestCase t: MAIN_TEST_CASES) {
diff --git a/test/1939-proxy-frames/expected.txt b/test/1939-proxy-frames/expected.txt
new file mode 100644
index 0000000..a4c97c9
--- /dev/null
+++ b/test/1939-proxy-frames/expected.txt
@@ -0,0 +1,8 @@
+Running public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) got value: Proxy for [interface art.Test1939$Foo]
+Running public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetLocalReference0" on remote thread.
+"GetLocalReference0" on public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) failed due to JVMTI_ERROR_OPAQUE_FRAME
+Running public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetProxyFrameLocation" on remote thread.
+"GetProxyFrameLocation" on public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) got value: -1
+Running public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetProxyFrameMethod" on remote thread.
+"GetProxyFrameMethod" on public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) got value: public final void $Proxy0.InterfaceProxyMethod(java.lang.Runnable)
diff --git a/test/1939-proxy-frames/info.txt b/test/1939-proxy-frames/info.txt
new file mode 100644
index 0000000..9fc3d62
--- /dev/null
+++ b/test/1939-proxy-frames/info.txt
@@ -0,0 +1,2 @@
+Test for jvmti get local instance
+
diff --git a/test/1939-proxy-frames/local_instance.cc b/test/1939-proxy-frames/local_instance.cc
new file mode 100644
index 0000000..dc833bf
--- /dev/null
+++ b/test/1939-proxy-frames/local_instance.cc
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include <iostream>
+#include <pthread.h>
+#include <stdio.h>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1939ProxyFrames {
+
+extern "C" JNIEXPORT jobject Java_art_Test1939_GetFrameMethod(JNIEnv* env,
+                                                              jclass,
+                                                              jthread thr,
+                                                              jint depth) {
+  jmethodID m = nullptr;
+  jlong loc = -1;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameLocation(thr, depth, &m, &loc))) {
+    return nullptr;
+  }
+  jclass klass = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodDeclaringClass(m, &klass))) {
+    return nullptr;
+  }
+  jobject res = env->ToReflectedMethod(klass, m, false);
+  env->DeleteLocalRef(klass);
+  return res;
+}
+
+extern "C" JNIEXPORT jlong Java_art_Test1939_GetFrameLocation(JNIEnv* env,
+                                                              jclass,
+                                                              jthread thr,
+                                                              jint depth) {
+  jmethodID m = nullptr;
+  jlong loc = -1;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameLocation(thr, depth, &m, &loc));
+  return loc;
+}
+
+}  // namespace Test1939ProxyFrames
+}  // namespace art
+
diff --git a/test/1939-proxy-frames/run b/test/1939-proxy-frames/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1939-proxy-frames/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/1939-proxy-frames/src/Main.java b/test/1939-proxy-frames/src/Main.java
new file mode 100644
index 0000000..85cab34
--- /dev/null
+++ b/test/1939-proxy-frames/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.Test1939.run();
+  }
+}
diff --git a/test/1939-proxy-frames/src/art/Breakpoint.java b/test/1939-proxy-frames/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1939-proxy-frames/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * 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.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1939-proxy-frames/src/art/Locals.java b/test/1939-proxy-frames/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1939-proxy-frames/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * 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.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1939-proxy-frames/src/art/StackTrace.java b/test/1939-proxy-frames/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1939-proxy-frames/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * 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.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1939-proxy-frames/src/art/Suspension.java b/test/1939-proxy-frames/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1939-proxy-frames/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1939-proxy-frames/src/art/Test1939.java b/test/1939-proxy-frames/src/art/Test1939.java
new file mode 100644
index 0000000..6147d1e
--- /dev/null
+++ b/test/1939-proxy-frames/src/art/Test1939.java
@@ -0,0 +1,178 @@
+/*
+ * 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.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1939 {
+  public static interface SafepointFunction {
+    public void invoke(
+        Thread thread,
+        Method target,
+        int depth) throws Exception;
+  }
+
+  public static interface GetterFunction {
+    public Object GetVar(Thread t, int depth);
+  }
+
+  public static SafepointFunction NamedGet(final String type, final GetterFunction get) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, int depth) {
+        try {
+          Object res = get.GetVar(t, depth);
+          System.out.println(this + " on " + method + " got value: " + res);
+        } catch (Exception e) {
+          System.out.println(this + " on " + method + " failed due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Get" + type + "\"";
+      }
+    };
+  }
+
+  public static class TestCase {
+    public final Object thiz;
+    public final Method target;
+
+    public TestCase(Method target) {
+      this(null, target);
+    }
+    public TestCase(Object thiz, Method target) {
+      this.thiz = thiz;
+      this.target = target;
+    }
+
+    public static class ThreadPauser implements Runnable {
+      public final Semaphore sem_wakeup_main;
+      public final Semaphore sem_wait;
+
+      public ThreadPauser() {
+        sem_wakeup_main = new Semaphore(0);
+        sem_wait = new Semaphore(0);
+      }
+
+      public void run() {
+        try {
+          sem_wakeup_main.release();
+          sem_wait.acquire();
+        } catch (Exception e) {
+          throw new Error("Error with semaphores!", e);
+        }
+      }
+
+      public void waitForOtherThreadToPause() throws Exception {
+        sem_wakeup_main.acquire();
+      }
+
+      public void wakeupOtherThread() throws Exception {
+        sem_wait.release();
+      }
+    }
+
+    public void exec(final SafepointFunction safepoint) throws Exception {
+      System.out.println("Running " + target + " with " + safepoint + " on remote thread.");
+      final ThreadPauser pause = new ThreadPauser();
+      Thread remote = new Thread(
+          () -> {
+            try {
+              target.invoke(thiz, pause);
+            } catch (Exception e) {
+              throw new Error("Error invoking remote thread " + Thread.currentThread(), e);
+            }
+          },
+          "remote thread for " + target + " with " + safepoint);
+      remote.start();
+      pause.waitForOtherThreadToPause();
+      try {
+        Suspension.suspend(remote);
+        StackTrace.StackFrameData frame = findStackFrame(remote);
+        safepoint.invoke(remote, target, frame.depth);
+      } finally {
+        Suspension.resume(remote);
+        pause.wakeupOtherThread();
+        remote.join();
+      }
+    }
+
+    private StackTrace.StackFrameData findStackFrame(Thread thr) {
+      for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+        if (frame.method.equals(target) ||
+            (frame.method.getName().equals(target.getName()) &&
+             Arrays.deepEquals(frame.method.getParameterTypes(), target.getParameterTypes()) &&
+             ((Method)frame.method).getReturnType().equals(target.getReturnType()))) {
+          return frame;
+        }
+      }
+      throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+    }
+  }
+
+  public static Method getMethod(Class<?> klass, String name) throws Exception {
+    return klass.getDeclaredMethod(name, Runnable.class);
+  }
+
+  public static interface Foo {
+    public void InterfaceProxyMethod(Runnable r);
+  }
+
+  public static Object getProxyObject(final Class... k) {
+    return Proxy.newProxyInstance(
+        Test1939.class.getClassLoader(),
+        k,
+        (p, m, a) -> {
+          if (m.getName().equals("toString")) {
+            return "Proxy for " + Arrays.toString(k);
+          } else {
+            ((Runnable)a[0]).run();
+            return null;
+          }
+        });
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final TestCase[] MAIN_TEST_CASES = new TestCase[] {
+    };
+
+    TestCase test = new TestCase(
+        getProxyObject(Foo.class), getMethod(Foo.class, "InterfaceProxyMethod"));
+    test.exec(NamedGet("This", Locals::GetLocalInstance));
+    test.exec(NamedGet("LocalReference0", (t, d) -> Locals.GetLocalVariableObject(t, d, 0)));
+    test.exec(NamedGet("ProxyFrameLocation", (t, d) -> Long.valueOf(GetFrameLocation(t, d))));
+    test.exec(NamedGet("ProxyFrameMethod", Test1939::GetFrameMethod));
+  }
+
+  public static native long GetFrameLocation(Thread thr, int depth);
+  public static native Executable GetFrameMethod(Thread thr, int depth);
+}
+
diff --git a/test/Android.bp b/test/Android.bp
index 31474d5..b737345 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -277,6 +277,7 @@
         "1930-monitor-info/monitor.cc",
         "1932-monitor-events-misc/monitor_misc.cc",
         "1934-jvmti-signal-thread/signal_threads.cc",
+        "1939-proxy-frames/local_instance.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/tools/libjdwp_art_failures.txt b/tools/libjdwp_art_failures.txt
index 57d3ce7..8d67c45 100644
--- a/tools/libjdwp_art_failures.txt
+++ b/tools/libjdwp_art_failures.txt
@@ -48,7 +48,7 @@
   name: "org.apache.harmony.jpda.tests.jdwp.Events.VMDeath002Test#testVMDeathRequest"
 },
 {
-  description: "Test fails with INTERNAL error due to proxy frame!",
+  description: "Test fails with OPAQUE_FRAME error due to attempting a GetLocalReference on a proxy frame instead of GetLocalInstance!",
   result: EXEC_FAILED,
   bug: 66903662,
   name: "org.apache.harmony.jpda.tests.jdwp.StackFrame.ProxyThisObjectTest#testThisObject"