[vm/debugger] Fixes async CollectAwaiterReturn()

The async, off-stack part of DebuggerStackTrace::CollectAwaiterReturn's
stack unwinding incorrectly looked on the stack instead of following
the Closure's awaiter chain.
This caused the async stack traces to be truncated and missing any
async frames, in turn causing ShouldPauseOnException to incorrectly
conclude no handler frame existing.
This would affect any case where the exception handling was located
around any except for the inner most awaiter.

TEST=Added regression test to pause_on_unhandled_exceptions_catcherror_test

Closes: https://github.com/dart-lang/sdk/issues/37953
Bug: https://github.com/dart-lang/sdk/issues/37953
Change-Id: I34a2b5ce3c7532032b2ef3dfbc2af7294d13a4c7
Cq-Do-Not-Cancel-Tryjobs: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/187405
Commit-Queue: Clement Skau <cskau@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/vm_service/test/get_stack_test.dart b/pkg/vm_service/test/get_stack_test.dart
index 77c3595..dc7c05d 100644
--- a/pkg/vm_service/test/get_stack_test.dart
+++ b/pkg/vm_service/test/get_stack_test.dart
@@ -97,7 +97,7 @@
 
     expect(result.frames, hasLength(10));
     expect(result.asyncCausalFrames, hasLength(26));
-    expect(result.awaiterFrames, hasLength(2));
+    expect(result.awaiterFrames, hasLength(13));
 
     expectFrames(result.frames!, [
       [equals('Regular'), endsWith(' func10')],
@@ -140,6 +140,17 @@
     expectFrames(result.awaiterFrames, [
       [equals('AsyncActivation'), endsWith(' func10')],
       [equals('AsyncActivation'), endsWith(' func9')],
+      [equals('AsyncActivation'), endsWith(' func8')],
+      [equals('AsyncActivation'), endsWith(' func7')],
+      [equals('AsyncActivation'), endsWith(' func6')],
+      [equals('AsyncActivation'), endsWith(' func5')],
+      [equals('AsyncActivation'), endsWith(' func4')],
+      [equals('AsyncActivation'), endsWith(' func3')],
+      [equals('AsyncActivation'), endsWith(' func2')],
+      [equals('AsyncActivation'), endsWith(' func1')],
+      [equals('AsyncActivation'), endsWith(' testMain')],
+      [equals('AsyncActivation'), endsWith(' _ServiceTesteeRunner.run')],
+      [equals('AsyncActivation'), endsWith(' runIsolateTests')],
     ]);
   },
 ];
diff --git a/runtime/observatory/tests/service/get_stack_limit_rpc_test.dart b/runtime/observatory/tests/service/get_stack_limit_rpc_test.dart
index b1ccb4f..09186aa 100644
--- a/runtime/observatory/tests/service/get_stack_limit_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_stack_limit_rpc_test.dart
@@ -55,7 +55,7 @@
     var awaiterFrames = stack['awaiterFrames'];
     expect(frames.length, greaterThanOrEqualTo(20));
     expect(asyncFrames.length, greaterThan(frames.length));
