Version 3.11.0-70.0.dev

Merge 294b05898f5edf8dd0aaedf51e48778dd59c631e into dev
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index bbbdfb9..b041a07 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -146,6 +146,14 @@
       ldflags += [
         "-rpath",
         "buildtools/mac-$host_cpu/clang/lib/clang/22/lib/darwin",
+
+        # Back from xcodebuild/ReleaseTSAN/dart
+        "-rpath",
+        "@loader_path/../../buildtools/mac-$host_cpu/clang/lib/clang/22/lib/darwin",
+
+        # Back from xcodebuild/ReleaseTSAN/dart-sdk/bin/dart
+        "-rpath",
+        "@loader_path/../../../../buildtools/mac-$host_cpu/clang/lib/clang/22/lib/darwin",
       ]
     }
 
diff --git a/pkg/analyzer/lib/src/dart/resolver/exit_detector.dart b/pkg/analyzer/lib/src/dart/resolver/exit_detector.dart
index a7b1149..0744c20 100644
--- a/pkg/analyzer/lib/src/dart/resolver/exit_detector.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/exit_detector.dart
@@ -187,6 +187,11 @@
   }
 
   @override
+  bool visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
+    return _elementExits(node.propertyName.element);
+  }
+
+  @override
   bool visitEmptyStatement(EmptyStatement node) => false;
 
   @override
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 1448ac9..cccf3cc 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -4315,16 +4315,22 @@
     int mixinIndex,
     NamedTypeImpl mixinName,
   ) {
+    var enclosingClass = _enclosingClass!;
     var mixinType = mixinName.type as InterfaceTypeImpl;
     for (var constraint in mixinType.superclassConstraints) {
-      var superType = _enclosingClass!.supertype as InterfaceTypeImpl;
+      var superType = enclosingClass.supertype as InterfaceTypeImpl;
       superType = superType.withNullability(NullabilitySuffix.none);
 
       bool isSatisfied = typeSystem.isSubtypeOf(superType, constraint);
       if (!isSatisfied) {
         for (int i = 0; i < mixinIndex && !isSatisfied; i++) {
+          // If there are less mixin types than mixin nodes, escape.
+          if (i >= enclosingClass.mixins.length) {
+            return false;
+          }
+          // Probe a previous mixin type.
           isSatisfied = typeSystem.isSubtypeOf(
-            _enclosingClass!.mixins[i],
+            enclosingClass.mixins[i],
             constraint,
           );
         }
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index c647931..daf8a4b 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -5178,11 +5178,12 @@
         return;
       }
 
-      nameScope = FormalParameterScope(
-        TypeParameterScope(nameScope, element.typeParameters),
-        element.formalParameters,
-      );
-      super.visitFunctionExpression(node);
+      nameScope = TypeParameterScope(nameScope, element.typeParameters);
+      node.typeParameters?.accept(this);
+      node.parameters?.accept(this);
+
+      nameScope = FormalParameterScope(nameScope, element.formalParameters);
+      node.body.accept(this);
     } finally {
       nameScope = outerScope;
       _enclosingClosure = outerClosure;
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index f085ec3..7696841 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -75167,6 +75167,47 @@
     );
   }
 
