[vm] Make stack trace generation code more conservative

If the _FutureListener.handleValue function gets deoptimized at a point
when the `this` is no longer needed/live the deoptimization procedure
may override the `this` slot in the caller frame with "<optimized out>".

This has been triggered on the "iso-stress" builder due to using VM
testing flags that cause many deoptimizations in many places.

The stack walking code should take this possibility into consideration
and be therefore conservative.

Alternatives considered:

  * Use "copy parameters" into our own frame approach - like we do
    with methods that  have optional parameters
    => This will make all unoptimized code slower.

  * Add a reachability fence to the _FutureListener.handleValue function
    => The reachabilityFench is only available in dart:_internal for VM
    => If the function could be debugged after fence but before return
       one might be able to trigger the issue nonetheless.

Closes https://github.com/dart-lang/sdk/issues/46823

TEST=Should remove flaky crashes on "iso-stress" builder.

Change-Id: I9d953706b292e02d2ba75fd794c872865faf45d0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/227401
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/vm/stack_trace.cc b/runtime/vm/stack_trace.cc
index 1f29e90..583e055 100644
--- a/runtime/vm/stack_trace.cc
+++ b/runtime/vm/stack_trace.cc
@@ -395,6 +395,10 @@
   if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) {
     // Next, look up caller's closure on the stack and walk backwards
     // through the yields.
+    //
+    // Due the async/async* closures having optional parameters, the
+    // caller-frame's pushed arguments includes the closure and should never be
+    // modified (even in the event of deopts).
     ObjectPtr* last_caller_obj =
         reinterpret_cast<ObjectPtr*>(frame->GetCallerSp());
     closure = FindClosureInFrame(last_caller_obj, function);
@@ -432,6 +436,12 @@
     Object& receiver =
         Object::Handle(*(reinterpret_cast<ObjectPtr*>(frame->GetCallerSp()) +
                          kNumArgsFutureListenerHandleValue));
+    if (receiver.ptr() == Symbols::OptimizedOut().ptr()) {
+      // In the very rare case that _FutureListener.handleValue has deoptimized
+      // it may override the receiver slot in the caller frame with "<optimized
+      // out>" due to the `this` no longer being needed.
+      return Closure::null();
+    }
 
     return caller_closure_finder->GetCallerInFutureListener(receiver);
   }
@@ -579,6 +589,9 @@
         function.parent_function() != Function::null()) {
       if (async_function.ptr() == function.parent_function()) {
         if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) {
+          // Due the async/async* closures having optional parameters, the
+          // caller-frame's pushed arguments includes the closure and should
+          // never be modified (even in the event of deopts).
           ObjectPtr* last_caller_obj =
               reinterpret_cast<ObjectPtr*>(frame->GetCallerSp());
           closure = FindClosureInFrame(last_caller_obj, function);