Merge "Properly de-duplicate debug symbol names."
diff --git a/runtime/native/dalvik_system_VMStack.cc b/runtime/native/dalvik_system_VMStack.cc
index 3e8040b..ed0eb97 100644
--- a/runtime/native/dalvik_system_VMStack.cc
+++ b/runtime/native/dalvik_system_VMStack.cc
@@ -160,12 +160,22 @@
   return Thread::InternalStackTraceToStackTraceElementArray(soa, trace);
 }
 
+static jobjectArray VMStack_getAnnotatedThreadStackTrace(JNIEnv* env, jclass, jobject javaThread) {
+  ScopedFastNativeObjectAccess soa(env);
+  auto fn = [](Thread* thread, const ScopedFastNativeObjectAccess& soaa)
+      REQUIRES_SHARED(Locks::mutator_lock_) -> jobjectArray {
+    return thread->CreateAnnotatedStackTrace(soaa);
+  };
+  return GetThreadStack(soa, javaThread, fn);
+}
+
 static JNINativeMethod gMethods[] = {
   FAST_NATIVE_METHOD(VMStack, fillStackTraceElements, "(Ljava/lang/Thread;[Ljava/lang/StackTraceElement;)I"),
   FAST_NATIVE_METHOD(VMStack, getCallingClassLoader, "()Ljava/lang/ClassLoader;"),
   FAST_NATIVE_METHOD(VMStack, getClosestUserClassLoader, "()Ljava/lang/ClassLoader;"),
   FAST_NATIVE_METHOD(VMStack, getStackClass2, "()Ljava/lang/Class;"),
   FAST_NATIVE_METHOD(VMStack, getThreadStackTrace, "(Ljava/lang/Thread;)[Ljava/lang/StackTraceElement;"),
+  FAST_NATIVE_METHOD(VMStack, getAnnotatedThreadStackTrace, "(Ljava/lang/Thread;)[Ldalvik/system/AnnotatedStackTraceElement;"),
 };
 
 void register_dalvik_system_VMStack(JNIEnv* env) {
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 9f4e544..46cb751 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2743,6 +2743,199 @@
   return result;
 }
 