+  test_manifest_constInitializer_functionExpression_formalParameter_defaultValue_formalParameter() async {
+    await _runLibraryManifestScenario(
+      initialCode: r'''
+final f = ({int x = x}) {};
+''',
+      expectedInitialEvents: r'''
+[operation] linkLibraryCycle SDK
+[operation] linkLibraryCycle
+  package:test/test.dart
+    hashForRequirements: #H0
+    declaredGetters
+      f: #M0
+    declaredVariables
+      f: #M1
+    exportMapId: #M2
+    exportMap
+      f: #M0
+''',
+      updatedCode: r'''
+final f = ({int x = x}) {};
+class A {}
+''',
+      expectedUpdatedEvents: r'''
+[operation] linkLibraryCycle
+  package:test/test.dart
+    hashForRequirements: #H1
+    declaredClasses
+      A: #M3
+        interface: #M4
+    declaredGetters
+      f: #M0
+    declaredVariables
+      f: #M1
+    exportMapId: #M5
+    exportMap
+      A: #M3
+      f: #M0
+''',
+    );
+  }
+
   test_manifest_constInitializer_identifier_addIdentifier() async {
     configuration.withElementManifests = true;
     await _runLibraryManifestScenario(
diff --git a/pkg/analyzer/test/src/dart/resolution/function_expression_test.dart b/pkg/analyzer/test/src/dart/resolution/function_expression_test.dart
index 95ea7e3..b51e4d7 100644
--- a/pkg/analyzer/test/src/dart/resolution/function_expression_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/function_expression_test.dart
@@ -2,6 +2,7 @@
 // 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.
 
+import 'package:analyzer/src/error/codes.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'context_collection_resolution.dart';
@@ -95,4 +96,40 @@
   staticType: Null Function<T extends num>()
 ''');
   }
+
+  test_signatureScope_noFormalParameters() async {
+    await assertErrorsInCode(
+      '''
+var f = ({int x = x}) {};
+''',
+      [error(CompileTimeErrorCode.undefinedIdentifier, 18, 1)],
+    );
+
+    var node = findNode.singleFormalParameterList;
+    assertResolvedNodeText(node, r'''
+FormalParameterList
+  leftParenthesis: (
+  leftDelimiter: {
+  parameter: DefaultFormalParameter
+    parameter: SimpleFormalParameter
+      type: NamedType
+        name: int
+        element: dart:core::@class::int
+        type: int
+      name: x
+      declaredElement: <testLibraryFragment> x@14
+        element: isPublic
+          type: int
+    separator: =
+    defaultValue: SimpleIdentifier
+      token: x
+      element: <null>
+      staticType: InvalidType
+    declaredElement: <testLibraryFragment> x@14
+      element: isPublic
+        type: int
+  rightDelimiter: }
+  rightParenthesis: )
+''');
+  }
 }
diff --git a/pkg/analyzer/test/src/dart/resolver/exit_detector_test.dart b/pkg/analyzer/test/src/dart/resolver/exit_detector_test.dart
index f3257bd..41715a1 100644
--- a/pkg/analyzer/test/src/dart/resolver/exit_detector_test.dart
+++ b/pkg/analyzer/test/src/dart/resolver/exit_detector_test.dart
@@ -1027,6 +1027,28 @@
 ''', 0);
   }
 
