Version 2.16.0-92.0.dev

Merge commit '016e09f080a047b841b64deefbf1e79910343ddc' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92ea9c7..2852bed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@
 - **Breaking Change** [#47769](https://github.com/dart-lang/sdk/issues/47769):
 The `Platform.packageRoot` API has been removed. It had been marked deprecated
 in 2018, as it doesn't work with any Dart 2.x release.
+- Add optional `sourcePort` parameter to `Socket.connect`, `Socket.startConnect`, `RawSocket.connect` and `RawSocket.startConnect`
 
 #### `dart:isolate`
 
diff --git a/pkg/analysis_server/lib/src/cider/rename.dart b/pkg/analysis_server/lib/src/cider/rename.dart
index 4c38533..0b7a0fc 100644
--- a/pkg/analysis_server/lib/src/cider/rename.dart
+++ b/pkg/analysis_server/lib/src/cider/rename.dart
@@ -5,11 +5,13 @@
 import 'package:analysis_server/src/services/correction/status.dart';
 import 'package:analysis_server/src/services/refactoring/naming_conventions.dart';
 import 'package:analysis_server/src/services/refactoring/refactoring.dart';
+import 'package:analysis_server/src/utilities/flutter.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/dart/micro/resolve_file.dart';
 import 'package:analyzer/src/dart/micro/utils.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 
 class CanRenameResponse {
   final LineInfo lineInfo;
@@ -17,6 +19,8 @@
   final FileResolver _fileResolver;
   final String filePath;
 
+  FlutterWidgetState? _flutterWidgetState;
+
   CanRenameResponse(this.lineInfo, this.refactoringElement, this._fileResolver,
       this.filePath);
 
@@ -24,6 +28,8 @@
 
   CheckNameResponse? checkNewName(String name) {
     var element = refactoringElement.element;
+    _flutterWidgetState = _findFlutterStateClass(element, name);
+
     RefactoringStatus? status;
     if (element is LocalVariableElement) {
       status = validateVariableName(name);
@@ -31,12 +37,36 @@
       status = validateParameterName(name);
     } else if (element is FunctionElement) {
       status = validateFunctionName(name);
-    }
-    if (status == null) {
+    } else if (element is TopLevelVariableElement) {
+      status = validateVariableName(name);
+    } else if (element is TypeAliasElement) {
+      status = validateTypeAliasName(name);
+    } else if (element is ClassElement) {
+      status = validateClassName(name);
+    } else if (status == null) {
       return null;
     }
     return CheckNameResponse(status, this);
   }
+
+  FlutterWidgetState? _findFlutterStateClass(Element element, String newName) {
+    if (Flutter.instance.isStatefulWidgetDeclaration(element)) {
+      var oldStateName = element.displayName + 'State';
+      var library = element.library!;
+      var state =
+          library.getType(oldStateName) ?? library.getType('_' + oldStateName);
+      if (state != null) {
+        var flutterWidgetStateNewName = newName + 'State';
+        // If the State was private, ensure that it stays private.
+        if (state.name.startsWith('_') &&
+            !flutterWidgetStateNewName.startsWith('_')) {
+          flutterWidgetStateNewName = '_' + flutterWidgetStateNewName;
+        }
+        return FlutterWidgetState(state, flutterWidgetStateNewName);
+      }
+    }
+    return null;
+  }
 }
 
 class CheckNameResponse {
@@ -50,9 +80,29 @@
   String get oldName => canRename.refactoringElement.element.displayName;
 
   RenameResponse? computeRenameRanges() {
-    var matches = canRename._fileResolver
-        .findReferences(canRename.refactoringElement.element);
-    return RenameResponse(matches, this);
+    var elements = <Element>[];
+    var element = canRename.refactoringElement.element;
+    if (element is PropertyInducingElement && element.isSynthetic) {
+      var property = element;
+      var getter = property.getter;
+      var setter = property.setter;
+      elements.addIfNotNull(getter);
+      elements.addIfNotNull(setter);
+    } else {
+      elements.add(element);
+    }
+    var matches = <CiderSearchMatch>[];
+    for (var element in elements) {
+      matches.addAll(canRename._fileResolver.findReferences(element));
+    }
+
+    FlutterWidgetRename? flutterRename;
+    if (canRename._flutterWidgetState != null) {
+      var stateWidget = canRename._flutterWidgetState!;
+      var match = canRename._fileResolver.findReferences(stateWidget.state);
+      flutterRename = FlutterWidgetRename(stateWidget.newName, match);
+    }
+    return RenameResponse(matches, this, flutterWidgetRename: flutterRename);
   }
 }
 
@@ -80,6 +130,9 @@
     if (element is MethodElement && element.isOperator) {
       return null;
     }
+    if (element is PropertyAccessorElement) {
+      element = element.variable;
+    }
     if (!_canRenameElement(element)) {
       return null;
     }
@@ -90,59 +143,43 @@
     return null;
   }
 
-  @deprecated
-  CheckNameResponse? checkNewName(
-      String filePath, int line, int column, String name) {
-    var resolvedUnit = _fileResolver.resolve(path: filePath);
-    var lineInfo = resolvedUnit.lineInfo;
-    var offset = lineInfo.getOffsetOfLine(line) + column;
-
-    var node = NodeLocator(offset).searchWithin(resolvedUnit.unit);
-    var element = getElementOfNode(node);
-
-    if (node == null || element == null) {
-      return null;
-    }
-
-    var refactoring = RenameRefactoring.getElementToRename(node, element);
-    if (refactoring == null) {
-      return null;
-    }
-    RefactoringStatus? status;
-    if (element is LocalVariableElement) {
-      status = validateVariableName(name);
-    } else if (element is ParameterElement) {
-      status = validateParameterName(name);
-    } else if (element is FunctionElement) {
-      status = validateFunctionName(name);
-    }
-    if (status == null) {
-      return null;
-    }
-
-    return CheckNameResponse(status,
-        CanRenameResponse(lineInfo, refactoring, _fileResolver, filePath));
-  }
-
   bool _canRenameElement(Element element) {
-    if (element is PropertyAccessorElement) {
-      element = element.variable;
-    }
     var enclosingElement = element.enclosingElement;
+    if (element is ConstructorElement) {
+      return false;
+    }
     if (element is LabelElement || element is LocalElement) {
       return true;
     }
     if (enclosingElement is ClassElement ||
-        enclosingElement is ExtensionElement) {
+        enclosingElement is ExtensionElement ||
+        enclosingElement is CompilationUnitElement) {
       return true;
     }
+
     return false;
   }
 }
 
+class FlutterWidgetRename {
+  final String name;
+  final List<CiderSearchMatch> matches;
+
+  FlutterWidgetRename(this.name, this.matches);
+}
+
+/// The corresponding `State` declaration of a  Flutter `StatefulWidget`.
+class FlutterWidgetState {
+  ClassElement state;
+  String newName;
+
+  FlutterWidgetState(this.state, this.newName);
+}
+
 class RenameResponse {
   final List<CiderSearchMatch> matches;
   final CheckNameResponse checkName;
+  FlutterWidgetRename? flutterWidgetRename;
 
-  RenameResponse(this.matches, this.checkName);
+  RenameResponse(this.matches, this.checkName, {this.flutterWidgetRename});
 }