+jobjectArray Thread::CreateAnnotatedStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const {
+  // This code allocates. Do not allow it to operate with a pending exception.
+  if (IsExceptionPending()) {
+    return nullptr;
+  }
+
+  // If flip_function is not null, it means we have run a checkpoint
+  // before the thread wakes up to execute the flip function and the
+  // thread roots haven't been forwarded.  So the following access to
+  // the roots (locks or methods in the frames) would be bad. Run it
+  // here. TODO: clean up.
+  // Note: copied from DumpJavaStack.
+  {
+    Thread* this_thread = const_cast<Thread*>(this);
+    Closure* flip_func = this_thread->GetFlipFunction();
+    if (flip_func != nullptr) {
+      flip_func->Run(this_thread);
+    }
+  }
+
+  class CollectFramesAndLocksStackVisitor : public MonitorObjectsStackVisitor {
+   public:
+    CollectFramesAndLocksStackVisitor(const ScopedObjectAccessAlreadyRunnable& soaa_in,
+                                      Thread* self,
+                                      Context* context)
+        : MonitorObjectsStackVisitor(self, context),
+          wait_jobject_(soaa_in.Env(), nullptr),
+          block_jobject_(soaa_in.Env(), nullptr),
+          soaa_(soaa_in) {}
+
+   protected:
+    VisitMethodResult StartMethod(ArtMethod* m, size_t frame_nr ATTRIBUTE_UNUSED)
+        OVERRIDE
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      ObjPtr<mirror::StackTraceElement> obj = CreateStackTraceElement(
+          soaa_, m, GetDexPc(/* abort on error */ false));
+      if (obj == nullptr) {
+        return VisitMethodResult::kEndStackWalk;
+      }
+      stack_trace_elements_.emplace_back(soaa_.Env(), soaa_.AddLocalReference<jobject>(obj.Ptr()));
+      return VisitMethodResult::kContinueMethod;
+    }
+
+    VisitMethodResult EndMethod(ArtMethod* m ATTRIBUTE_UNUSED) OVERRIDE {
+      lock_objects_.push_back({});
+      lock_objects_[lock_objects_.size() - 1].swap(frame_lock_objects_);
+
+      DCHECK_EQ(lock_objects_.size(), stack_trace_elements_.size());
+
+      return VisitMethodResult::kContinueMethod;
+    }
+
+    void VisitWaitingObject(mirror::Object* obj, ThreadState state ATTRIBUTE_UNUSED)
+        OVERRIDE
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      wait_jobject_.reset(soaa_.AddLocalReference<jobject>(obj));
+    }
+    void VisitSleepingObject(mirror::Object* obj)
+        OVERRIDE
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      wait_jobject_.reset(soaa_.AddLocalReference<jobject>(obj));
+    }
+    void VisitBlockedOnObject(mirror::Object* obj,
+                              ThreadState state ATTRIBUTE_UNUSED,
+                              uint32_t owner_tid ATTRIBUTE_UNUSED)
+        OVERRIDE
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      block_jobject_.reset(soaa_.AddLocalReference<jobject>(obj));
+    }
+    void VisitLockedObject(mirror::Object* obj)
+        OVERRIDE
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      frame_lock_objects_.emplace_back(soaa_.Env(), soaa_.AddLocalReference<jobject>(obj));
+    }
+
+   public:
+    std::vector<ScopedLocalRef<jobject>> stack_trace_elements_;
+    ScopedLocalRef<jobject> wait_jobject_;
+    ScopedLocalRef<jobject> block_jobject_;
+    std::vector<std::vector<ScopedLocalRef<jobject>>> lock_objects_;
+
+   private:
+    const ScopedObjectAccessAlreadyRunnable& soaa_;
+
+    std::vector<ScopedLocalRef<jobject>> frame_lock_objects_;
+  };
+
+  std::unique_ptr<Context> context(Context::Create());
+  CollectFramesAndLocksStackVisitor dumper(soa, const_cast<Thread*>(this), context.get());
+  dumper.WalkStack();
+
+  // There should not be a pending exception. Otherwise, return with it pending.
+  if (IsExceptionPending()) {
+    return nullptr;
+  }
+
+  // Now go and create Java arrays.
+
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+
+  StackHandleScope<6> hs(soa.Self());
+  mirror::Class* aste_array_class = class_linker->FindClass(
+      soa.Self(),
+      "[Ldalvik/system/AnnotatedStackTraceElement;",
+      ScopedNullHandle<mirror::ClassLoader>());
+  if (aste_array_class == nullptr) {
+    return nullptr;
+  }
+  Handle<mirror::Class> h_aste_array_class(hs.NewHandle<mirror::Class>(aste_array_class));
+
+  mirror::Class* o_array_class = class_linker->FindClass(soa.Self(),
+                                                         "[Ljava/lang/Object;",
+                                                         ScopedNullHandle<mirror::ClassLoader>());
+  if (o_array_class == nullptr) {
+    // This should not fail in a healthy runtime.
+    soa.Self()->AssertPendingException();
+    return nullptr;
+  }
+  Handle<mirror::Class> h_o_array_class(hs.NewHandle<mirror::Class>(o_array_class));
+
+  Handle<mirror::Class> h_aste_class(hs.NewHandle<mirror::Class>(
+      h_aste_array_class->GetComponentType()));
+  ArtField* stack_trace_element_field = h_aste_class->FindField(
+      soa.Self(), h_aste_class.Get(), "stackTraceElement", "Ljava/lang/StackTraceElement;");
+  DCHECK(stack_trace_element_field != nullptr);
+  ArtField* held_locks_field = h_aste_class->FindField(
+        soa.Self(), h_aste_class.Get(), "heldLocks", "[Ljava/lang/Object;");
+  DCHECK(held_locks_field != nullptr);
+  ArtField* blocked_on_field = h_aste_class->FindField(
+        soa.Self(), h_aste_class.Get(), "blockedOn", "Ljava/lang/Object;");
+  DCHECK(blocked_on_field != nullptr);
+
+  size_t length = dumper.stack_trace_elements_.size();
+  ObjPtr<mirror::ObjectArray<mirror::Object>> array =
+      mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(), aste_array_class, length);
+  if (array == nullptr) {
+    soa.Self()->AssertPendingOOMException();
+    return nullptr;
+  }
+
+  ScopedLocalRef<jobjectArray> result(soa.Env(), soa.Env()->AddLocalReference<jobjectArray>(array));
+
+  MutableHandle<mirror::Object> handle(hs.NewHandle<mirror::Object>(nullptr));
+  MutableHandle<mirror::ObjectArray<mirror::Object>> handle2(
+      hs.NewHandle<mirror::ObjectArray<mirror::Object>>(nullptr));
+  for (size_t i = 0; i != length; ++i) {
+    handle.Assign(h_aste_class->AllocObject(soa.Self()));
+    if (handle == nullptr) {
+      soa.Self()->AssertPendingOOMException();
+      return nullptr;
+    }
+
+    // Set stack trace element.
+    stack_trace_element_field->SetObject<false>(
+        handle.Get(), soa.Decode<mirror::Object>(dumper.stack_trace_elements_[i].get()));
+
+    // Create locked-on array.
+    if (!dumper.lock_objects_[i].empty()) {
+      handle2.Assign(mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(),
+                                                                h_o_array_class.Get(),
+                                                                dumper.lock_objects_[i].size()));
+      if (handle2 == nullptr) {
+        soa.Self()->AssertPendingOOMException();
+        return nullptr;
+      }
+      int32_t j = 0;
+      for (auto& scoped_local : dumper.lock_objects_[i]) {
+        if (scoped_local == nullptr) {
+          continue;
+        }
+        handle2->Set(j, soa.Decode<mirror::Object>(scoped_local.get()));
+        DCHECK(!soa.Self()->IsExceptionPending());
+        j++;
+      }
+      held_locks_field->SetObject<false>(handle.Get(), handle2.Get());
+    }
+
+    // Set blocked-on object.
+    if (i == 0) {
+      if (dumper.block_jobject_ != nullptr) {
+        blocked_on_field->SetObject<false>(
+            handle.Get(), soa.Decode<mirror::Object>(dumper.block_jobject_.get()));
+      }
+    }
+
+    ScopedLocalRef<jobject> elem(soa.Env(), soa.AddLocalReference<jobject>(handle.Get()));
+    soa.Env()->SetObjectArrayElement(result.get(), i, elem.get());
+    DCHECK(!soa.Self()->IsExceptionPending());
+  }
+
+  return result.release();
+}
+
 void Thread::ThrowNewExceptionF(const char* exception_class_descriptor, const char* fmt, ...) {
   va_list args;
   va_start(args, fmt);
diff --git a/runtime/thread.h b/runtime/thread.h
index 1e89887..426d27d 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -599,6 +599,9 @@
       jobjectArray output_array = nullptr, int* stack_depth = nullptr)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  jobjectArray CreateAnnotatedStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   bool HasDebuggerShadowFrames() const {
     return tlsPtr_.frame_id_to_shadow_frame != nullptr;
   }