+  test_dotShorthandPropertyAccess_getterReturnsNever() async {
+    await _assertNthStatementExits(r'''
+class C {
+  static Never get foo => throw 42;
+}
+void f() {
+  C _ = .foo;
+}
+''', 0);
+  }
+
+  test_dotShorthandPropertyAccess_noExit() async {
+    await _assertNthStatementDoesNotExit(r'''
+class C {
+  static C get foo => C();
+}
+void f() {
+  C _ = .foo;
+}
+''', 0);
+  }
+
   test_forStatement_implicitTrue_breakWithLabel() async {
     await _assertNthStatementDoesNotExit(r'''
 void f() {
diff --git a/pkg/analyzer/test/src/diagnostics/mixin_application_not_implemented_interface_test.dart b/pkg/analyzer/test/src/diagnostics/mixin_application_not_implemented_interface_test.dart
index 07262bb..58356cc 100644
--- a/pkg/analyzer/test/src/diagnostics/mixin_application_not_implemented_interface_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/mixin_application_not_implemented_interface_test.dart
@@ -16,6 +16,23 @@
 @reflectiveTest
 class MixinApplicationNotImplementedInterfaceTest
     extends PubPackageResolutionTest {
+  test_class_hasRecursion() async {
+    // https://github.com/dart-lang/sdk/issues/61829
+    await assertErrorsInCode(
+      '''
+class A {}
+abstract class X with Unresolved, M, CycleWithX {}
+mixin M on A {}
+mixin CycleWithX on X {}
+''',
+      [
+        error(CompileTimeErrorCode.recursiveInterfaceInheritance, 26, 1),
+        error(CompileTimeErrorCode.mixinOfNonClass, 33, 10),
+        error(CompileTimeErrorCode.recursiveInterfaceInheritance, 84, 10),
+      ],
+    );
+  }
+
   test_class_matchingInterface() async {
     await assertNoErrorsInCode('''
 abstract class A<T> {}
diff --git a/pkg/compiler/lib/src/js/js.dart b/pkg/compiler/lib/src/js/js.dart
index 1ac621b8..a396cea 100644
--- a/pkg/compiler/lib/src/js/js.dart
+++ b/pkg/compiler/lib/src/js/js.dart
@@ -25,13 +25,11 @@
 String prettyPrint(
   Node node, {
   bool enableMinification = false,
-  bool allowVariableMinification = true,
   bool preferSemicolonToNewlineInMinifiedOutput = false,
 }) {
   // TODO(johnniwinther): Do we need all the options here?
   JavaScriptPrintingOptions options = JavaScriptPrintingOptions(
-    shouldCompressOutput: enableMinification,
-    minifyLocalVariables: allowVariableMinification,
+    minify: enableMinification,
     preferSemicolonToNewlineInMinifiedOutput:
         preferSemicolonToNewlineInMinifiedOutput,
   );
@@ -48,13 +46,12 @@
   DumpInfoJsAstRegistry? monitor,
   JavaScriptAnnotationMonitor annotationMonitor =
       const JavaScriptAnnotationMonitor(),
-  bool allowVariableMinification = true,
   List<CodeOutputListener> listeners = const [],
 }) {
+  bool enableMinification = compilerOptions.enableMinification;
   JavaScriptPrintingOptions options = JavaScriptPrintingOptions(
     utf8: compilerOptions.features.writeUtf8.isEnabled,
-    shouldCompressOutput: compilerOptions.enableMinification,
-    minifyLocalVariables: allowVariableMinification,
+    minify: enableMinification,
   );
   CodeBuffer outBuffer = CodeBuffer(listeners);
   SourceInformationProcessor sourceInformationProcessor =
diff --git a/pkg/compiler/lib/src/js/js_debug.dart b/pkg/compiler/lib/src/js/js_debug.dart
index b45179c..09f3035 100644
--- a/pkg/compiler/lib/src/js/js_debug.dart
+++ b/pkg/compiler/lib/src/js/js_debug.dart
@@ -12,8 +12,7 @@
 /// Unparse the JavaScript [node].
 String nodeToString(Node node, {bool pretty = false}) {
   JavaScriptPrintingOptions options = JavaScriptPrintingOptions(
-    shouldCompressOutput: !pretty,
-    preferSemicolonToNewlineInMinifiedOutput: !pretty,
+    minify: !pretty,
   );
   LenientPrintingContext printingContext = LenientPrintingContext();
   Printer(options, printingContext).visit(node);
diff --git a/pkg/compiler/test/js/js_parser_statements_test.dart b/pkg/compiler/test/js/js_parser_statements_test.dart
index d69f6a4..1fc0d3f 100644
--- a/pkg/compiler/test/js/js_parser_statements_test.dart
+++ b/pkg/compiler/test/js/js_parser_statements_test.dart
@@ -9,7 +9,7 @@
 
 testStatement(String statement, arguments, String expect) {
   jsAst.Node node = js.statement(statement, arguments);
-  String jsText = jsAst.prettyPrint(node, allowVariableMinification: false);
+  String jsText = jsAst.prettyPrint(node);
   Expect.stringEquals(
     expect.trim(),
     jsText.trim(),
diff --git a/pkg/compiler/test/js/js_parser_test.dart b/pkg/compiler/test/js/js_parser_test.dart
index 0757adc..70aa7c0 100644
--- a/pkg/compiler/test/js/js_parser_test.dart
+++ b/pkg/compiler/test/js/js_parser_test.dart
@@ -8,7 +8,7 @@
 
 testExpression(String expression, [String expect = ""]) {
   jsAst.Node node = js(expression);
-  String jsText = jsAst.prettyPrint(node, allowVariableMinification: false);
+  String jsText = jsAst.prettyPrint(node);
   if (expect == "") {
     Expect.stringEquals(expression, jsText);
   } else {
diff --git a/pkg/compiler/test/sourcemaps/helpers/sourcemap_helper.dart b/pkg/compiler/test/sourcemaps/helpers/sourcemap_helper.dart
index c5e257b..67ccc09 100644
--- a/pkg/compiler/test/sourcemaps/helpers/sourcemap_helper.dart
+++ b/pkg/compiler/test/sourcemaps/helpers/sourcemap_helper.dart
@@ -696,7 +696,7 @@
 
   String nodeToString(js.Node node) {
     js.JavaScriptPrintingOptions options = js.JavaScriptPrintingOptions(
-      shouldCompressOutput: true,
+      minify: true,
       preferSemicolonToNewlineInMinifiedOutput: true,
     );
     LenientPrintingContext printingContext = LenientPrintingContext();
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index d39958f..b1e5cdb1 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -1860,13 +1860,13 @@
       // runtime.
       final i = internalizedStringsForJSRuntime.length;
       internalizedString = module.globals.import('s', '$i',
-          w.GlobalType(w.RefType.extern(nullable: true), mutable: false));
+          w.GlobalType(w.RefType.extern(nullable: false), mutable: false));
       internalizedStringsForJSRuntime.add(s);
     } else {
       internalizedString = module.globals.import(
         'S',
         s,
-        w.GlobalType(w.RefType.extern(nullable: true), mutable: false),
+        w.GlobalType(w.RefType.extern(nullable: false), mutable: false),
       );
     }
     _internalizedStringGlobals[(module, s)] = internalizedString;
diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.wat
index d9f9f94..2e3a546 100644
--- a/pkg/dart2wasm/test/ir_tests/deferred.constant.wat
+++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.wat
@@ -25,8 +25,8 @@
     (field $fun (ref $#Closure-0-1)))))
   (type $type256 <...>)
   (type $#DummyStruct <...>)
-  (global $S.globalH1Bar< (import "S" "globalH1Bar<") externref)
-  (global $S.globalH0Foo (import "S" "globalH0Foo") externref)
+  (global $S.globalH1Bar< (import "S" "globalH1Bar<") (ref extern))
+  (global $S.globalH0Foo (import "S" "globalH0Foo") (ref extern))
   (global $global29 (ref $#DummyStruct) <...>)
   (global $"C28 _InterfaceType" (ref $_InterfaceType) <...>)
   (global $"C334 \"h0\"" (ref $JSStringImpl) <...>)
diff --git a/pkg/dart2wasm/test/ir_tests/hello.wat b/pkg/dart2wasm/test/ir_tests/hello.wat
index e94dde2..6b9c527 100644
--- a/pkg/dart2wasm/test/ir_tests/hello.wat
+++ b/pkg/dart2wasm/test/ir_tests/hello.wat
@@ -4,7 +4,7 @@
   (type $JSStringImpl (sub final $#Top (struct
     (field $field0 i32)
     (field $_ref externref))))
-  (global $"S.hello world" (import "S" "hello world") externref)
+  (global $"S.hello world" (import "S" "hello world") (ref extern))
   (global $"C329 \"hello world\"" (ref $JSStringImpl)
     (i32.const 4)
     (global.get $"S.hello world")
diff --git a/pkg/js_ast/lib/src/printer.dart b/pkg/js_ast/lib/src/printer.dart
index 35314e4..61e7834 100644
--- a/pkg/js_ast/lib/src/printer.dart
+++ b/pkg/js_ast/lib/src/printer.dart
@@ -11,14 +11,16 @@
   final bool utf8;
   final bool shouldCompressOutput;
   final bool minifyLocalVariables;
+  final bool minifyStatementLabels;
   final bool preferSemicolonToNewlineInMinifiedOutput;
 
   const JavaScriptPrintingOptions({
     this.utf8 = false,
-    this.shouldCompressOutput = false,
-    this.minifyLocalVariables = false,
+    bool minify = false,
     this.preferSemicolonToNewlineInMinifiedOutput = false,
-  });
+  }) : shouldCompressOutput = minify,
+       minifyLocalVariables = minify,
+       minifyStatementLabels = minify;
 }
 
 /// An environment in which JavaScript printing is done.  Provides emitting of
@@ -89,6 +91,7 @@
   final bool shouldCompressOutput;
   final DanglingElseVisitor danglingElseVisitor;
   final LocalNamer localNamer;
+  final _LabelNamer _labelNamer;
   final bool isDebugContext;
 
   int _charCount = 0;
@@ -140,10 +143,10 @@
     : isDebugContext = context.isDebugContext,
       shouldCompressOutput = options.shouldCompressOutput,
       danglingElseVisitor = DanglingElseVisitor(context),
-      localNamer = determineRenamer(
-        options.shouldCompressOutput,
-        options.minifyLocalVariables,
-      );
+      localNamer = options.minifyLocalVariables
+          ? MinifyRenamer()
+          : IdentityNamer(),
+      _labelNamer = _LabelNamer(options.minifyStatementLabels);
 
   static LocalNamer determineRenamer(
     bool shouldCompressOutput,
@@ -577,7 +580,7 @@
     if (node.targetLabel == null) {
       outIndent('continue');
     } else {
-      outIndent('continue ${node.targetLabel}');
+      outIndent('continue ${_labelNamer.mapLabelName(node.targetLabel!)}');
     }
     outSemicolonLn();
   }
@@ -587,7 +590,7 @@
     if (node.targetLabel == null) {
       outIndent('break');
     } else {
-      outIndent('break ${node.targetLabel}');
+      outIndent('break ${_labelNamer.mapLabelName(node.targetLabel!)}');
     }
     outSemicolonLn();
   }
@@ -724,7 +727,7 @@
 
   @override
   void visitLabeledStatement(LabeledStatement node) {
-    outIndent('${node.label}:');
+    outIndent('${_labelNamer.mapLabelName(node.label)}:');
     blockBody(node.body, needsSeparation: false, needsNewline: true);
   }
 
@@ -740,7 +743,7 @@
         newAtStatementBegin: false,
       );
     }
-    localNamer.enterScope(vars);
+    _enterFunctionScope(vars);
     out('(');
     visitCommaSeparated(
       fun.params,
@@ -771,10 +774,20 @@
       shouldIndent: false,
       needsNewline: false,
     );
-    localNamer.leaveScope();
+    _exitFunctionScope();
     return closingPosition;
   }
 
+  void _enterFunctionScope(VarCollector vars) {
+    localNamer.enterScope(vars);
+    _labelNamer.enterFunction();
+  }
+
+  void _exitFunctionScope() {
+    _labelNamer.exitFunction();
+    localNamer.leaveScope();
+  }
+
   @override
   void visitFunctionDeclaration(FunctionDeclaration declaration) {
     VarCollector vars = VarCollector();
@@ -1393,7 +1406,7 @@
 
   int arrowFunctionOut(ArrowFunction fun, VarCollector vars) {
     // TODO: support static, get/set, async, and generators.
-    localNamer.enterScope(vars);
+    _enterFunctionScope(vars);
     final List<Parameter> params = fun.params;
     if (params.length == 1 && _isIdentifierParameter(params.first)) {
       visitNestedExpression(
@@ -1438,7 +1451,7 @@
       if (needsParens) out(')');
       closingPosition = _charCount;
     }
-    localNamer.leaveScope();
+    _exitFunctionScope();
     return closingPosition;
   }
 
@@ -1637,7 +1650,7 @@
   int methodOut(MethodDefinition node, VarCollector vars) {
     // TODO: support static, get/set, async, and generators.
     Fun fun = node.function;
-    localNamer.enterScope(vars);
+    _enterFunctionScope(vars);
     out('(');
     visitCommaSeparated(
       fun.params,
@@ -1652,7 +1665,7 @@
       shouldIndent: false,
       needsNewline: false,
     );
-    localNamer.leaveScope();
+    _exitFunctionScope();
     return closingPosition;
   }
 
@@ -2139,6 +2152,42 @@
   }
 }
 
+class _LabelNamer {
+  final bool renameLabels;
+
+  Map<String, String> _renamings = {};
+
+  final List<Map<String, String>> _outerScopes = [];
+
+  _LabelNamer(this.renameLabels);
+
+  String mapLabelName(String name) {
+    if (!renameLabels) return name;
+    return _renamings[name] ??= _newLabelName(_renamings, name);
+  }
+
+  static String _newLabelName(Map<String, String> renamings, String name) {
+    assert(!renamings.containsKey(name));
+    int index = renamings.length;
+    if (index < 26) return String.fromCharCode(index + 'A'.codeUnitAt(0));
+    index -= 26;
+    if (index < 26) return String.fromCharCode(index + 'a'.codeUnitAt(0));
+    index -= 26;
+    return 'L$index';
+  }
+
+  void enterFunction() {
+    if (!renameLabels) return;
+    _outerScopes.add(_renamings);
+    _renamings = {};
+  }
+
+  void exitFunction() {
+    if (!renameLabels) return;
+    _renamings = _outerScopes.removeLast();
+  }
+}
+
 /// Information pertaining the enter and exit callbacks for [node].
 class EnterExitNode {
   final EnterExitNode? parent;
diff --git a/pkg/js_ast/test/print_helper.dart b/pkg/js_ast/test/print_helper.dart
index 2a9603c..43913ec 100644
--- a/pkg/js_ast/test/print_helper.dart
+++ b/pkg/js_ast/test/print_helper.dart
@@ -56,11 +56,7 @@
 }
 
 String prettyPrint(Node node) {
-  JavaScriptPrintingOptions options = JavaScriptPrintingOptions(
-    shouldCompressOutput: false,
-    minifyLocalVariables: false,
-    preferSemicolonToNewlineInMinifiedOutput: false,
-  );
+  JavaScriptPrintingOptions options = JavaScriptPrintingOptions();
   SimpleJavaScriptPrintingContext context = SimpleJavaScriptPrintingContext();
   Printer printer = Printer(options, context);
   printer.visit(node);
diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc
index 8c83740..de76ea4 100644
--- a/runtime/vm/parser.cc
+++ b/runtime/vm/parser.cc
@@ -47,7 +47,6 @@
 ParsedFunction::ParsedFunction(Thread* thread, const Function& function)
     : thread_(thread),
       function_(function),
-      code_(Code::Handle(zone(), function.unoptimized_code())),
       scope_(nullptr),
       regexp_compile_data_(nullptr),
       function_type_arguments_(nullptr),
diff --git a/runtime/vm/parser.h b/runtime/vm/parser.h
index 5237e32..070f9c7 100644
--- a/runtime/vm/parser.h
+++ b/runtime/vm/parser.h
@@ -71,7 +71,6 @@
   ParsedFunction(Thread* thread, const Function& function);
 
   const Function& function() const { return function_; }
-  const Code& code() const { return code_; }
 
   LocalScope* scope() const { return scope_; }
   void set_scope(LocalScope* scope) {
@@ -294,7 +293,6 @@
  private:
   Thread* thread_;
   const Function& function_;
-  Code& code_;
   LocalScope* scope_;
   RegExpCompileData* regexp_compile_data_;
   LocalVariable* function_type_arguments_;
diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index 1c1d472..10ba261 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -396,6 +396,30 @@
     throw UnsupportedError("NativeCallable cannot be constructed dynamically.");
   }
 
+  /// Constructs a [NativeCallable] that can be invoked from any thread.
+  ///
+  /// When the native code invokes the function [nativeFunction],
+  /// the [callback] will be executed within the isolate group
+  /// of the [Isolate] which originally constructed the callable.
+  /// Specifically, this means that an attempt to access any
+  /// static or global field which is not shared between
+  /// isolates in a group will result in a [Error].
+  ///
+  /// If an exception is thrown by the [callback], the
+  /// native function will return the `exceptionalReturn`,
+  /// which must be assignable to the return type of
+  /// the [callback].
+  ///
+  /// [callback] and [exceptionalReturn] must be
+  /// _trivially shareable_.
+  ///
+  /// This callback must be [close]d when it is no longer
+  /// needed. An [Isolate] that created the callback will
+  /// be kept alive until [close] is called.
+  ///
+  /// After [NativeCallable.close] is called, invoking
+  /// the [nativeFunction] from native code will cause
+  /// undefined behavior.
   factory NativeCallable.isolateGroupBound(
     @DartRepresentationOf("T") Function callback, {
     Object? exceptionalReturn,
diff --git a/tests/ffi/ffi.status b/tests/ffi/ffi.status
index 12fd963..6b4c9f3 100644
--- a/tests/ffi/ffi.status
+++ b/tests/ffi/ffi.status
@@ -12,9 +12,6 @@
 function_structs_by_value_generated_ret_test: Pass, Slow # https://dartbug.com/47303 https://dartbug.com/45007
 native_assets/asset_*: Pass, Slow # https://dartbug.com/56330
 
-[ $arch == simarm64_arm64 ]
-many_listener_callbacks_test/*: Pass, Slow
-
 [ $builder_tag == optimization_counter_threshold ]
 aliasing_test: SkipByDesign # Already has VMOptions=--optimization-counter-threshold
 function_callbacks_structs_by_value_generated_test: SkipByDesign # Already has VMOptions=--optimization-counter-threshold
@@ -89,6 +86,9 @@
 [ $compiler != dart2analyzer && $compiler != fasta && $runtime != dart_precompiled && $runtime != vm ]
 *: SkipByDesign # FFI is a VM-only feature. (This test suite is part of the default set.)
 
+[ $arch == simarm64_arm64 || $sanitizer == asan || $sanitizer == msan || $sanitizer == tsan ]
+many_listener_callbacks_test/*: Pass, ExtraSlow
+
 # These tests trigger and catch an abort (intentionally) and terminate the VM.
 # They're incompatible with ASAN because not all memory is freed when aborting and
 # with AppJit because the abort the VM before it can generate a snapshot.
diff --git a/tools/VERSION b/tools/VERSION
index f78dfb5..2621cf7 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 3
 MINOR 11
 PATCH 0
-PRERELEASE 69
+PRERELEASE 70
 PRERELEASE_PATCH 0