diff --git a/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart b/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
index 5a009f5..0b28c2c 100644
--- a/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
+++ b/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
@@ -537,30 +537,43 @@
       ..removeAll(registrationsToRemove)
       ..addAll(registrationsToAdd);
 
+    Future<void>? unregistrationRequest;
     if (registrationsToRemove.isNotEmpty) {
       final unregistrations = registrationsToRemove
           .map((r) => Unregistration(id: r.id, method: r.method))
           .toList();
-      await _server.sendRequest(Method.client_unregisterCapability,
+      // It's important not to await this request here, as we must ensure
+      // we cannot re-enter this method until we have sent both the unregister
+      // and register requests to the client atomically.
+      // https://github.com/dart-lang/sdk/issues/47851#issuecomment-988093109
+      unregistrationRequest = _server.sendRequest(
+          Method.client_unregisterCapability,
           UnregistrationParams(unregisterations: unregistrations));
     }
 
+    Future<void>? registrationRequest;
     // Only send the registration request if we have at least one (since
     // otherwise we don't know that the client supports registerCapability).
     if (registrationsToAdd.isNotEmpty) {
-      final registrationResponse = await _server.sendRequest(
-        Method.client_registerCapability,
-        RegistrationParams(registrations: registrationsToAdd),
-      );
-
-      final error = registrationResponse.error;
-      if (error != null) {
-        _server.logErrorToClient(
-          'Failed to register capabilities with client: '
-          '(${error.code}) '
-          '${error.message}',
-        );
-      }
+      registrationRequest = _server
+          .sendRequest(Method.client_registerCapability,
+              RegistrationParams(registrations: registrationsToAdd))
+          .then((registrationResponse) {
+        final error = registrationResponse.error;
+        if (error != null) {
+          _server.logErrorToClient(
+            'Failed to register capabilities with client: '
+            '(${error.code}) '
+            '${error.message}',
+          );
+        }
+      });
     }
+
+    // Only after we have sent both unregistration + registration events may
+    // we await them, knowing another "thread" could not have executed this
+    // method between them.
+    await unregistrationRequest;
+    await registrationRequest;
   }
 }
diff --git a/pkg/analysis_server/test/lsp/initialization_test.dart b/pkg/analysis_server/test/lsp/initialization_test.dart
index 4d006be..413afeb 100644
--- a/pkg/analysis_server/test/lsp/initialization_test.dart
+++ b/pkg/analysis_server/test/lsp/initialization_test.dart
@@ -136,6 +136,94 @@
     );
   }
 
+  Future<void> test_dynamicRegistration_areNotInterleaved() async {
+    // Some of the issues in https://github.com/dart-lang/sdk/issues/47851
+    // (duplicate hovers/code actions/etc.) were caused by duplicate
+    // registrations. This happened when we tried to rebuild registrations
+    // concurrently as the code would:
+    //
+    // 1. compute the new set of registrations
+    // 2. compute which had changed
+    // 3. send _and await_ the unregistration
+    // 4. send _and await_ the new registration
+    //
+    // If the code was triggered multiple times concurrently, it was possible
+    // for step 3 in second request to occur before step 4 in the first, which
+    // would result in unregistrations being sent for registrations that had
+    // not been sent. They would be silently dropped, and then we'd end up with
+    // multiple registrations.
+    //
+    // This test triggers the rebuild via pluginManager.pluginsChangedController
+    // multiple times and ensures the events all arrive in the correct order,
+    // that is, no unregistration ever contains the ID of something that is not
+    // currently registered.
+
+    final registrations = <Registration>[];
+    await monitorDynamicRegistrations(
+      registrations,
+      () => initialize(
+          textDocumentCapabilities:
+              withAllSupportedTextDocumentDynamicRegistrations(
+                  emptyTextDocumentClientCapabilities)),
+    );
+
+    final knownRegistrationsIds =
+        registrations.map((registration) => registration.id).toSet();
+
+    final numberOfReregistrations = 10;
+
+    // Listen to incoming registrations, ensure they're not in the set, and add
+    // them.
+    final registrationsDone = requestsFromServer
+        .where((n) => n.method == Method.client_registerCapability)
+        .take(numberOfReregistrations)
+        .listen((request) {
+      respondTo(request, null);
+      final registrations =
+          RegistrationParams.fromJson(request.params as Map<String, Object?>)
+              .registrations;
+      for (final registration in registrations) {
+        final id = registration.id;
+        if (!knownRegistrationsIds.add(id)) {
+          throw 'Registration $id was already in the existing set!';
+        }
+      }
+    }).asFuture();
+
+    // Listen to incoming unregistrations, verify they're in the set, and remove
+    // them.
+    final unregistrationsDone = requestsFromServer
+        .where((n) => n.method == Method.client_unregisterCapability)
+        .take(numberOfReregistrations)
+        .listen((request) {
+      respondTo(request, null);
+      final unregistrations =
+          UnregistrationParams.fromJson(request.params as Map<String, Object?>)
+              .unregisterations;
+      for (final unregistration in unregistrations) {
+        final id = unregistration.id;
+        if (!knownRegistrationsIds.remove(id)) {
+          throw 'Registration $id was not in the existing set!';
+        }
+      }
+    }).asFuture();
+
+    // Trigger multiple plugin events that will rebuild the registrations.
+    for (var i = 0; i < numberOfReregistrations; i++) {
+      final plugin = configureTestPlugin();
+      plugin.currentSession = PluginSession(plugin);
+      // Ensure they have different file types so the registrations change,
+      // otherwise they will be optimised out as not changing.
+      plugin.currentSession!.interestingFiles = ['*.foo$i'];
+      pluginManager.pluginsChangedController.add(null);
+      await null; // Allow the server to begin processing the change.
+    }
+
+    // Wait for both streams to have handled all of the numberOfReregistrations
+    // expected events (without throwing).
+    await Future.wait([registrationsDone, unregistrationsDone]);
+  }
+
   Future<void> test_dynamicRegistration_containsAppropriateSettings() async {
     // Basic check that the server responds with the capabilities we'd expect,
     // for ex including analysis_options.yaml in text synchronization but not
diff --git a/pkg/analysis_server/test/src/cider/rename_test.dart b/pkg/analysis_server/test/src/cider/rename_test.dart
index ff14890..02368a6 100644
--- a/pkg/analysis_server/test/src/cider/rename_test.dart
+++ b/pkg/analysis_server/test/src/cider/rename_test.dart
@@ -8,6 +8,7 @@
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
+import '../utilities/mock_packages.dart';
 import 'cider_service.dart';
 
 void main() {
@@ -20,6 +21,22 @@
 class CiderRenameComputerTest extends CiderServiceTest {
   late _CorrectionContext _correctionContext;
 
+  @override
+  void setUp() {
+    super.setUp();
+    BazelMockPackages.instance.addFlutter(resourceProvider);
+  }
+
+  void test_canRename_class() {
+    var refactor = _compute(r'''
+class ^Old {}
+}
+''');
+
+    expect(refactor!.refactoringElement.element.name, 'Old');
+    expect(refactor.refactoringElement.offset, _correctionContext.offset);
+  }
+
   void test_canRename_field() {
     var refactor = _compute(r'''
 class A {
@@ -104,6 +121,15 @@
     expect(refactor.refactoringElement.offset, _correctionContext.offset);
   }
 
+  void test_checkName_class() {
+    var result = _checkName(r'''
+class ^Old {}
+''', 'New');
+
+    expect(result!.status.problems.length, 0);
+    expect(result.oldName, 'Old');
+  }
+
   void test_checkName_function() {
     var result = _checkName(r'''
 int ^foo() => 2;
@@ -146,6 +172,131 @@
     expect(result.oldName, 'a');
   }
 
+  void test_checkName_topLevelVariable() {
+    var result = _checkName(r'''
+var ^foo;
+''', 'bar');
+
+    expect(result!.status.problems.length, 0);
+    expect(result.oldName, 'foo');
+  }
+
+  void test_checkName_TypeAlias() {
+    var result = _checkName(r'''
+typedef ^Foo = void Function();
+''', 'Bar');
+
+    expect(result!.status.problems.length, 0);
+    expect(result.oldName, 'Foo');
+  }
+
+  void test_rename_class() {
+    var result = _rename(r'''
+class ^Old implements Other {
+  Old() {}
+  Old.named() {}
+}
+class Other {
+  factory Other.a() = Old;
+  factory Other.b() = Old.named;
+}
+void f() {
+  Old t1 = new Old();
+  Old t2 = new Old.named();
+}
+''', 'New');
+
+    expect(result!.matches.length, 1);
+    expect(result.matches, [
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'), [
+        CharacterLocation(1, 7),
+        CharacterLocation(2, 3),
+        CharacterLocation(3, 3),
+        CharacterLocation(6, 23),
+        CharacterLocation(7, 23),
+        CharacterLocation(10, 3),
+        CharacterLocation(10, 16),
+        CharacterLocation(11, 3),
+        CharacterLocation(11, 16)
+      ])
+    ]);
+  }
+
+  void test_rename_class_flutterWidget() {
+    var result = _rename(r'''
+import 'package:flutter/material.dart';
+
+class ^TestPage extends StatefulWidget {
+  const TestPage();
+
+  @override
+  State<TestPage> createState() => TestPageState();
+}
+
+class TestPageState extends State<TestPage> {
+  @override
+  Widget build(BuildContext context) => throw 0;
+}
+''', 'NewPage');
+
+    expect(result!.matches.length, 1);
+    expect(result.matches, [
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'), [
+        CharacterLocation(3, 7),
+        CharacterLocation(4, 9),
+        CharacterLocation(7, 9),
+        CharacterLocation(10, 35)
+      ])
+    ]);
+    expect(result.flutterWidgetRename != null, isTrue);
+    expect(result.flutterWidgetRename!.name, 'NewPageState');
+    expect(result.flutterWidgetRename!.matches, [
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
+          [CharacterLocation(7, 36), CharacterLocation(10, 7)])
+    ]);
+  }
+
+  void test_rename_function() {
+    var result = _rename(r'''
+test() {}
+^foo() {}
+void f() {
+  print(test);
+  print(test());
+  foo();
+}
+''', 'bar');
+
+    expect(result!.matches.length, 1);
+    expect(result.matches, [
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'), [
+        CharacterLocation(2, 1),
+        CharacterLocation(6, 3),
+      ])
+    ]);
+  }
+
+  void test_rename_function_imported() {
+    var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
+foo() {}
+''');
+    fileResolver.resolve(path: a.path);
+    var result = _rename(r'''
+import 'a.dart';
+void f() {
+  ^foo();
+}
+''', 'bar');
+    expect(result!.matches.length, 2);
+    expect(result.matches, [
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/a.dart'), [
+        CharacterLocation(1, 1),
+      ]),
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
+          [CharacterLocation(3, 3)])
+    ]);
+  }
+
   void test_rename_local() {
     var result = _rename(r'''
 void foo() {
@@ -160,31 +311,6 @@
             [CharacterLocation(2, 7), CharacterLocation(2, 22)]));
   }
 
