[vm] Add option for GC at [re]throw.

Add a reduced test for crash / false ConcurrentModificationError observed by the isolate stress test.

TEST=ci
Bug: https://github.com/dart-lang/sdk/issues/60017
Change-Id: I665a7ec48c150d270e751ab13393384613d44674
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/408201
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
diff --git a/runtime/tests/vm/dart/nested_try_throw_environment_test.dart b/runtime/tests/vm/dart/nested_try_throw_environment_test.dart
new file mode 100644
index 0000000..83d2d0a
--- /dev/null
+++ b/runtime/tests/vm/dart/nested_try_throw_environment_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// VMOptions=--gc_at_throw --force_evacuation
+
+import 'package:expect/expect.dart';
+import 'dart:async';
+
+controlFlow() {
+  for (final FutureOr<T> Function<T>(T) func in <dynamic>[id, future]) {
+    try {
+      try {
+        throw "string";
+      } catch (e) {
+        Expect.equals("string", e);
+        rethrow;
+      } finally {
+        Expect.equals(0, func(0));
+      }
+    } catch (e) {
+      Expect.equals("string", e);
+    } finally {
+      Expect.equals(0, func(0));
+    }
+  }
+}
+
+FutureOr<T> future<T>(T value) => value;
+FutureOr<T> id<T>(T value) => value;
+
+main() {
+  for (int i = 0; i < 100; i++) {
+    controlFlow();
+  }
+}
diff --git a/runtime/tools/dartfuzz/flag_fuzzer.dart b/runtime/tools/dartfuzz/flag_fuzzer.dart
index 7708b7e..2d046be 100644
--- a/runtime/tools/dartfuzz/flag_fuzzer.dart
+++ b/runtime/tools/dartfuzz/flag_fuzzer.dart
@@ -52,6 +52,7 @@
   "--verify_before_gc",
   "--verify_store_buffer",
   "--write_protect_code",
+  "--gc_at_throw",
 ];
 
 final compilerFlags = [
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index d581acd..b5df184 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -111,6 +111,7 @@
             verbose_stack_overflow,
             false,
             "Print additional details about stack overflow.");
+DEFINE_FLAG(bool, gc_at_throw, false, "Run evacuating GC at throw and rethrow");
 
 DECLARE_FLAG(int, reload_every);
 DECLARE_FLAG(bool, reload_every_optimized);
@@ -1587,11 +1588,25 @@
 }
 
 DEFINE_RUNTIME_ENTRY(Throw, 1) {
+  if (FLAG_gc_at_throw) {
+    isolate->group()->heap()->CollectGarbage(thread, GCType::kEvacuate,
+                                             GCReason::kDebugging);
+    isolate->group()->heap()->CollectAllGarbage(GCReason::kDebugging,
+                                                /*compact=*/true);
+  }
+
   const Instance& exception = Instance::CheckedHandle(zone, arguments.ArgAt(0));
   Exceptions::Throw(thread, exception);
 }
 
 DEFINE_RUNTIME_ENTRY(ReThrow, 3) {
+  if (FLAG_gc_at_throw) {
+    isolate->group()->heap()->CollectGarbage(thread, GCType::kEvacuate,
+                                             GCReason::kDebugging);
+    isolate->group()->heap()->CollectAllGarbage(GCReason::kDebugging,
+                                                /*compact=*/true);
+  }
+
   const Instance& exception = Instance::CheckedHandle(zone, arguments.ArgAt(0));
   const Instance& stacktrace =
       Instance::CheckedHandle(zone, arguments.ArgAt(1));