-    expect(awaiterFrames.length, 13);
+    expect(awaiterFrames.length, greaterThan(frames.length));
     expect(stack['truncated'], false);
     verifyStack(frames, [
       'bar.async_op', 'foo.async_op', 'bar.async_op', 'foo.async_op',
@@ -75,7 +75,7 @@
 
     expect(frames.length, fullStackLength);
     expect(asyncFrames.length, fullStackLength + 1);
-    expect(asyncFrames.length, fullStackLength + 1);
+    expect(awaiterFrames.length, fullStackLength + 1);
     expect(stack['truncated'], true);
     verifyStack(frames, [
       'bar.async_op', 'foo.async_op', 'bar.async_op', 'foo.async_op',
diff --git a/runtime/observatory/tests/service/pause_on_unhandled_exceptions_catcherror_test.dart b/runtime/observatory/tests/service/pause_on_unhandled_exceptions_catcherror_test.dart
index b8c96cb..a0598aa 100644
--- a/runtime/observatory/tests/service/pause_on_unhandled_exceptions_catcherror_test.dart
+++ b/runtime/observatory/tests/service/pause_on_unhandled_exceptions_catcherror_test.dart
@@ -12,12 +12,23 @@
   throw 'Throw from throwAsync!';
 }
 
+Future<void> nestedThrowAsync() async {
+  await Future.delayed(const Duration(milliseconds: 100));
+  await throwAsync();
+}
+
 testeeMain() async {
   await throwAsync().then((v) {
     print('Hello from then()!');
   }).catchError((e, st) {
     print('Caught in catchError: $e!');
   });
+  // Make sure we can chain through off-stack awaiters as well.
+  try {
+    await nestedThrowAsync();
+  } catch (e) {
+    print('Caught in catch: $e!');
+  }
 }
 
 var tests = <IsolateTest>[
diff --git a/runtime/observatory_2/tests/service_2/get_stack_limit_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_stack_limit_rpc_test.dart
index 33766da..fca283d 100644
--- a/runtime/observatory_2/tests/service_2/get_stack_limit_rpc_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_stack_limit_rpc_test.dart
@@ -55,7 +55,7 @@
     var awaiterFrames = stack['awaiterFrames'];
     expect(frames.length, greaterThanOrEqualTo(20));
     expect(asyncFrames.length, greaterThan(frames.length));
-    expect(awaiterFrames.length, 13);
+    expect(awaiterFrames.length, greaterThan(frames.length));
     expect(stack['truncated'], false);
     verifyStack(frames, [
       'bar.async_op', 'foo.async_op', 'bar.async_op', 'foo.async_op',
@@ -75,7 +75,7 @@
 
     expect(frames.length, fullStackLength);
     expect(asyncFrames.length, fullStackLength + 1);
-    expect(asyncFrames.length, fullStackLength + 1);
+    expect(awaiterFrames.length, fullStackLength + 1);
     expect(stack['truncated'], true);
     verifyStack(frames, [
       'bar.async_op', 'foo.async_op', 'bar.async_op', 'foo.async_op',
diff --git a/runtime/observatory_2/tests/service_2/pause_on_unhandled_exceptions_catcherror_test.dart b/runtime/observatory_2/tests/service_2/pause_on_unhandled_exceptions_catcherror_test.dart
index b8c96cb..a0598aa 100644
--- a/runtime/observatory_2/tests/service_2/pause_on_unhandled_exceptions_catcherror_test.dart
+++ b/runtime/observatory_2/tests/service_2/pause_on_unhandled_exceptions_catcherror_test.dart
@@ -12,12 +12,23 @@
   throw 'Throw from throwAsync!';
 }
 
+Future<void> nestedThrowAsync() async {
+  await Future.delayed(const Duration(milliseconds: 100));
+  await throwAsync();
+}
+
 testeeMain() async {
   await throwAsync().then((v) {
     print('Hello from then()!');
   }).catchError((e, st) {
     print('Caught in catchError: $e!');
   });
+  // Make sure we can chain through off-stack awaiters as well.
+  try {
+    await nestedThrowAsync();
+  } catch (e) {
+    print('Caught in catch: $e!');
+  }
 }
 
 var tests = <IsolateTest>[
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index b8e8b06..2b5db6a 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -1935,7 +1935,6 @@
   Function& function = Function::Handle(zone);
   Code& inlined_code = Code::Handle(zone);
   Closure& async_activation = Closure::Handle(zone);
-  Object& next_async_activation = Object::Handle(zone);
   Array& deopt_frame = Array::Handle(zone);
   bool stack_has_async_function = false;
   Closure& closure = Closure::Handle();
@@ -2045,13 +2044,10 @@
   while (!async_activation.IsNull() &&
          async_activation.context() != Object::null()) {
     ActivationFrame* activation = new (zone) ActivationFrame(async_activation);
-
-    if (!(activation->function().IsAsyncClosure() ||
-          activation->function().IsAsyncGenClosure())) {
-      break;
+    if (activation->function().IsAsyncClosure() ||
+        activation->function().IsAsyncGenClosure()) {
+      activation->ExtractTokenPositionFromAsyncClosure();
     }
-
-    activation->ExtractTokenPositionFromAsyncClosure();
     stack_trace->AddActivation(activation);
     if (FLAG_trace_debugger_stacktrace) {
       OS::PrintErr(
@@ -2059,13 +2055,7 @@
           "closures:\n\t%s\n",
           activation->function().ToFullyQualifiedCString());
     }
-
-    next_async_activation = activation->GetAsyncAwaiter(&caller_closure_finder);
-    if (next_async_activation.IsNull()) {
-      break;
-    }
-
-    async_activation = Closure::RawCast(next_async_activation.ptr());
+    async_activation = caller_closure_finder.FindCaller(async_activation);
   }
 
   return stack_trace;