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",