-  void test_rename_method() {
-    var a = newFile('/workspace/dart/test/lib/a.dart', content: r'''
-void foo() {
-  a;
-}
-''');
-    fileResolver.resolve(path: a.path);
-
-    var result = _rename(r'''
-import 'a.dart';
-
-main() {
-^foo();
-}
-''', 'bar');
-
-    expect(result!.matches.length, 2);
-    expect(result.matches, [
-      CiderSearchMatch(convertPath('/workspace/dart/test/lib/a.dart'),
-          [CharacterLocation(1, 6)]),
-      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
-          [CharacterLocation(4, 1)])
-    ]);
-  }
-
   void test_rename_parameter() {
     var result = _rename(r'''
 void foo(String ^a) {
@@ -195,6 +321,38 @@
     expect(result.checkName.oldName, 'a');
   }
 
+  void test_rename_propertyAccessor() {
+    var result = _rename(r'''
+get foo {}
+set foo(x) {}
+void f() {
+  print(foo);
+  ^foo = 1;
+  foo += 2;
+''', 'bar');
+    expect(result!.matches, [
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
+          [CharacterLocation(1, 5), CharacterLocation(4, 9)]),
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'), [
+        CharacterLocation(2, 5),
+        CharacterLocation(5, 3),
+        CharacterLocation(6, 3)
+      ])
+    ]);
+  }
+
+  void test_typeAlias_functionType() {
+    var result = _rename(r'''
+typedef ^F = void Function();
+void f(F a) {}
+''', 'bar');
+
+    expect(result!.matches, [
+      CiderSearchMatch(convertPath('/workspace/dart/test/lib/test.dart'),
+          [CharacterLocation(1, 9), CharacterLocation(2, 8)])
+    ]);
+  }
+
   CheckNameResponse? _checkName(String content, String newName) {
     _updateFile(content);
 
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index 86b03d0..af87eed 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -391,6 +391,7 @@
       buffer.addBool(implicitCasts);
       buffer.addBool(implicitDynamic);
       buffer.addBool(propagateLinterExceptions);
+      buffer.addBool(strictCasts);
       buffer.addBool(strictInference);
       buffer.addBool(strictRawTypes);
       buffer.addBool(useFastaParser);
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
index 58450c4..cabe570 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
@@ -4,7 +4,7 @@
 
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/error/error.dart';
-import 'package:analyzer/src/dart/error/lint_codes.dart';
+import 'package:analyzer/src/error/codes.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -25,6 +25,38 @@
     return driver.test.libraryContextTestView.linkedCycles;
   }
 
+  test_analysisOptions_strictCasts() async {
+    useEmptyByteStore();
+
+    // Configure `strict-casts: false`.
+    writeTestPackageAnalysisOptionsFile(
+      AnalysisOptionsFileConfig(
+        strictCasts: false,
+      ),
+    );
+
+    newFile(testFilePath, content: r'''
+dynamic a = 0;
+int b = a;
+''');
+
+    // `strict-cast: false`, so no errors.
+    assertErrorsInList(await _computeTestFileErrors(), []);
+
+    // Configure `strict-casts: true`.
+    disposeAnalysisContextCollection();
+    writeTestPackageAnalysisOptionsFile(
+      AnalysisOptionsFileConfig(
+        strictCasts: true,
+      ),
+    );
+
+    // `strict-cast: true`, so has errors.
+    assertErrorsInList(await _computeTestFileErrors(), [
+      error(CompileTimeErrorCode.INVALID_ASSIGNMENT, 23, 1),
+    ]);
+  }
+
   test_change_factoryConstructor_addEqNothing() async {
     await resolveTestCode(r'''
 class A {
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index 579b10e..994427b 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -684,8 +684,10 @@
   @override
   void describe(TestCase testCase, Progress progress, OutputWriter output) {
     if (invalidJsonStdout != null) {
+      output.subsection("analyzer json parse result");
+      output.write("- parse failed");
       output.subsection("invalid analyzer json");
-      output.write(invalidJsonStdout);
+      super.describe(testCase, progress, output);
       return;
     }
 
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index efa37c8..17b9404 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -144,7 +144,7 @@
   V(SocketBase_IsBindError, 2)                                                 \
   V(Socket_Available, 1)                                                       \
   V(Socket_AvailableDatagram, 1)                                               \
-  V(Socket_CreateBindConnect, 5)                                               \
+  V(Socket_CreateBindConnect, 6)                                               \
   V(Socket_CreateUnixDomainBindConnect, 4)                                     \
   V(Socket_CreateBindDatagram, 6)                                              \
   V(Socket_CreateConnect, 4)                                                   \
diff --git a/runtime/bin/socket.cc b/runtime/bin/socket.cc
index 62cc613..21bfadf 100644
--- a/runtime/bin/socket.cc
+++ b/runtime/bin/socket.cc
@@ -396,8 +396,13 @@
   SocketAddress::SetAddrPort(&addr, static_cast<intptr_t>(port));
   RawAddr sourceAddr;
   SocketAddress::GetSockAddr(Dart_GetNativeArgument(args, 3), &sourceAddr);
+  Dart_Handle source_port_arg = Dart_GetNativeArgument(args, 4);
+  int64_t source_port =
+      DartUtils::GetInt64ValueCheckRange(source_port_arg, 0, 65535);
+  SocketAddress::SetAddrPort(&sourceAddr, static_cast<intptr_t>(source_port));
+
   if (addr.addr.sa_family == AF_INET6) {
-    Dart_Handle scope_id_arg = Dart_GetNativeArgument(args, 4);
+    Dart_Handle scope_id_arg = Dart_GetNativeArgument(args, 5);
     int64_t scope_id =
         DartUtils::GetInt64ValueCheckRange(scope_id_arg, 0, 65535);
     SocketAddress::SetAddrScope(&addr, scope_id);
diff --git a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
index 4960213..413116c 100644
--- a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
+++ b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
@@ -475,13 +475,13 @@
 class RawSocket {
   @patch
   static Future<RawSocket> connect(dynamic host, int port,
-      {dynamic sourceAddress, Duration? timeout}) {
+      {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) {
     throw UnsupportedError("RawSocket constructor");
   }
 
   @patch
   static Future<ConnectionTask<RawSocket>> startConnect(dynamic host, int port,
-      {dynamic sourceAddress}) {
+      {dynamic sourceAddress, int sourcePort = 0}) {
     throw UnsupportedError("RawSocket constructor");
   }
 }
@@ -490,13 +490,13 @@
 class Socket {
   @patch
   static Future<Socket> _connect(dynamic host, int port,
-      {dynamic sourceAddress, Duration? timeout}) {
+      {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) {
     throw UnsupportedError("Socket constructor");
   }
 
   @patch
   static Future<ConnectionTask<Socket>> _startConnect(dynamic host, int port,
-      {dynamic sourceAddress}) {
+      {dynamic sourceAddress, int sourcePort = 0}) {
     throw UnsupportedError("Socket constructor");
   }
 }
diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
index 6a88d0f..ae50e7b 100644
--- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
@@ -475,13 +475,13 @@
 class RawSocket {
   @patch
   static Future<RawSocket> connect(dynamic host, int port,
-      {dynamic sourceAddress, Duration? timeout}) {
+      {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) {
     throw new UnsupportedError("RawSocket constructor");
   }
 
   @patch
   static Future<ConnectionTask<RawSocket>> startConnect(dynamic host, int port,
-      {dynamic sourceAddress}) {
+      {dynamic sourceAddress, int sourcePort = 0}) {
     throw new UnsupportedError("RawSocket constructor");
   }
 }
@@ -490,13 +490,13 @@
 class Socket {
   @patch
   static Future<Socket> _connect(dynamic host, int port,
-      {dynamic sourceAddress, Duration? timeout}) {
+      {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) {
     throw new UnsupportedError("Socket constructor");
   }
 
   @patch
   static Future<ConnectionTask<Socket>> _startConnect(dynamic host, int port,
-      {dynamic sourceAddress}) {
+      {dynamic sourceAddress, int sourcePort = 0}) {
     throw new UnsupportedError("Socket constructor");
   }
 }
diff --git a/sdk/lib/_internal/vm/bin/socket_patch.dart b/sdk/lib/_internal/vm/bin/socket_patch.dart
index 8812068..d681626 100644
--- a/sdk/lib/_internal/vm/bin/socket_patch.dart
+++ b/sdk/lib/_internal/vm/bin/socket_patch.dart
@@ -17,14 +17,14 @@
 class RawSocket {
   @patch
   static Future<RawSocket> connect(dynamic host, int port,
-      {dynamic sourceAddress, Duration? timeout}) {
-    return _RawSocket.connect(host, port, sourceAddress, timeout);
+      {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) {
+    return _RawSocket.connect(host, port, sourceAddress, sourcePort, timeout);
   }
 
   @patch
   static Future<ConnectionTask<RawSocket>> startConnect(dynamic host, int port,
-      {dynamic sourceAddress}) {
-    return _RawSocket.startConnect(host, port, sourceAddress);
+      {dynamic sourceAddress, int sourcePort = 0}) {
+    return _RawSocket.startConnect(host, port, sourceAddress, sourcePort);
   }
 }
 
@@ -654,7 +654,7 @@
   }
 
   static Future<ConnectionTask<_NativeSocket>> startConnect(
-      dynamic host, int port, dynamic sourceAddress) {
+      dynamic host, int port, dynamic sourceAddress, int sourcePort) {
     // Looks up [sourceAddress] to one or more IP addresses,
     // then tries connecting to each one until a connection succeeds.
     // Attempts are staggered by a minimum delay, so a new
@@ -666,6 +666,7 @@
       host = escapeLinkLocalAddress(host);
     }
     _throwOnBadPort(port);
+    _throwOnBadPort(sourcePort);
     _InternetAddress? source;
     if (sourceAddress != null) {
       if (sourceAddress is _InternetAddress) {
@@ -682,7 +683,7 @@
 
     return new Future.value(host).then<ConnectionTask<_NativeSocket>>((host) {
       if (host is _InternetAddress) {
-        return tryConnectToResolvedAddresses(host, port, source,
+        return tryConnectToResolvedAddresses(host, port, source, sourcePort,
             Stream.value(<_InternetAddress>[host]), stackTrace);
       }
       final hostname = host as String;
@@ -700,7 +701,7 @@
               : lookupAsStream(hostname);
 
       return tryConnectToResolvedAddresses(
-          host, port, source, stream, stackTrace);
+          host, port, source, sourcePort, stream, stackTrace);
     });
   }
 
@@ -708,6 +709,7 @@
       dynamic host,
       int port,
       _InternetAddress? source,
+      int sourcePort,
       Stream<List<InternetAddress>> addresses,
       StackTrace callerStackTrace) {
     // Completer for result.
@@ -758,10 +760,16 @@
             connectionResult is OSError);
       } else {
         final address_ = address as _InternetAddress;
-        if (source == null) {
+        if (source == null && sourcePort == 0) {
           connectionResult = socket.nativeCreateConnect(
               address_._in_addr, port, address_._scope_id);
         } else {
+          // allow specified port without address
+          if (source == null) {
+            source = address_.type == InternetAddressType.IPv4
+                ? _InternetAddress.anyIPv4
+                : _InternetAddress.anyIPv6;
+          }
           if (source.type != InternetAddressType.IPv4 &&
               source.type != InternetAddressType.IPv6) {
             return SocketException(
@@ -773,8 +781,8 @@
                 "${InternetAddressType.IPv6} but was ${source.type}",
                 address: address);
           }
-          connectionResult = socket.nativeCreateBindConnect(
-              address_._in_addr, port, source._in_addr, address_._scope_id);
+          connectionResult = socket.nativeCreateBindConnect(address_._in_addr,
+              port, source._in_addr, sourcePort, address_._scope_id);
         }
         assert(connectionResult == true || connectionResult is OSError);
       }
@@ -935,9 +943,9 @@
     return new ConnectionTask<_NativeSocket>._(result.future, onCancel);
   }
 
-  static Future<_NativeSocket> connect(
-      dynamic host, int port, dynamic sourceAddress, Duration? timeout) {
-    return startConnect(host, port, sourceAddress)
+  static Future<_NativeSocket> connect(dynamic host, int port,
+      dynamic sourceAddress, int sourcePort, Duration? timeout) {
+    return startConnect(host, port, sourceAddress, sourcePort)
         .then((ConnectionTask<_NativeSocket> task) {
       Future<_NativeSocket> socketFuture = task.socket;
       if (timeout != null) {
@@ -1653,8 +1661,8 @@
   @pragma("vm:external-name", "Socket_CreateUnixDomainConnect")
   external nativeCreateUnixDomainConnect(String addr, _Namespace namespace);
   @pragma("vm:external-name", "Socket_CreateBindConnect")
-  external nativeCreateBindConnect(
-      Uint8List addr, int port, Uint8List sourceAddr, int scope_id);
+  external nativeCreateBindConnect(Uint8List addr, int port,
+      Uint8List sourceAddr, int sourcePort, int scope_id);
   @pragma("vm:external-name", "Socket_CreateUnixDomainBindConnect")
   external nativeCreateUnixDomainBindConnect(
       String addr, String sourceAddr, _Namespace namespace);
@@ -1792,9 +1800,9 @@
   // Flag to handle Ctrl-D closing of stdio on Mac OS.
   bool _isMacOSTerminalInput = false;
 
-  static Future<RawSocket> connect(
-      dynamic host, int port, dynamic sourceAddress, Duration? timeout) {
-    return _NativeSocket.connect(host, port, sourceAddress, timeout)
+  static Future<RawSocket> connect(dynamic host, int port,
+      dynamic sourceAddress, int sourcePort, Duration? timeout) {
+    return _NativeSocket.connect(host, port, sourceAddress, sourcePort, timeout)
         .then((socket) {
       if (!const bool.fromEnvironment("dart.vm.product")) {
         _SocketProfile.collectNewSocket(
@@ -1805,8 +1813,8 @@
   }
 
   static Future<ConnectionTask<_RawSocket>> startConnect(
-      dynamic host, int port, dynamic sourceAddress) {
-    return _NativeSocket.startConnect(host, port, sourceAddress)
+      dynamic host, int port, dynamic sourceAddress, int sourcePort) {
+    return _NativeSocket.startConnect(host, port, sourceAddress, sourcePort)
         .then((ConnectionTask<_NativeSocket> nativeTask) {
       final Future<_RawSocket> raw =
           nativeTask.socket.then((_NativeSocket nativeSocket) {
@@ -2016,16 +2024,19 @@
 class Socket {
   @patch
   static Future<Socket> _connect(dynamic host, int port,
-      {dynamic sourceAddress, Duration? timeout}) {
+      {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) {
     return RawSocket.connect(host, port,
-            sourceAddress: sourceAddress, timeout: timeout)
+            sourceAddress: sourceAddress,
+            sourcePort: sourcePort,
+            timeout: timeout)
         .then((socket) => new _Socket(socket));
   }
 
   @patch
   static Future<ConnectionTask<Socket>> _startConnect(dynamic host, int port,
-      {dynamic sourceAddress}) {
-    return RawSocket.startConnect(host, port, sourceAddress: sourceAddress)
+      {dynamic sourceAddress, int sourcePort = 0}) {
+    return RawSocket.startConnect(host, port,
+            sourceAddress: sourceAddress, sourcePort: sourcePort)
         .then((rawTask) {
       Future<Socket> socket =
           rawTask.socket.then((rawSocket) => new _Socket(rawSocket));
diff --git a/sdk/lib/io/overrides.dart b/sdk/lib/io/overrides.dart
index c97e84d..a124c76 100644
--- a/sdk/lib/io/overrides.dart
+++ b/sdk/lib/io/overrides.dart
@@ -81,10 +81,10 @@
 
       // Socket
       Future<Socket> Function(dynamic, int,
-              {dynamic sourceAddress, Duration? timeout})?
+              {dynamic sourceAddress, int sourcePort, Duration? timeout})?
           socketConnect,
       Future<ConnectionTask<Socket>> Function(dynamic, int,
-              {dynamic sourceAddress})?
+              {dynamic sourceAddress, int sourcePort})?
           socketStartConnect,
 
       // ServerSocket
@@ -269,9 +269,9 @@
   /// When this override is installed, this functions overrides the behavior of
   /// `Socket.connect(...)`.
   Future<Socket> socketConnect(host, int port,
-      {sourceAddress, Duration? timeout}) {
+      {sourceAddress, int sourcePort = 0, Duration? timeout}) {
     return Socket._connect(host, port,
-        sourceAddress: sourceAddress, timeout: timeout);
+        sourceAddress: sourceAddress, sourcePort: sourcePort, timeout: timeout);
   }
 
   /// Asynchronously returns a [ConnectionTask] that connects to the given host
@@ -280,8 +280,9 @@
   /// When this override is installed, this functions overrides the behavior of
   /// `Socket.startConnect(...)`.
   Future<ConnectionTask<Socket>> socketStartConnect(host, int port,
-      {sourceAddress}) {
-    return Socket._startConnect(host, port, sourceAddress: sourceAddress);
+      {sourceAddress, int sourcePort = 0}) {
+    return Socket._startConnect(host, port,
+        sourceAddress: sourceAddress, sourcePort: sourcePort);
   }
 
   // ServerSocket
@@ -355,9 +356,11 @@
 
   // Socket
   Future<Socket> Function(dynamic, int,
-      {dynamic sourceAddress, Duration? timeout})? _socketConnect;
+      {dynamic sourceAddress,
+      int sourcePort,
+      Duration? timeout})? _socketConnect;
   Future<ConnectionTask<Socket>> Function(dynamic, int,
-      {dynamic sourceAddress})? _socketStartConnect;
+      {dynamic sourceAddress, int sourcePort})? _socketStartConnect;
 
   // ServerSocket
   Future<ServerSocket> Function(dynamic, int,
@@ -518,30 +521,34 @@
   // Socket
   @override
   Future<Socket> socketConnect(host, int port,
-      {sourceAddress, Duration? timeout}) {
+      {sourceAddress, int sourcePort = 0, Duration? timeout}) {
     if (_socketConnect != null) {
       return _socketConnect!(host, port,
           sourceAddress: sourceAddress, timeout: timeout);
     }
     if (_previous != null) {
       return _previous!.socketConnect(host, port,
-          sourceAddress: sourceAddress, timeout: timeout);
+          sourceAddress: sourceAddress,
+          sourcePort: sourcePort,
+          timeout: timeout);
     }
     return super.socketConnect(host, port,
-        sourceAddress: sourceAddress, timeout: timeout);
+        sourceAddress: sourceAddress, sourcePort: sourcePort, timeout: timeout);
   }
 
   @override
   Future<ConnectionTask<Socket>> socketStartConnect(host, int port,
-      {sourceAddress}) {
+      {sourceAddress, int sourcePort = 0}) {
     if (_socketStartConnect != null) {
-      return _socketStartConnect!(host, port, sourceAddress: sourceAddress);
+      return _socketStartConnect!(host, port,
+          sourceAddress: sourceAddress, sourcePort: sourcePort);
     }
     if (_previous != null) {
-      return _previous!
-          .socketStartConnect(host, port, sourceAddress: sourceAddress);
+      return _previous!.socketStartConnect(host, port,
+          sourceAddress: sourceAddress, sourcePort: sourcePort);
     }
-    return super.socketStartConnect(host, port, sourceAddress: sourceAddress);
+    return super.socketStartConnect(host, port,
+        sourceAddress: sourceAddress, sourcePort: sourcePort);
   }
 
   // ServerSocket
diff --git a/sdk/lib/io/socket.dart b/sdk/lib/io/socket.dart
index 26cb3ef..3c102c5 100644
--- a/sdk/lib/io/socket.dart
+++ b/sdk/lib/io/socket.dart
@@ -589,19 +589,22 @@
   /// be a [String] or an [InternetAddress]. If a [String] is passed it must
   /// hold a numeric IP address.
   ///
+  /// The [sourcePort] defines the local port to bind to. If [sourcePort] is
+  /// not specified or zero, a port will be chosen.
+  ///
   /// The argument [timeout] is used to specify the maximum allowed time to wait
   /// for a connection to be established. If [timeout] is longer than the system
   /// level timeout duration, a timeout may occur sooner than specified in
   /// [timeout]. On timeout, a [SocketException] is thrown and all ongoing
   /// connection attempts to [host] are cancelled.
   external static Future<RawSocket> connect(host, int port,
-      {sourceAddress, Duration? timeout});
+      {sourceAddress, int sourcePort = 0, Duration? timeout});
 
   /// Like [connect], but returns a [Future] that completes with a
   /// [ConnectionTask] that can be cancelled if the [RawSocket] is no
   /// longer needed.
   external static Future<ConnectionTask<RawSocket>> startConnect(host, int port,
-      {sourceAddress});
+      {sourceAddress, int sourcePort = 0});
 
   /// The number of received and non-read bytes in the socket that can be read.
   int available();
@@ -758,40 +761,46 @@
   /// be a [String] or an [InternetAddress]. If a [String] is passed it must
   /// hold a numeric IP address.
   ///
+  /// The [sourcePort] defines the local port to bind to. If [sourcePort] is
+  /// not specified or zero, a port will be chosen.
+  ///
   /// The argument [timeout] is used to specify the maximum allowed time to wait
   /// for a connection to be established. If [timeout] is longer than the system
   /// level timeout duration, a timeout may occur sooner than specified in
   /// [timeout]. On timeout, a [SocketException] is thrown and all ongoing
   /// connection attempts to [host] are cancelled.
   static Future<Socket> connect(host, int port,
-      {sourceAddress, Duration? timeout}) {
+      {sourceAddress, int sourcePort = 0, Duration? timeout}) {
     final IOOverrides? overrides = IOOverrides.current;
     if (overrides == null) {
       return Socket._connect(host, port,
-          sourceAddress: sourceAddress, timeout: timeout);
+          sourceAddress: sourceAddress,
+          sourcePort: sourcePort,
+          timeout: timeout);
     }
     return overrides.socketConnect(host, port,
-        sourceAddress: sourceAddress, timeout: timeout);
+        sourceAddress: sourceAddress, sourcePort: sourcePort, timeout: timeout);
   }
 
   /// Like [connect], but returns a [Future] that completes with a
   /// [ConnectionTask] that can be cancelled if the [Socket] is no
   /// longer needed.
   static Future<ConnectionTask<Socket>> startConnect(host, int port,
-      {sourceAddress}) {
+      {sourceAddress, int sourcePort = 0}) {
     final IOOverrides? overrides = IOOverrides.current;
     if (overrides == null) {
-      return Socket._startConnect(host, port, sourceAddress: sourceAddress);
+      return Socket._startConnect(host, port,
+          sourceAddress: sourceAddress, sourcePort: sourcePort);
     }
     return overrides.socketStartConnect(host, port,
-        sourceAddress: sourceAddress);
+        sourceAddress: sourceAddress, sourcePort: sourcePort);
   }
 
   external static Future<Socket> _connect(host, int port,
-      {sourceAddress, Duration? timeout});
+      {sourceAddress, int sourcePort = 0, Duration? timeout});
 
   external static Future<ConnectionTask<Socket>> _startConnect(host, int port,
-      {sourceAddress});
+      {sourceAddress, int sourcePort = 0});
 
   /// Destroys the socket in both directions.
   ///
diff --git a/tests/standalone/io/io_override_test.dart b/tests/standalone/io/io_override_test.dart
index fb8c780..fdd49ba 100644
--- a/tests/standalone/io/io_override_test.dart
+++ b/tests/standalone/io/io_override_test.dart
@@ -161,12 +161,12 @@
 }
 
 Future<Socket> socketConnect(dynamic host, int port,
-    {dynamic sourceAddress, Duration? timeout}) {
+    {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) {
   throw "";
 }
 
 Future<ConnectionTask<Socket>> socketStartConnect(dynamic host, int port,
-    {dynamic sourceAddress}) {
+    {dynamic sourceAddress, int sourcePort = 0}) {
   throw "";
 }
 
diff --git a/tests/standalone/io/socket_local_port_test.dart b/tests/standalone/io/socket_local_port_test.dart
new file mode 100644
index 0000000..f7251ec
--- /dev/null
+++ b/tests/standalone/io/socket_local_port_test.dart
@@ -0,0 +1,162 @@
+// Copyright (c) 2021, 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.
+
+import 'dart:async';
+import "dart:io";
+
+import "package:expect/expect.dart";
+
+Future testCustomPortIPv4() async {
+  String clientAddress = "127.0.0.1";
+  int customLocalPort = 50988;
+  String serverAddress = clientAddress;
+  int port = 50989;
+
+  testCustomPort(serverAddress, port, clientAddress, customLocalPort);
+}
+
+Future testCustomPortIPv6() async {
+  String clientAddress = "::1";
+  int customLocalPort = 50988;
+  String serverAddress = clientAddress;
+  int port = 50989;
+
+  testCustomPort(serverAddress, port, clientAddress, customLocalPort);
+}
+
+Future testCustomPortIPv4NoSourceAddress() async {
+  String expectedClientAddress = "127.0.0.1";
+  int customLocalPort = 50988;
+  String serverAddress = expectedClientAddress;
+  int port = 50989;
+
+  testCustomPort(serverAddress, port, expectedClientAddress, customLocalPort);
+}
+
+Future testCustomPortIPv6NoSourceAddress() async {
+  String expectedClientAddress = "::1";
+  int customLocalPort = 50988;
+  String serverAddress = expectedClientAddress;
+  int port = 50989;
+
+  testCustomPort(serverAddress, port, expectedClientAddress, customLocalPort);
+}
+
+Future testNoCustomPortIPv4() async {
+  String host = "127.0.0.1";
+  String clientAddress = host;
+  int serverPort = 39998;
+
+  await testNoCustomPortNoSourceAddress(host, serverPort, clientAddress);
+}
+
+Future testNoCustomPortIPv6() async {
+  String host = "::1";
+  String clientAddress = host;
+  int serverPort = 39998;
+
+  await testNoCustomPortNoSourceAddress(host, serverPort, clientAddress);
+}
+
+Future testNoCustomPortNoSourceAddressIPv4() async {
+  String host = "127.0.0.1";
+  String expectedAddress = host;
+  int serverPort = 39998;
+
+  await testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress);
+}
+
+Future testNoCustomPortNoSourceAddressIPv6() async {
+  String host = "::1";
+  String expectedAddress = host;
+  int serverPort = 39998;
+
+  await testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress);
+}
+
+// Core functionality
+void testCustomPort(
+    String host, int port, String sourceAddress, int sourcePort) async {
+  var server = await ServerSocket.bind(host, port);
+  server.listen((client) {
+    Expect.equals(server.port, port);
+    Expect.equals(client.remotePort, sourcePort);
+    Expect.equals(client.address.address, sourceAddress);
+    client.destroy();
+  });
+
+  Socket s = await Socket.connect(host, port,
+      sourceAddress: sourceAddress, sourcePort: sourcePort);
+  s.destroy();
+  server.close();
+}
+
+Future testCustomPortNoSourceAddress(
+    String host, int port, String expectedAddress, int sourcePort) async {
+  Completer completer = new Completer();
+  var server = await ServerSocket.bind(host, port);
+
+  server.listen((client) {
+    Expect.equals(server.port, port);
+    Expect.equals(client.remotePort, sourcePort);
+    Expect.equals(client.address.address, expectedAddress);
+    client.destroy();
+    completer.complete();
+  });
+
+  Socket s = await Socket.connect(host, port, sourcePort: sourcePort);
+  s.destroy();
+  server.close();
+
+  return completer.future;
+}
+
+Future testNoCustomPort(String host, int port, String sourceAddress) async {
+  Completer completer = new Completer();
+  var server = await ServerSocket.bind(host, port);
+  Socket.connect(host, port, sourceAddress: sourceAddress).then((clientSocket) {
+    server.listen((client) {
+      Expect.equals(server.port, port);
+      Expect.equals(client.remotePort, clientSocket.port);
+      Expect.equals(client.address.address, sourceAddress);
+
+      client.destroy();
+      completer.complete();
+    });
+
+    clientSocket.destroy();
+    server.close();
+  });
+
+  return completer.future;
+}
+
+Future testNoCustomPortNoSourceAddress(
+    String host, int port, String expectedAddress) async {
+  Completer completer = new Completer();
+  var server = await ServerSocket.bind(host, port);
+  Socket.connect(host, port).then((clientSocket) {
+    server.listen((client) {
+      Expect.equals(server.port, port);
+      Expect.equals(client.remotePort, clientSocket.port);
+      Expect.equals(client.address.address, expectedAddress);
+      clientSocket.destroy();
+      client.destroy();
+      server.close();
+      completer.complete();
+    });
+  });
+  return completer.future;
+}
+
+Future main() async {
+  await testCustomPortIPv4();
+  await testCustomPortIPv6();
+
+  await testNoCustomPortIPv4();
+  await testNoCustomPortIPv6();
+
+  await testNoCustomPortNoSourceAddressIPv4();
+  await testNoCustomPortNoSourceAddressIPv6();
+}
diff --git a/tests/standalone_2/io/io_override_test.dart b/tests/standalone_2/io/io_override_test.dart
index 5ac4160..e7d9a81 100644
--- a/tests/standalone_2/io/io_override_test.dart
+++ b/tests/standalone_2/io/io_override_test.dart
@@ -161,12 +161,12 @@
 }
 
 Future<Socket> socketConnect(host, int port,
-    {sourceAddress, Duration timeout}) {
+    {sourceAddress, int sourcePort, Duration timeout}) {
   return null;
 }
 
 Future<ConnectionTask<Socket>> socketStartConnect(host, int port,
-    {sourceAddress}) {
+    {sourceAddress, int sourcePort}) {
   return null;
 }
 
diff --git a/tests/standalone_2/io/socket_local_port_test.dart b/tests/standalone_2/io/socket_local_port_test.dart
new file mode 100644
index 0000000..f7251ec
--- /dev/null
+++ b/tests/standalone_2/io/socket_local_port_test.dart
@@ -0,0 +1,162 @@
+// Copyright (c) 2021, 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.
+
+import 'dart:async';
+import "dart:io";
+
+import "package:expect/expect.dart";
+
+Future testCustomPortIPv4() async {
+  String clientAddress = "127.0.0.1";
+  int customLocalPort = 50988;
+  String serverAddress = clientAddress;
+  int port = 50989;
+
+  testCustomPort(serverAddress, port, clientAddress, customLocalPort);
+}
+
+Future testCustomPortIPv6() async {
+  String clientAddress = "::1";
+  int customLocalPort = 50988;
+  String serverAddress = clientAddress;
+  int port = 50989;
+
+  testCustomPort(serverAddress, port, clientAddress, customLocalPort);
+}
+
+Future testCustomPortIPv4NoSourceAddress() async {
+  String expectedClientAddress = "127.0.0.1";
+  int customLocalPort = 50988;
+  String serverAddress = expectedClientAddress;
+  int port = 50989;
+
+  testCustomPort(serverAddress, port, expectedClientAddress, customLocalPort);
+}
+
+Future testCustomPortIPv6NoSourceAddress() async {
+  String expectedClientAddress = "::1";
+  int customLocalPort = 50988;
+  String serverAddress = expectedClientAddress;
+  int port = 50989;
+
+  testCustomPort(serverAddress, port, expectedClientAddress, customLocalPort);
+}
+
+Future testNoCustomPortIPv4() async {
+  String host = "127.0.0.1";
+  String clientAddress = host;
+  int serverPort = 39998;
+
+  await testNoCustomPortNoSourceAddress(host, serverPort, clientAddress);
+}
+
+Future testNoCustomPortIPv6() async {
+  String host = "::1";
+  String clientAddress = host;
+  int serverPort = 39998;
+
+  await testNoCustomPortNoSourceAddress(host, serverPort, clientAddress);
+}
+
+Future testNoCustomPortNoSourceAddressIPv4() async {
+  String host = "127.0.0.1";
+  String expectedAddress = host;
+  int serverPort = 39998;
+
+  await testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress);
+}
+
+Future testNoCustomPortNoSourceAddressIPv6() async {
+  String host = "::1";
+  String expectedAddress = host;
+  int serverPort = 39998;
+
+  await testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress);
+}
+
+// Core functionality
+void testCustomPort(
+    String host, int port, String sourceAddress, int sourcePort) async {
+  var server = await ServerSocket.bind(host, port);
+  server.listen((client) {
+    Expect.equals(server.port, port);
+    Expect.equals(client.remotePort, sourcePort);
+    Expect.equals(client.address.address, sourceAddress);
+    client.destroy();
+  });
+
+  Socket s = await Socket.connect(host, port,
+      sourceAddress: sourceAddress, sourcePort: sourcePort);
+  s.destroy();
+  server.close();
+}
+
+Future testCustomPortNoSourceAddress(
+    String host, int port, String expectedAddress, int sourcePort) async {
+  Completer completer = new Completer();
+  var server = await ServerSocket.bind(host, port);
+
+  server.listen((client) {
+    Expect.equals(server.port, port);
+    Expect.equals(client.remotePort, sourcePort);
+    Expect.equals(client.address.address, expectedAddress);
+    client.destroy();
+    completer.complete();
+  });
+
+  Socket s = await Socket.connect(host, port, sourcePort: sourcePort);
+  s.destroy();
+  server.close();
+
+  return completer.future;
+}
+
+Future testNoCustomPort(String host, int port, String sourceAddress) async {
+  Completer completer = new Completer();
+  var server = await ServerSocket.bind(host, port);
+  Socket.connect(host, port, sourceAddress: sourceAddress).then((clientSocket) {
+    server.listen((client) {
+      Expect.equals(server.port, port);
+      Expect.equals(client.remotePort, clientSocket.port);
+      Expect.equals(client.address.address, sourceAddress);
+
+      client.destroy();
+      completer.complete();
+    });
+
+    clientSocket.destroy();
+    server.close();
+  });
+
+  return completer.future;
+}
+
+Future testNoCustomPortNoSourceAddress(
+    String host, int port, String expectedAddress) async {
+  Completer completer = new Completer();
+  var server = await ServerSocket.bind(host, port);
+  Socket.connect(host, port).then((clientSocket) {
+    server.listen((client) {
+      Expect.equals(server.port, port);
+      Expect.equals(client.remotePort, clientSocket.port);
+      Expect.equals(client.address.address, expectedAddress);
+      clientSocket.destroy();
+      client.destroy();
+      server.close();
+      completer.complete();
+    });
+  });
+  return completer.future;
+}
+
+Future main() async {
+  await testCustomPortIPv4();
+  await testCustomPortIPv6();
+
+  await testNoCustomPortIPv4();
+  await testNoCustomPortIPv6();
+
+  await testNoCustomPortNoSourceAddressIPv4();
+  await testNoCustomPortNoSourceAddressIPv6();
+}
diff --git a/tools/VERSION b/tools/VERSION
index 28b90d6..dee2d26 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 91
+PRERELEASE 92
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index c060e70..7a644b3 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -544,6 +544,11 @@
     },
     "dart2js-(linux|mac|win)-chrome": {
       "options": {
+        "use-sdk": true
+      }
+    },
+    "dart2js-canary-(linux|mac|win)-chrome": {
+      "options": {
         "use-sdk": true,
         "dart2js-options": [
           "--canary"
@@ -691,9 +696,9 @@
         ]
       }
     },
-    "dart2js-modern-(linux|mac|win)-d8": {
+    "dart2js-canary-(linux|mac|win)-d8": {
       "options": {
-        "builder-tag": "dart2js_modern",
+        "builder-tag": "dart2js_canary",
         "use-sdk": true,
         "dart2js-options": [
           "--canary"
@@ -2398,6 +2403,62 @@
     },
     {
       "builders": [
+        "dart2js-canary-x64"
+      ],
+      "meta": {
+        "description": "dart2js canary tests."
+      },
+      "steps": [
+        {
+          "name": "build dart",
+          "script": "tools/build.py",
+          "arguments": [
+            "create_sdk",
+            "dart2js_bot"
+          ]
+        },
+        {
+          "name": "dart2js canary d8 tests",
+          "arguments": [
+            "-ndart2js-canary-linux-d8",
+            "language",
+            "corelib",
+            "web"
+          ],
+          "shards": 6,
+          "fileset": "web_platform_hostasserts_nnbd"
+        },
+        {
+          "name": "dart2js canary chrome tests",
+          "arguments": [
+            "-ndart2js-canary-linux-chrome",
+            "web"
+          ],
+          "shards": 3,
+          "fileset": "web_platform_hostasserts_nnbd"
+        },
+        {
+          "name": "dart2js canary lib tests",
+          "arguments": [
+            "-ndart2js-canary-linux-chrome",
+            "lib"
+          ],
+          "shards": 3,
+          "fileset": "web_platform_hostasserts_nnbd"
+        },
+        {
+          "name": "dart2js canary co19 tests",
+          "arguments": [
+            "-ndart2js-canary-linux-chrome",
+            "co19"
+          ],
+          "shards": 6,
+          "fileset": "web_platform_hostasserts_nnbd"
+        }
+      ]
+    },
+    {
+      "builders": [
         "dart2js-unit-linux-x64-release"
       ],
       "meta": {
@@ -2644,13 +2705,6 @@
           ]
         },
         {
-          "name": "dart2js --canary smoke tests",
-          "arguments": [
-            "-ndart2js-modern-linux-d8",
-            "web_2"
-          ]
-        },
-        {
           "name": "dart2js d8 fragment merging tests",
           "arguments": [
             "-ndart2js-minified-hostasserts-weak-max-fragments-linux-x64-d8",