diff --git a/test/168-vmstack-annotated/expected.txt b/test/168-vmstack-annotated/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/168-vmstack-annotated/expected.txt
diff --git a/test/168-vmstack-annotated/info.txt b/test/168-vmstack-annotated/info.txt
new file mode 100644
index 0000000..d849bc3
--- /dev/null
+++ b/test/168-vmstack-annotated/info.txt
@@ -0,0 +1 @@
+Regression test for b/68703210
diff --git a/test/168-vmstack-annotated/run b/test/168-vmstack-annotated/run
new file mode 100644
index 0000000..9365411
--- /dev/null
+++ b/test/168-vmstack-annotated/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# 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.
+
+# Use a smaller heap so it's easier to potentially fill up.
+exec ${RUN} $@ --runtime-option -Xmx2m
diff --git a/test/168-vmstack-annotated/src/Main.java b/test/168-vmstack-annotated/src/Main.java
new file mode 100644
index 0000000..8234f94
--- /dev/null
+++ b/test/168-vmstack-annotated/src/Main.java
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+import java.lang.Thread.State;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+
+public class Main {
+
+    static class Runner implements Runnable {
+        List<Object> locks;
+        List<CyclicBarrier> barriers;
+
+        public Runner(List<Object> locks, List<CyclicBarrier> barriers) {
+            this.locks = locks;
+            this.barriers = barriers;
+        }
+
+        @Override
+        public void run() {
+            step(locks, barriers);
+        }
+
+        private void step(List<Object> l, List<CyclicBarrier> b) {
+            if (l.isEmpty()) {
+                // Nothing to do, sleep indefinitely.
+                try {
+                    Thread.sleep(100000000);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            } else {
+                Object lockObject = l.remove(0);
+                CyclicBarrier barrierObject = b.remove(0);
+
+                if (lockObject == null) {
+                    // No lock object: only take barrier, recurse.
+                    try {
+                        barrierObject.await();
+                    } catch (InterruptedException | BrokenBarrierException e) {
+                        throw new RuntimeException(e);
+                    }
+                    step(l, b);
+                } else if (barrierObject != null) {
+                    // Have barrier: sync, wait and recurse.
+                    synchronized(lockObject) {
+                        try {
+                            barrierObject.await();
+                        } catch (InterruptedException | BrokenBarrierException e) {
+                            throw new RuntimeException(e);
+                        }
+                        step(l, b);
+                    }
+                } else {
+                    // Sync, and get next step (which is assumed to have object and barrier).
+                    synchronized (lockObject) {
+                        Object lockObject2 = l.remove(0);
+                        CyclicBarrier barrierObject2 = b.remove(0);
+                        synchronized(lockObject2) {
+                            try {
+                                barrierObject2.await();
+                            } catch (InterruptedException | BrokenBarrierException e) {
+                                throw new RuntimeException(e);
+                            }
+                            step(l, b);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        try {
+            testCluster1();
+        } catch (Exception e) {
+            Map<Thread,StackTraceElement[]> stacks = Thread.getAllStackTraces();
+            for (Map.Entry<Thread,StackTraceElement[]> entry : stacks.entrySet()) {
+                System.out.println(entry.getKey());
+                System.out.println(Arrays.toString(entry.getValue()));
+            }
+            throw e;
+        }
+    }
+
+    private static void testCluster1() throws Exception {
+        // Test setup (at deadlock):
+        //
+        // Thread 1:
+        //   #0 step: synchornized(o3) { synchronized(o2) }
+        //   #1 step: synchronized(o1)
+        //
+        // Thread 2:
+        //   #0 step: synchronized(o1)
+        //   #1 step: synchronized(o4) { synchronized(o2) }
+        //
+        LinkedList<Object> l1 = new LinkedList<>();
+        LinkedList<CyclicBarrier> b1 = new LinkedList<>();
+        LinkedList<Object> l2 = new LinkedList<>();
+        LinkedList<CyclicBarrier> b2 = new LinkedList<>();
+
+        Object o1 = new Object();
+        Object o2 = new Object();
+        Object o3 = new Object();
+        Object o4 = new Object();
+
+        l1.add(o1);
+        l1.add(o3);
+        l1.add(o2);
+        l2.add(o4);
+        l2.add(o2);
+        l2.add(o1);
+
+        CyclicBarrier c1 = new CyclicBarrier(3);
+        CyclicBarrier c2 = new CyclicBarrier(2);
+        b1.add(c1);
+        b1.add(null);
+        b1.add(c2);
+        b2.add(null);
+        b2.add(c1);
+        b2.add(c2);
+
+        Thread t1 = new Thread(new Runner(l1, b1));
+        t1.setDaemon(true);
+        t1.start();
+        Thread t2 = new Thread(new Runner(l2, b2));
+        t2.setDaemon(true);
+        t2.start();
+
+        c1.await();
+
+        waitNotRunnable(t1);
+        waitNotRunnable(t2);
+        Thread.sleep(250);    // Unfortunately this seems necessary. :-(
+
+        // Thread 1.
+        {
+            Object[] stack1 = getAnnotatedStack(t1);
+            assertBlockedOn(stack1[0], o2);              // Blocked on o2.
+            assertLocks(stack1[0], o3);                  // Locked o3.
+            assertStackTraceElementStep(stack1[0]);
+
+            assertBlockedOn(stack1[1], null);            // Frame can't be blocked.
+            assertLocks(stack1[1], o1);                  // Locked o1.
+            assertStackTraceElementStep(stack1[1]);
+        }
+
+        // Thread 2.
+        {
+            Object[] stack2 = getAnnotatedStack(t2);
+            assertBlockedOn(stack2[0], o1);              // Blocked on o1.
+            assertLocks(stack2[0]);                      // Nothing locked.
+            assertStackTraceElementStep(stack2[0]);
+
+            assertBlockedOn(stack2[1], null);            // Frame can't be blocked.
+            assertLocks(stack2[1], o4, o2);              // Locked o4, o2.
+            assertStackTraceElementStep(stack2[1]);
+        }
+    }
+
+    private static void waitNotRunnable(Thread t) throws InterruptedException {
+        while (t.getState() == State.RUNNABLE) {
+            Thread.sleep(100);
+        }
+    }
+
+    private static Object[] getAnnotatedStack(Thread t) throws Exception {
+        Class<?> vmStack = Class.forName("dalvik.system.VMStack");
+        Method m = vmStack.getDeclaredMethod("getAnnotatedThreadStackTrace", Thread.class);
+        return (Object[]) m.invoke(null, t);
+    }
+
+    private static void assertEquals(Object o1, Object o2) {
+        if (o1 != o2) {
+            throw new RuntimeException("Expected " + o1 + " == " + o2);
+        }
+    }
+    private static void assertLocks(Object fromTrace, Object... locks) throws Exception {
+        Object fieldValue = fromTrace.getClass().getDeclaredMethod("getHeldLocks").
+                invoke(fromTrace);
+        assertEquals((Object[]) fieldValue,
+                (locks == null) ? null : (locks.length == 0 ? null : locks));
+    }
+    private static void assertBlockedOn(Object fromTrace, Object block) throws Exception {
+        Object fieldValue = fromTrace.getClass().getDeclaredMethod("getBlockedOn").
+                invoke(fromTrace);
+        assertEquals(fieldValue, block);
+    }
+    private static void assertEquals(Object[] o1, Object[] o2) {
+        if (!Arrays.equals(o1, o2)) {
+            throw new RuntimeException(
+                    "Expected " + Arrays.toString(o1) + " == " + Arrays.toString(o2));
+        }
+    }
+    private static void assertStackTraceElementStep(Object o) throws Exception {
+        Object fieldValue = o.getClass().getDeclaredMethod("getStackTraceElement").invoke(o);
+        if (fieldValue instanceof StackTraceElement) {
+            StackTraceElement elem = (StackTraceElement) fieldValue;
+            if (!elem.getMethodName().equals("step")) {
+                throw new RuntimeException("Expected step method");
+            }
+            return;
+        }
+        throw new RuntimeException("Expected StackTraceElement " + fieldValue + " / " + o);
+    }
+}
+