From 408918d6f5e28250cb48f50bd2b444bcc2f7982f Mon Sep 17 00:00:00 2001 From: Kenzie Schmoll Date: Mon, 18 Mar 2024 21:50:09 +0000 Subject: [PATCH 1/7] [dds] Start DTD from DevTools server if it is not already started. Fixes https://github.com/dart-lang/sdk/issues/54937. Tested: pkg/dartdev test for `dart devtools` command, and new `dtd_test.dart` in pkg/dds. Change-Id: I530ba2fe4d5809082378b61c282ba7856974e21e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/354460 Commit-Queue: Kenzie Davisson Reviewed-by: Ben Konyi Reviewed-by: Dan Chevalier --- pkg/dartdev/test/commands/devtools_test.dart | 63 ++++++++++---- pkg/dds/CHANGELOG.md | 1 + pkg/dds/bin/dds.dart | 2 + pkg/dds/lib/dds.dart | 6 ++ pkg/dds/lib/devtools_server.dart | 30 +++++-- pkg/dds/lib/src/dds_impl.dart | 29 ++++++- pkg/dds/lib/src/devtools/dtd.dart | 87 ++++++++++++++++++++ pkg/dds/lib/src/devtools/handler.dart | 17 +++- pkg/dds/test/dap/mocks.dart | 4 + pkg/dds/test/devtools_server/dtd_test.dart | 46 +++++++++++ pkg/dtd_impl/bin/dtd.dart | 11 ++- pkg/dtd_impl/lib/dart_tooling_daemon.dart | 24 ++++-- sdk/lib/_internal/vm/bin/vmservice_io.dart | 6 ++ 13 files changed, 288 insertions(+), 38 deletions(-) create mode 100644 pkg/dds/lib/src/devtools/dtd.dart create mode 100644 pkg/dds/test/devtools_server/dtd_test.dart diff --git a/pkg/dartdev/test/commands/devtools_test.dart b/pkg/dartdev/test/commands/devtools_test.dart index 53de48122591..c04c9414059c 100644 --- a/pkg/dartdev/test/commands/devtools_test.dart +++ b/pkg/dartdev/test/commands/devtools_test.dart @@ -70,27 +70,60 @@ void devtools() { // start the devtools server process = await p.start(['devtools', '--no-launch-browser', '--machine']); process!.stderr.transform(utf8.decoder).listen(print); - final Stream inStream = process!.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()); - final line = await inStream.first; - final json = jsonDecode(line); + String? devToolsHost; + int? devToolsPort; + final devToolsServedCompleter = Completer(); + final dtdServedCompleter = Completer(); - // {"event":"server.started","method":"server.started","params":{ - // "host":"127.0.0.1","port":9100,"pid":93508,"protocolVersion":"1.1.0" - // }} - expect(json['event'], 'server.started'); - expect(json['params'], isNotNull); + late StreamSubscription sub; + sub = process!.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((line) async { + final json = jsonDecode(line); + final eventName = json['event'] as String?; + final params = (json['params'] as Map?)?.cast(); + switch (eventName) { + case 'server.dtdStarted': + // {"event":"server.dtdStarted","params":{ + // "uri":"ws://127.0.0.1:50882/nQf49D0YcbONeKVq" + // }} + expect(params!['uri'], isA()); + dtdServedCompleter.complete(); + case 'server.started': + // {"event":"server.started","method":"server.started","params":{ + // "host":"127.0.0.1","port":9100,"pid":93508,"protocolVersion":"1.1.0" + // }} + expect(params!['host'], isA()); + expect(params['port'], isA()); + devToolsHost = params['host'] as String; + devToolsPort = params['port'] as int; + + // We can cancel the subscription because the 'server.started' event + // is expected after the 'server.dtdStarted' event. + await sub.cancel(); + devToolsServedCompleter.complete(); + default: + } + }); - final host = json['params']['host']; - final port = json['params']['port']; - expect(host, isA()); - expect(port, isA()); + await Future.wait([ + dtdServedCompleter.future, + devToolsServedCompleter.future, + ]).timeout( + const Duration(seconds: 5), + onTimeout: () => throw Exception( + 'Expected DTD and DevTools to be served, but one or both were not.', + ), + ); // Connect to the port and confirm we can load a devtools resource. HttpClient client = HttpClient(); - final httpRequest = await client.get(host, port, 'index.html'); + expect(devToolsHost, isNotNull); + expect(devToolsPort, isNotNull); + final httpRequest = + await client.get(devToolsHost!, devToolsPort!, 'index.html'); final httpResponse = await httpRequest.close(); final contents = diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md index 99d178f34cd1..27179b42a361 100644 --- a/pkg/dds/CHANGELOG.md +++ b/pkg/dds/CHANGELOG.md @@ -1,6 +1,7 @@ # 3.3.1 - [DAP] Fixed an issue introduced in 3.3.0 where `Source.name` could contain a file paths when a `package:` or `dart:` URI should have been used. - Updated `package:devtools_shared` version to ^8.0.1. +- Start the Dart Tooling Daemon from the DevTools server when a connection is not passed to the server on start. # 3.3.0 - **Breaking change:** [DAP] Several signatures in DAP debug adapter classes have been updated to use `Uri`s where they previously used `String path`s. This is to support communicating with the DAP client using URIs instead of file paths. URIs may be used only when the client sets the custom `supportsDartUris` client capability during initialization. diff --git a/pkg/dds/bin/dds.dart b/pkg/dds/bin/dds.dart index acb009f9066b..230cfe23dc4a 100644 --- a/pkg/dds/bin/dds.dart +++ b/pkg/dds/bin/dds.dart @@ -148,6 +148,8 @@ ${argParser.usage} 'state': 'started', 'ddsUri': dds.uri.toString(), if (dds.devToolsUri != null) 'devToolsUri': dds.devToolsUri.toString(), + if (dds.hostedDartToolingDaemon?.uri != null) + 'dtdUri': dds.hostedDartToolingDaemon!.uri, })); } catch (e, st) { writeErrorResponse(e, st); diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart index 86de77ca509d..cbf2c1bd494a 100644 --- a/pkg/dds/lib/dds.dart +++ b/pkg/dds/lib/dds.dart @@ -150,6 +150,12 @@ abstract class DartDevelopmentService { /// Returns `null` if DevTools is not running. Uri? get devToolsUri; + /// Metadata for the Dart Tooling Daemon instance that is hosted by DevTools. + /// + /// This will be null if DTD was not started by the DevTools server. For + /// example, it may have been started by an IDE. + ({String? uri, String? secret})? get hostedDartToolingDaemon; + /// Set to `true` if this instance of [DartDevelopmentService] is accepting /// requests. bool get isRunning; diff --git a/pkg/dds/lib/devtools_server.dart b/pkg/dds/lib/devtools_server.dart index 5044c317be3e..c18ebebb74a1 100644 --- a/pkg/dds/lib/devtools_server.dart +++ b/pkg/dds/lib/devtools_server.dart @@ -14,6 +14,7 @@ import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf; import 'src/devtools/client.dart'; +import 'src/devtools/dtd.dart'; import 'src/devtools/handler.dart'; import 'src/devtools/machine_mode_command_handler.dart'; import 'src/devtools/memory_profile.dart'; @@ -98,6 +99,12 @@ class DevToolsServer { help: 'Port to serve DevTools on; specify 0 to automatically use any ' 'available port.', ) + ..addOption( + argDtdUri, + valueHelp: 'uri', + help: 'A URI pointing to a dart tooling daemon that devtools should ' + 'interface with.', + ) ..addFlag( argLaunchBrowser, help: @@ -116,12 +123,6 @@ class DevToolsServer { help: 'Start devtools headlessly and write memory profiling samples to the ' 'indicated file.', - ) - ..addOption( - argDtdUri, - valueHelp: 'uri', - help: 'A uri pointing to a dart tooling daemon that devtools should ' - 'interface with.', ); argParser.addSeparator('App size options:'); @@ -273,13 +274,24 @@ class DevToolsServer { clientManager = ClientManager( requestNotificationPermissions: enableNotifications, ); + + String? dtdSecret; + if (dtdUri == null) { + final (:uri, :secret) = await startDtd( + machineMode: machineMode, + // TODO(https://github.com/dart-lang/sdk/issues/55034): pass the value + // of the Dart CLI flag `--print-dtd` here. + printDtdUri: false, + ); + dtdUri = uri; + dtdSecret = secret; + } + handler ??= await defaultHandler( buildDir: customDevToolsPath!, clientManager: clientManager, analytics: DevToolsUtils.initializeAnalytics(), - // TODO(kenz): pass the DTD secret here when DTD is started by DevTools - // server. - dtd: (uri: dtdUri, secret: null), + dtd: (uri: dtdUri, secret: dtdSecret), ); HttpServer? server; diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart index 74c9799f8c12..d15d95c8f9f3 100644 --- a/pkg/dds/lib/src/dds_impl.dart +++ b/pkg/dds/lib/src/dds_impl.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; +import 'package:devtools_shared/devtools_server.dart' show DTDConnectionInfo; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:meta/meta.dart'; import 'package:shelf/shelf.dart'; @@ -24,6 +25,7 @@ import 'client.dart'; import 'client_manager.dart'; import 'constants.dart'; import 'dap_handler.dart'; +import 'devtools/dtd.dart'; import 'devtools/handler.dart'; import 'expression_evaluator.dart'; import 'isolate_manager.dart'; @@ -171,6 +173,20 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService { ); } pipeline = pipeline.addMiddleware(_authCodeMiddleware); + + if (_devToolsConfiguration?.enable ?? false) { + // If we are enabling DevTools in DDS, then we also need to start the Dart + // tooling daemon, since this is usually the responsibility of the + // DevTools server when a DTD uri is not already passed to the DevTools + // server on start. + _hostedDartToolingDaemon = await startDtd( + machineMode: false, + // TODO(https://github.com/dart-lang/sdk/issues/55034): pass the value + // of the Dart CLI flag `--print-dtd` here. + printDtdUri: false, + ); + } + final handler = pipeline.addHandler(_handlers().handler); // Start the DDS server. late String errorMessage; @@ -348,13 +364,17 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService { // If DDS is serving DevTools, install the DevTools handlers and forward // any unhandled HTTP requests to the VM service. - if (_devToolsConfiguration != null && _devToolsConfiguration!.enable) { + if (_devToolsConfiguration?.enable ?? false) { final String buildDir = _devToolsConfiguration!.customBuildDirectoryPath.toFilePath(); return defaultHandler( dds: this, buildDir: buildDir, notFoundHandler: notFoundHandler, + dtd: ( + uri: _hostedDartToolingDaemon?.uri, + secret: _hostedDartToolingDaemon?.secret + ), ) as FutureOr Function(Request); } @@ -464,6 +484,8 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService { return _devToolsUri; } + Uri? _devToolsUri; + @override void setExternalDevToolsUri(Uri uri) { if (_devToolsConfiguration?.enable ?? false) { @@ -472,7 +494,10 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService { _devToolsUri = uri; } - Uri? _devToolsUri; + @override + DTDConnectionInfo? get hostedDartToolingDaemon => _hostedDartToolingDaemon; + + DTDConnectionInfo? _hostedDartToolingDaemon; final bool _ipv6; diff --git a/pkg/dds/lib/src/devtools/dtd.dart b/pkg/dds/lib/src/devtools/dtd.dart new file mode 100644 index 000000000000..f5c476984f8d --- /dev/null +++ b/pkg/dds/lib/src/devtools/dtd.dart @@ -0,0 +1,87 @@ +// Copyright (c) 2024, 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:convert'; +import 'dart:io'; +import 'dart:isolate'; + +import 'package:devtools_shared/devtools_server.dart' show DTDConnectionInfo; +import 'package:path/path.dart' as path; + +import 'utils.dart'; + +Future startDtd({ + required bool machineMode, + required bool printDtdUri, +}) async { + final sdkPath = File(Platform.resolvedExecutable).parent.parent.path; + String dtdSnapshot = path.absolute( + sdkPath, + 'bin', + 'snapshots', + 'dart_tooling_daemon.dart.snapshot', + ); + + final completer = Completer(); + void completeForError() => completer.complete((uri: null, secret: null)); + + final exitPort = ReceivePort() + ..listen((_) { + completeForError(); + }); + final errorPort = ReceivePort() + ..listen((_) { + completeForError(); + }); + final receivePort = ReceivePort() + ..listen((message) { + try { + // [message] is a JSON encoded String from package:dtd_impl. + final json = jsonDecode(message) as Map; + if (json + case { + 'tooling_daemon_details': { + 'uri': String uri, + 'trusted_client_secret': String secret, + } + }) { + if (printDtdUri || machineMode) { + DevToolsUtils.printOutput( + 'Serving the Dart Tooling Daemon at $uri', + { + 'event': 'server.dtdStarted', + 'params': {'uri': uri}, + }, + machineMode: machineMode, + ); + } + completer.complete((uri: uri, secret: secret)); + } + } catch (_) { + completeForError(); + } + }); + + try { + await Isolate.spawnUri( + Uri.file(dtdSnapshot), + ['--machine'], + receivePort.sendPort, + onExit: exitPort.sendPort, + onError: errorPort.sendPort, + ); + } catch (_, __) { + completeForError(); + } + + final result = await completer.future.timeout( + const Duration(seconds: 5), + onTimeout: () => (uri: null, secret: null), + ); + receivePort.close(); + errorPort.close(); + exitPort.close(); + return result; +} diff --git a/pkg/dds/lib/src/devtools/handler.dart b/pkg/dds/lib/src/devtools/handler.dart index f70548e2df72..24cf4fd66758 100644 --- a/pkg/dds/lib/src/devtools/handler.dart +++ b/pkg/dds/lib/src/devtools/handler.dart @@ -28,10 +28,17 @@ import 'utils.dart'; /// [buildDir] is the path to the pre-compiled DevTools instance to be served. /// /// [notFoundHandler] is a [Handler] to which requests that could not be handled -/// by the DevTools handler are forwarded (e.g., a proxy to the VM service). +/// by the DevTools handler are forwarded (e.g., a proxy to the VM +/// service). /// /// If [dds] is null, DevTools is not being served by a DDS instance and is /// served by a standalone server (see `package:dds/devtools_server.dart`). +/// +/// If [dtd] or [dtd.uri] is null, the Dart Tooling Daemon is not available for +/// this DevTools server connection. +/// +/// If [dtd.uri] is non-null, but [dtd.secret] is null, then DTD was started by a +/// client that is not the DevTools server (e.g. an IDE). FutureOr defaultHandler({ DartDevelopmentServiceImpl? dds, required String buildDir, @@ -183,7 +190,9 @@ Future _serveStaticFile( try { fileBytes = file.readAsBytesSync(); } catch (e) { - return Response.notFound('could not read file as bytes: ${file.path}'); + return Response.notFound( + 'could not read file as bytes: ${file.path}', + ); } } return Response.ok(fileBytes, headers: headers); @@ -193,7 +202,9 @@ Future _serveStaticFile( try { contents = file.readAsStringSync(); } catch (e) { - return Response.notFound('could not read file as String: ${file.path}'); + return Response.notFound( + 'could not read file as String: ${file.path}', + ); } if (baseHref != null) { diff --git a/pkg/dds/test/dap/mocks.dart b/pkg/dds/test/dap/mocks.dart index 9f22eea8b12c..21e3d97d94cf 100644 --- a/pkg/dds/test/dap/mocks.dart +++ b/pkg/dds/test/dap/mocks.dart @@ -10,6 +10,7 @@ import 'package:dds/dds.dart'; import 'package:dds/src/dap/adapters/dart_cli_adapter.dart'; import 'package:dds/src/dap/adapters/dart_test_adapter.dart'; import 'package:dds/src/dap/isolate_manager.dart'; +import 'package:devtools_shared/devtools_server.dart' show DTDConnectionInfo; import 'package:vm_service/vm_service.dart'; /// A [DartCliDebugAdapter] that captures information about the process that @@ -243,6 +244,9 @@ class MockDartDevelopmentService implements DartDevelopmentService { @override Uri? get devToolsUri => throw UnimplementedError(); + @override + DTDConnectionInfo? get hostedDartToolingDaemon => throw UnimplementedError(); + @override Future get done => throw UnimplementedError(); diff --git a/pkg/dds/test/devtools_server/dtd_test.dart b/pkg/dds/test/devtools_server/dtd_test.dart new file mode 100644 index 000000000000..e097f632b866 --- /dev/null +++ b/pkg/dds/test/devtools_server/dtd_test.dart @@ -0,0 +1,46 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:dds/devtools_server.dart'; +import 'package:test/test.dart'; + +import 'utils/server_driver.dart'; + +void main() { + group('Dart Tooling Daemon connection', () { + test('does not start DTD when a DTD uri is passed as an argument', + () async { + final server = await DevToolsServerDriver.create( + additionalArgs: ['--${DevToolsServer.argDtdUri}=some_uri'], + ); + try { + final dtdStartedEvent = await server.stdout + .firstWhere( + (map) => map!['event'] == 'server.dtdStarted', + orElse: () => null, + ) + .timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); + expect(dtdStartedEvent, isNull); + } finally { + server.kill(); + } + }); + + test('starts DTD when no DTD uri is passed as an argument', () async { + final server = await DevToolsServerDriver.create(); + try { + final dtdStartedEvent = await server.stdout.firstWhere( + (map) => map!['event'] == 'server.dtdStarted', + orElse: () => null, + ); + expect(dtdStartedEvent, isNotNull); + } finally { + server.kill(); + } + }); + }); +} diff --git a/pkg/dtd_impl/bin/dtd.dart b/pkg/dtd_impl/bin/dtd.dart index 4d7cc9392823..206509606d9d 100644 --- a/pkg/dtd_impl/bin/dtd.dart +++ b/pkg/dtd_impl/bin/dtd.dart @@ -2,11 +2,20 @@ // 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:isolate'; + import 'package:dtd_impl/dart_tooling_daemon.dart'; -void main(List args) async { +/// Starts the Dart Tooling Daemon with a list of arguments and a nullable +/// Object [port], which will be cast as a [SendPort?] object. +/// +/// When [port] is non-null, the [DartToolingDaemon.startService] method will +/// send information about the DTD connection back over [port] instead of +/// printing it to stdout. +void main(List args, dynamic port) async { await DartToolingDaemon.startService( args, shouldLogRequests: true, + sendPort: port as SendPort?, ); // TODO(@danchevalier): turn off logging } diff --git a/pkg/dtd_impl/lib/dart_tooling_daemon.dart b/pkg/dtd_impl/lib/dart_tooling_daemon.dart index 27596741044b..a9596c65f832 100644 --- a/pkg/dtd_impl/lib/dart_tooling_daemon.dart +++ b/pkg/dtd_impl/lib/dart_tooling_daemon.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; import 'dart:math'; import 'package:args/args.dart'; @@ -162,11 +163,15 @@ class DartToolingDaemon { /// Set [ipv6] to true to have the service use ipv6 instead of ipv4. /// /// Set [shouldLogRequests] to true to enable logging. + /// + /// When [sendPort] is non-null, information about the DTD connection will be + /// sent over [port] instead of being printed to stdout. static Future startService( List args, { bool ipv6 = false, bool shouldLogRequests = false, int port = 0, + SendPort? sendPort, }) async { final argParser = DartToolingDaemonOptions.createArgParser(); final parsedArgs = argParser.parse(args); @@ -186,14 +191,17 @@ class DartToolingDaemon { ); await dtd._startService(port: port); if (machineMode) { - print( - jsonEncode({ - 'tooling_daemon_details': { - 'uri': dtd.uri.toString(), - ...(!unrestrictedMode ? {'trusted_client_secret': secret} : {}), - }, - }), - ); + final encoded = jsonEncode({ + 'tooling_daemon_details': { + 'uri': dtd.uri.toString(), + ...(!unrestrictedMode ? {'trusted_client_secret': secret} : {}), + }, + }); + if (sendPort == null) { + print(encoded); + } else { + sendPort.send(encoded); + } } else { print( 'The Dart Tooling Daemon is listening on ' diff --git a/sdk/lib/_internal/vm/bin/vmservice_io.dart b/sdk/lib/_internal/vm/bin/vmservice_io.dart index 5f02fedc6417..05cd7b3e58b3 100644 --- a/sdk/lib/_internal/vm/bin/vmservice_io.dart +++ b/sdk/lib/_internal/vm/bin/vmservice_io.dart @@ -145,6 +145,12 @@ class _DebuggingSession { final state = result['state']; if (state == 'started') { if (result.containsKey('devToolsUri')) { + // TODO(https://github.com/dart-lang/sdk/issues/55034): only print + // this if the Dart CLI flag `--print-dtd` is present. + if (result.containsKey('dtdUri') && false) { + final dtdUri = result['dtdUri']; + print('The Dart Tooling Daemon is listening on $dtdUri'); + } // NOTE: update pkg/dartdev/lib/src/commands/run.dart if this message // is changed to ensure consistency. const devToolsMessagePrefix = From 4ff34de97af1ee31d84579725cae49a263f56787 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Mon, 18 Mar 2024 22:41:24 +0000 Subject: [PATCH 2/7] Augment. Support for 'augment' on top level variables. Change-Id: I06504c3d3b7b0a4e3d5e5e7d9587eb266f4496de Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358320 Commit-Queue: Konstantin Shcheglov Reviewed-by: Brian Wilkerson --- .../lib/src/dart/analysis/driver.dart | 2 +- .../lib/src/summary2/bundle_reader.dart | 7 ++ .../lib/src/summary2/bundle_writer.dart | 6 ++ .../lib/src/summary2/element_builder.dart | 1 + .../test/src/summary/elements_test.dart | 87 +++++++++++++++++++ 5 files changed, 102 insertions(+), 1 deletion(-) diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart index 6416552182af..1bbf854aed92 100644 --- a/pkg/analyzer/lib/src/dart/analysis/driver.dart +++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart @@ -95,7 +95,7 @@ import 'package:meta/meta.dart'; // TODO(scheglov): Clean up the list of implicitly analyzed files. class AnalysisDriver { /// The version of data format, should be incremented on every format change. - static const int DATA_VERSION = 351; + static const int DATA_VERSION = 353; /// The number of exception contexts allowed to write. Once this field is /// zero, we stop writing any new exception contexts in this process. diff --git a/pkg/analyzer/lib/src/summary2/bundle_reader.dart b/pkg/analyzer/lib/src/summary2/bundle_reader.dart index acc9f6e7a76d..bfbea90632bd 100644 --- a/pkg/analyzer/lib/src/summary2/bundle_reader.dart +++ b/pkg/analyzer/lib/src/summary2/bundle_reader.dart @@ -2566,6 +2566,13 @@ class TopLevelVariableElementLinkedData ); element.macroDiagnostics = reader.readMacroDiagnostics(); element.type = reader.readRequiredType(); + + final augmentationTarget = reader.readElement(); + if (augmentationTarget is TopLevelVariableElementImpl) { + augmentationTarget.augmentation = element; + element.augmentationTarget = augmentationTarget; + } + if (element is ConstTopLevelVariableElementImpl) { var initializer = reader._readOptionalExpression(); if (initializer != null) { diff --git a/pkg/analyzer/lib/src/summary2/bundle_writer.dart b/pkg/analyzer/lib/src/summary2/bundle_writer.dart index b8313c5af473..f3a50932c0f2 100644 --- a/pkg/analyzer/lib/src/summary2/bundle_writer.dart +++ b/pkg/analyzer/lib/src/summary2/bundle_writer.dart @@ -636,6 +636,12 @@ class BundleWriter { _resolutionSink._writeAnnotationList(element.metadata); _resolutionSink.writeMacroDiagnostics(element.macroDiagnostics); _resolutionSink.writeType(element.type); + + _resolutionSink.writeElement(element.augmentationTarget); + if (element.isAugmentation) { + _propertyAugmentations.add(element); + } + _resolutionSink._writeOptionalNode(element.constantInitializer); } diff --git a/pkg/analyzer/lib/src/summary2/element_builder.dart b/pkg/analyzer/lib/src/summary2/element_builder.dart index 5aa41a72a18c..25c16134046c 100644 --- a/pkg/analyzer/lib/src/summary2/element_builder.dart +++ b/pkg/analyzer/lib/src/summary2/element_builder.dart @@ -1191,6 +1191,7 @@ class ElementBuilder extends ThrowingAstVisitor { } element.hasInitializer = variable.initializer != null; + element.isAugmentation = node.augmentKeyword != null; element.isConst = node.variables.isConst; element.isExternal = node.externalKeyword != null; element.isFinal = node.variables.isFinal; diff --git a/pkg/analyzer/test/src/summary/elements_test.dart b/pkg/analyzer/test/src/summary/elements_test.dart index 6296f57724e8..75c4d1229cc6 100644 --- a/pkg/analyzer/test/src/summary/elements_test.dart +++ b/pkg/analyzer/test/src/summary/elements_test.dart @@ -28,6 +28,8 @@ main() { defineReflectiveTests(FunctionAugmentationFromBytesTest); defineReflectiveTests(MixinAugmentationKeepLinkingTest); defineReflectiveTests(MixinAugmentationFromBytesTest); + defineReflectiveTests(TopLevelVariableAugmentationKeepLinkingTest); + defineReflectiveTests(TopLevelVariableAugmentationFromBytesTest); defineReflectiveTests(UpdateNodeTextExpectations); }); } @@ -52405,6 +52407,91 @@ library } } +@reflectiveTest +class TopLevelVariableAugmentationFromBytesTest extends ElementsBaseTest + with TopLevelVariableAugmentationMixin { + @override + bool get keepLinkingLibraries => false; +} + +@reflectiveTest +class TopLevelVariableAugmentationKeepLinkingTest extends ElementsBaseTest + with TopLevelVariableAugmentationMixin { + @override + bool get keepLinkingLibraries => true; +} + +mixin TopLevelVariableAugmentationMixin on ElementsBaseTest { + test_function_augments_function() async { + newFile('$testPackageLibPath/a.dart', r''' +library augment 'test.dart'; +augment int foo = 1; +'''); + + var library = await buildLibrary(r''' +import augment 'a.dart'; +int foo = 0; +'''); + + configuration + ..withExportScope = true + ..withPropertyLinking = true; + checkElementText(library, r''' +library + definingUnit + topLevelVariables + static foo @29 + type: int + shouldUseTypeForInitializerInference: true + id: variable_0 + getter: getter_0 + setter: setter_0 + augmentation: self::@augmentation::package:test/a.dart::@variableAugmentation::foo + accessors + synthetic static get foo @-1 + returnType: int + id: getter_0 + variable: variable_0 + synthetic static set foo= @-1 + parameters + requiredPositional _foo @-1 + type: int + returnType: void + id: setter_0 + variable: variable_0 + augmentationImports + package:test/a.dart + definingUnit + topLevelVariables + augment static foo @41 + type: int + shouldUseTypeForInitializerInference: true + id: variable_1 + getter: getter_1 + setter: setter_1 + augmentationTarget: self::@variable::foo + accessors + synthetic static get foo @-1 + returnType: int + id: getter_1 + variable: variable_1 + synthetic static set foo= @-1 + parameters + requiredPositional _foo @-1 + type: int + returnType: void + id: setter_1 + variable: variable_1 + exportedReferences + declared self::@augmentation::package:test/a.dart::@getter::foo + declared self::@augmentation::package:test/a.dart::@setter::foo + exportNamespace + foo: self::@augmentation::package:test/a.dart::@getter::foo + foo=: self::@augmentation::package:test/a.dart::@setter::foo +'''); + } +} + extension on ElementTextConfiguration { void forPromotableFields({ Set classNames = const {}, From 22873be577ea074140ff2ad487ce45596b3d1b92 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Mon, 18 Mar 2024 22:56:10 +0000 Subject: [PATCH 3/7] Completion. Benchmark for continuously asking completions. Useful to focus on measuring performance of computing suggestions only, without any overhead of resolution, protocol conversion, etc. To be run as is, or with `--observe:xxxx` to see what to optimize. You need to supply your own Flutter checkout. Change-Id: Ie143b4ec9c24e05a0de2a14c1b8f0e1c20ef3a8a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358222 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../code_completion/benchmark/flutter.dart | 114 ++++++++++++++++++ .../benchmark/sliding_statistics.dart | 65 ++++++++++ 2 files changed, 179 insertions(+) create mode 100644 pkg/analysis_server/tool/code_completion/benchmark/flutter.dart create mode 100644 pkg/analysis_server/tool/code_completion/benchmark/sliding_statistics.dart diff --git a/pkg/analysis_server/tool/code_completion/benchmark/flutter.dart b/pkg/analysis_server/tool/code_completion/benchmark/flutter.dart new file mode 100644 index 000000000000..56c32c61224b --- /dev/null +++ b/pkg/analysis_server/tool/code_completion/benchmark/flutter.dart @@ -0,0 +1,114 @@ +// Copyright (c) 2024, 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 'package:analysis_server/src/services/completion/dart/completion_manager.dart'; +import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart'; +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/file_system/overlay_file_system.dart'; +import 'package:analyzer/file_system/physical_file_system.dart'; +import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart'; +import 'package:analyzer/src/util/performance/operation_performance.dart'; + +import 'sliding_statistics.dart'; + +/// A tool to see the current performance of code completion at an interesting +/// location. To see details, run it with `--observe:5000` and then use +/// DevTools CPU profiler. +/// +/// Currently this is not a tool to track performance, it does not record it +/// anywhere. +/// +/// Update the marked code as necessary to construct a case for checking +/// performance. The code below is just one example that we saw to have +/// significant cost. +/// +/// Don't forget to update [flutterPackagePath]. +Future main() async { + await _runForever( + path: '$flutterPackagePath/lib/test.dart', + markedCode: r''' +import 'package:flutter/widgets.dart'; +Widget x = ^; +''', + ); +} + +const String flutterEnvironmentPath = + '/Users/scheglov/dart/flutter_elements/environment'; + +/// This should be the path of the `package:flutter` in a local checkout. +/// You don't have to use [flutterEnvironmentPath] from above. +const String flutterPackagePath = '$flutterEnvironmentPath/packages/flutter'; + +Future _runForever({ + required String path, + required String markedCode, +}) async { + final offset = markedCode.indexOf('^'); + if (offset == -1) { + throw ArgumentError('No ^ marker'); + } + + final rawCode = + markedCode.substring(0, offset) + markedCode.substring(offset + 1); + if (rawCode.contains('^')) { + throw ArgumentError('Duplicate ^ marker'); + } + + final resourceProvider = OverlayResourceProvider( + PhysicalResourceProvider.INSTANCE, + ); + + resourceProvider.setOverlay( + path, + content: rawCode, + modificationStamp: -1, + ); + + var collection = AnalysisContextCollectionImpl( + resourceProvider: resourceProvider, + includedPaths: [path], + sdkPath: '/Users/scheglov/Applications/dart-sdk', + ); + var analysisContext = collection.contextFor(path); + var analysisSession = analysisContext.currentSession; + var unitResult = await analysisSession.getResolvedUnit(path); + unitResult as ResolvedUnitResult; + + var dartRequest = DartCompletionRequest.forResolvedUnit( + resolvedUnit: unitResult, + offset: offset, + ); + + final statistics = SlidingStatistics(100); + while (true) { + var timer = Stopwatch()..start(); + var budget = CompletionBudget(Duration(seconds: 30)); + List suggestions = []; + for (var i = 0; i < 10; i++) { + suggestions = await DartCompletionManager( + budget: budget, + notImportedSuggestions: NotImportedSuggestions(), + ).computeSuggestions( + dartRequest, + OperationPerformanceImpl(''), + useFilter: false, + ); + } + + final responseTime = timer.elapsedMilliseconds; + statistics.add(responseTime); + if (statistics.isReady) { + print( + '[${DateTime.now().millisecondsSinceEpoch}]' + '[time: $responseTime ms][mean: ${statistics.mean.toStringAsFixed(1)}]' + '[stdDev: ${statistics.standardDeviation.toStringAsFixed(3)}]' + '[min: ${statistics.min.toStringAsFixed(1)}]' + '[max: ${statistics.max.toStringAsFixed(1)}]', + ); + } else { + print('[time: $responseTime ms][suggestions: ${suggestions.length}]'); + } + } +} diff --git a/pkg/analysis_server/tool/code_completion/benchmark/sliding_statistics.dart b/pkg/analysis_server/tool/code_completion/benchmark/sliding_statistics.dart new file mode 100644 index 000000000000..131be4f05f68 --- /dev/null +++ b/pkg/analysis_server/tool/code_completion/benchmark/sliding_statistics.dart @@ -0,0 +1,65 @@ +// Copyright (c) 2024, 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:math'; +import 'dart:typed_data'; + +class SlidingStatistics { + final Uint32List _values; + int _index = 0; + bool _isReady = false; + + SlidingStatistics(int length) : _values = Uint32List(length); + + bool get isReady => _isReady; + + int get max { + var result = 0; + for (final value in _values) { + if (value > result) { + result = value; + } + } + return result; + } + + double get mean { + assert(isReady); + var sum = 0.0; + for (final value in _values) { + sum += value; + } + return sum / _values.length; + } + + int get min { + var result = 1 << 20; + for (final value in _values) { + if (value < result) { + result = value; + } + } + return result; + } + + double get standardDeviation { + assert(isReady); + final mean = this.mean; + var sum = 0.0; + for (final value in _values) { + final diff = value - mean; + sum += diff * diff; + } + return sqrt(sum / _values.length); + } + + void add(int value) { + _values[_index] = value; + _index++; + if (_index == _values.length) { + _isReady = true; + } + _index %= _values.length; + } +} From e0062963377515498339edeb9919100ffff0061b Mon Sep 17 00:00:00 2001 From: pq Date: Tue, 19 Mar 2024 00:19:58 +0000 Subject: [PATCH 4/7] more tests for nested excludes and "inherited" lints See: https://github.com/dart-lang/sdk/issues/54858 Change-Id: Ied3f8eb61c2a846dee3108d61690c4308b02a96f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/356380 Commit-Queue: Phil Quitslund Reviewed-by: Brian Wilkerson --- .../resource_provider_mixin.dart | 18 ++ .../analysis_context_collection_test.dart | 168 ++++++++++++++++-- 2 files changed, 169 insertions(+), 17 deletions(-) diff --git a/pkg/analyzer/lib/src/test_utilities/resource_provider_mixin.dart b/pkg/analyzer/lib/src/test_utilities/resource_provider_mixin.dart index 657b911949a8..5034b90fe363 100644 --- a/pkg/analyzer/lib/src/test_utilities/resource_provider_mixin.dart +++ b/pkg/analyzer/lib/src/test_utilities/resource_provider_mixin.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/file_system/memory_file_system.dart'; +import 'package:analyzer/src/test_utilities/package_config_file_builder.dart'; import 'package:analyzer/src/util/file_paths.dart' as file_paths; import 'package:path/path.dart' as path; import 'package:path/path.dart'; @@ -146,11 +147,28 @@ mixin ResourceProviderMixin { return newFile(path, content); } + File newPackageConfigJsonFileFromBuilder( + String directoryPath, + PackageConfigFileBuilder builder, + ) { + final content = builder.toContent(toUriStr: toUriStr); + return newPackageConfigJsonFile(directoryPath, content); + } + File newPubspecYamlFile(String directoryPath, String content) { String path = join(directoryPath, file_paths.pubspecYaml); return newFile(path, content); } + void newSinglePackageConfigJsonFile({ + required String packagePath, + required String name, + }) { + final builder = PackageConfigFileBuilder() + ..add(name: name, rootPath: packagePath); + newPackageConfigJsonFileFromBuilder(packagePath, builder); + } + Uri toUri(String path) { path = convertPath(path); return resourceProvider.pathContext.toUri(path); diff --git a/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart b/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart index 520feec6675c..2ebdbd442cec 100644 --- a/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart +++ b/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart @@ -242,23 +242,6 @@ class AnalysisContextCollectionTest with ResourceProviderMixin { Folder get sdkRoot => newFolder('/sdk'); - File newPackageConfigJsonFileFromBuilder( - String directoryPath, - PackageConfigFileBuilder builder, - ) { - final content = builder.toContent(toUriStr: toUriStr); - return newPackageConfigJsonFile(directoryPath, content); - } - - void newSinglePackageConfigJsonFile({ - required String packagePath, - required String name, - }) { - final builder = PackageConfigFileBuilder() - ..add(name: name, rootPath: packagePath); - newPackageConfigJsonFileFromBuilder(packagePath, builder); - } - void setUp() { ContextLocatorImpl.singleOptionContexts = enableSingleOptionContexts; createMockSdk( @@ -537,6 +520,72 @@ workspaces '''); } + test_packageConfigWorkspace_multipleAnalysisOptions_nestedNestedExclude() async { + final workspaceRootPath = '/home'; + final testPackageRootPath = '$workspaceRootPath/test'; + final testPackageLibPath = '$testPackageRootPath/lib'; + + newPubspecYamlFile(testPackageRootPath, r''' +name: test +'''); + + newSinglePackageConfigJsonFile( + packagePath: testPackageRootPath, + name: 'test', + ); + + newAnalysisOptionsYamlFile(testPackageRootPath, ''); + newFile('$testPackageLibPath/a.dart', ''); + + final nestedPath = '$testPackageLibPath/nested'; + newAnalysisOptionsYamlFile(nestedPath, r''' +analyzer: + exclude: + - excluded/** +'''); + newFile('$nestedPath/b.dart', ''); + newFile('$nestedPath/excluded/b.dart', ''); + + final nestedNestedPath = '$nestedPath/nested'; + newAnalysisOptionsYamlFile(nestedNestedPath, r''' +analyzer: + exclude: + - excluded2/** +'''); + newFile('$nestedNestedPath/c.dart', ''); + newFile('$nestedNestedPath/excluded2/d.dart', ''); + + _assertWorkspaceCollectionText(workspaceRootPath, r''' +contexts + /home/test + packagesFile: /home/test/.dart_tool/package_config.json + workspace: workspace_0 + analyzedFiles + /home/test/lib/a.dart + uri: package:test/a.dart + analysisOptions_0 + workspacePackage_0_0 + /home/test/lib/nested/b.dart + uri: package:test/nested/b.dart + analysisOptions_1 + workspacePackage_0_0 + /home/test/lib/nested/nested/c.dart + uri: package:test/nested/nested/c.dart + analysisOptions_2 + workspacePackage_0_0 +analysisOptions + analysisOptions_0: /home/test/analysis_options.yaml + analysisOptions_1: /home/test/lib/nested/analysis_options.yaml + analysisOptions_2: /home/test/lib/nested/nested/analysis_options.yaml +workspaces + workspace_0: PackageConfigWorkspace + root: /home/test + pubPackages + workspacePackage_0_0: PubPackage + root: /home/test +'''); + } + test_packageConfigWorkspace_multipleAnalysisOptions_outerExclude() async { final workspaceRootPath = '/home'; final testPackageRootPath = '$workspaceRootPath/test'; @@ -1108,6 +1157,91 @@ workspaces '''); } + @override + test_packageConfigWorkspace_multipleAnalysisOptions_nestedNestedExclude() async { + final workspaceRootPath = '/home'; + final testPackageRootPath = '$workspaceRootPath/test'; + final testPackageLibPath = '$testPackageRootPath/lib'; + + newPubspecYamlFile(testPackageRootPath, r''' +name: test +'''); + + newSinglePackageConfigJsonFile( + packagePath: testPackageRootPath, + name: 'test', + ); + + newAnalysisOptionsYamlFile(testPackageRootPath, ''); + newFile('$testPackageLibPath/a.dart', ''); + + final nestedPath = '$testPackageLibPath/nested'; + newAnalysisOptionsYamlFile(nestedPath, r''' +analyzer: + exclude: + - excluded/** +'''); + newFile('$nestedPath/b.dart', ''); + newFile('$nestedPath/excluded/b.dart', ''); + + final nestedNestedPath = '$nestedPath/nested'; + newAnalysisOptionsYamlFile(nestedNestedPath, r''' +analyzer: + exclude: + - excluded/** +'''); + newFile('$nestedNestedPath/c.dart', ''); + newFile('$nestedNestedPath/excluded/d.dart', ''); + + _assertWorkspaceCollectionText(workspaceRootPath, r''' +contexts + /home/test + packagesFile: /home/test/.dart_tool/package_config.json + workspace: workspace_0 + analyzedFiles + /home/test/lib/a.dart + uri: package:test/a.dart + analysisOptions_0 + workspacePackage_0_0 + /home/test/lib/nested + packagesFile: /home/test/.dart_tool/package_config.json + workspace: workspace_1 + analyzedFiles + /home/test/lib/nested/b.dart + uri: package:test/nested/b.dart + analysisOptions_1 + workspacePackage_1_0 + /home/test/lib/nested/nested + packagesFile: /home/test/.dart_tool/package_config.json + workspace: workspace_2 + analyzedFiles + /home/test/lib/nested/nested/c.dart + uri: package:test/nested/nested/c.dart + analysisOptions_2 + workspacePackage_2_0 +analysisOptions + analysisOptions_0: /home/test/analysis_options.yaml + analysisOptions_1: /home/test/lib/nested/analysis_options.yaml + analysisOptions_2: /home/test/lib/nested/nested/analysis_options.yaml +workspaces + workspace_0: PackageConfigWorkspace + root: /home/test + pubPackages + workspacePackage_0_0: PubPackage + root: /home/test + workspace_1: PackageConfigWorkspace + root: /home/test + pubPackages + workspacePackage_1_0: PubPackage + root: /home/test + workspace_2: PackageConfigWorkspace + root: /home/test + pubPackages + workspacePackage_2_0: PubPackage + root: /home/test +'''); + } + @override test_packageConfigWorkspace_multipleAnalysisOptions_outerExclude() async { final workspaceRootPath = '/home'; From ef07a172107771ca499ae213f1fbbeeba6d4c6f1 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Tue, 19 Mar 2024 00:50:38 +0000 Subject: [PATCH 5/7] Augment. Update ElementDisplayStringBuilder. Change-Id: I50c38ea97712f72af4aeb681ba5394e2f0a3bc5f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358322 Reviewed-by: Phil Quitslund Commit-Queue: Konstantin Shcheglov --- .../dart/element/display_string_builder.dart | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/analyzer/lib/src/dart/element/display_string_builder.dart b/pkg/analyzer/lib/src/dart/element/display_string_builder.dart index 19c95eafb5f4..8bd9debdfab6 100644 --- a/pkg/analyzer/lib/src/dart/element/display_string_builder.dart +++ b/pkg/analyzer/lib/src/dart/element/display_string_builder.dart @@ -71,6 +71,10 @@ class ElementDisplayStringBuilder { } void writeConstructorElement(ConstructorElement element) { + if (element.isAugmentation) { + _write('augment '); + } + _writeType(element.returnType); _write(' '); @@ -88,6 +92,10 @@ class ElementDisplayStringBuilder { } void writeEnumElement(EnumElement element) { + if (element.isAugmentation) { + _write('augment '); + } + _write('enum '); _write(element.displayName); _writeTypeParameters(element.typeParameters); @@ -95,18 +103,6 @@ class ElementDisplayStringBuilder { _writeTypesIfNotEmpty(' implements ', element.interfaces); } - // void writePropertyAccessor(PropertyAccessorElement element) { - // var variable = element.variable2; - // if (variable == null) { - // // builder.; - // } - // - // writeExecutableElement( - // element, - // (element.isGetter ? 'get ' : 'set ') + variable2.displayName, - // ); - // } - void writeExecutableElement(ExecutableElement element, String name) { if (element.isAugmentation) { _write('augment '); @@ -135,6 +131,10 @@ class ElementDisplayStringBuilder { } void writeExtensionElement(ExtensionElement element) { + if (element.isAugmentation) { + _write('augment '); + } + _write('extension'); if (element.displayName.isNotEmpty) { _write(' '); From 44c2e17600458efbbff947372370d23ae9c3a73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 19 Mar 2024 08:20:59 +0000 Subject: [PATCH 6/7] [dart2wasm] Update built-in string imports according to the latest spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec: https://github.com/WebAssembly/js-string-builtins Change-Id: Idb387e6dd27b203ddca52291c44fb2aa15ee75cf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/357560 Commit-Queue: Ömer Ağacan Reviewed-by: Martin Kustermann --- pkg/dart2wasm/bin/run_wasm.js | 36 +++++++++++----- pkg/dart2wasm/lib/js/runtime_blob.dart | 32 +++++++-------- sdk/lib/_internal/wasm/lib/js_string.dart | 50 ++++++++++++++++------- 3 files changed, 76 insertions(+), 42 deletions(-) diff --git a/pkg/dart2wasm/bin/run_wasm.js b/pkg/dart2wasm/bin/run_wasm.js index e51890bb89d3..6915ff1b5391 100644 --- a/pkg/dart2wasm/bin/run_wasm.js +++ b/pkg/dart2wasm/bin/run_wasm.js @@ -363,8 +363,20 @@ const main = async () => { } const dart2wasm = await import(args[jsRuntimeArg]); - function compile(filename) { - // Create a Wasm module from the binary wasm file. + + /// Returns whether the `js-string` built-in is supported. + function detectImportedStrings() { + let bytes = [ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, + 0, 2, 23, 1, 14, 119, 97, 115, 109, 58, 106, 115, 45, + 115, 116, 114, 105, 110, 103, 4, 99, 97, 115, 116, 0, 0 + ]; + return !WebAssembly.validate( + new Uint8Array(bytes), {builtins: ['js-string']}); + } + + function compile(filename, withJsStringBuiltins) { + // Create a Wasm module from the binary Wasm file. var bytes; if (isJSC) { bytes = readFile(filename, "binary"); @@ -373,11 +385,10 @@ const main = async () => { } else { bytes = readRelativeToScript(filename, "binary"); } - return new WebAssembly.Module(bytes); - } - - function instantiate(filename, imports) { - return new WebAssembly.Instance(compile(filename), imports); + return WebAssembly.compile( + bytes, + withJsStringBuiltins ? {builtins: ['js-string']} : {} + ); } globalThis.window ??= globalThis; @@ -386,14 +397,17 @@ const main = async () => { // Is an FFI module specified? if (args.length > 2) { - // instantiate FFI module - var ffiInstance = instantiate(args[ffiArg], {}); - // Make its exports available as imports under the 'ffi' module name + // Instantiate FFI module. + var ffiInstance = await WebAssembly.instantiate(await compile(args[ffiArg], false), {}); + // Make its exports available as imports under the 'ffi' module name. importObject.ffi = ffiInstance.exports; } // Instantiate the Dart module, importing from the global scope. - var dartInstance = await dart2wasm.instantiate(Promise.resolve(compile(args[wasmArg])), Promise.resolve(importObject)); + var dartInstance = await dart2wasm.instantiate( + compile(args[wasmArg], detectImportedStrings()), + Promise.resolve(importObject), + ); // Call `main`. If tasks are placed into the event loop (by scheduling tasks // explicitly or awaiting Futures), these will automatically keep the script diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart index 721d2cb9372b..7bcd3f433b2f 100644 --- a/pkg/dart2wasm/lib/js/runtime_blob.dart +++ b/pkg/dart2wasm/lib/js/runtime_blob.dart @@ -113,22 +113,6 @@ const jsRuntimeBlobPart3 = r''' return wrapped; } - if (WebAssembly.String === undefined) { - WebAssembly.String = { - "charCodeAt": (s, i) => s.charCodeAt(i), - "compare": (s1, s2) => { - if (s1 < s2) return -1; - if (s1 > s2) return 1; - return 0; - }, - "concat": (s1, s2) => s1 + s2, - "equals": (s1, s2) => s1 === s2, - "fromCharCode": (i) => String.fromCharCode(i), - "length": (s) => s.length, - "substring": (s, a, b) => s.substring(a, b), - }; - } - // Imports const dart2wasm = { '''; @@ -150,9 +134,25 @@ const jsRuntimeBlobPart5 = r''' Array: Array, Reflect: Reflect, }; + + const jsStringPolyfill = { + "charCodeAt": (s, i) => s.charCodeAt(i), + "compare": (s1, s2) => { + if (s1 < s2) return -1; + if (s1 > s2) return 1; + return 0; + }, + "concat": (s1, s2) => s1 + s2, + "equals": (s1, s2) => s1 === s2, + "fromCharCode": (i) => String.fromCharCode(i), + "length": (s) => s.length, + "substring": (s, a, b) => s.substring(a, b), + }; + dartInstance = await WebAssembly.instantiate(await modulePromise, { ...baseImports, ...(await importObjectPromise), + "wasm:js-string": jsStringPolyfill, }); return dartInstance; diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart index af47437df9de..d6dd00e34692 100644 --- a/sdk/lib/_internal/wasm/lib/js_string.dart +++ b/sdk/lib/_internal/wasm/lib/js_string.dart @@ -81,7 +81,8 @@ final class JSStringImpl implements String { @override String operator +(String other) { if (other is JSStringImpl) { - return JSStringImpl(_jsConcat(toExternRef, other.toExternRef)); + return JSStringImpl( + _jsStringConcatImport(toExternRef, other.toExternRef)); } // TODO(joshualitt): Refactor `string_patch.dart` so we can directly @@ -712,32 +713,51 @@ bool _jsIdentical(WasmExternRef? ref1, WasmExternRef? ref2) => js.JS('Object.is', ref1, ref2); @pragma("wasm:prefer-inline") -int _jsCharCodeAt(WasmExternRef? stringRef, int index) => js - .JS( - 'WebAssembly.String.charCodeAt', stringRef, WasmI32.fromInt(index)) - .toIntUnsigned(); - -WasmExternRef _jsConcat(WasmExternRef? s1, WasmExternRef? s2) => - js.JS('WebAssembly.String.concat', s1, s2); +int _jsCharCodeAt(WasmExternRef? stringRef, int index) => + _jsStringCharCodeAtImport(stringRef, WasmI32.fromInt(index)) + .toIntUnsigned(); @pragma("wasm:prefer-inline") WasmExternRef _jsSubstring( WasmExternRef? stringRef, int startIndex, int endIndex) => - js.JS('WebAssembly.String.substring', stringRef, - WasmI32.fromInt(startIndex), WasmI32.fromInt(endIndex)); + _jsStringSubstringImport( + stringRef, WasmI32.fromInt(startIndex), WasmI32.fromInt(endIndex)); @pragma("wasm:prefer-inline") int _jsLength(WasmExternRef? stringRef) => - js.JS('WebAssembly.String.length', stringRef).toIntUnsigned(); + _jsStringLengthImport(stringRef).toIntUnsigned(); @pragma("wasm:prefer-inline") bool _jsEquals(WasmExternRef? s1, WasmExternRef? s2) => - js.JS('WebAssembly.String.equals', s1, s2).toBool(); + _jsStringEqualsImport(s1, s2).toBool(); @pragma("wasm:prefer-inline") int _jsCompare(WasmExternRef? s1, WasmExternRef? s2) => - js.JS('WebAssembly.String.compare', s1, s2).toIntSigned(); + _jsStringCompareImport(s1, s2).toIntSigned(); @pragma("wasm:prefer-inline") -WasmExternRef _jsFromCharCode(int charCode) => js.JS( - 'WebAssembly.String.fromCharCode', WasmI32.fromInt(charCode)); +WasmExternRef _jsFromCharCode(int charCode) => + _jsStringFromCharCodeImport(WasmI32.fromInt(charCode)); + +@pragma("wasm:import", "wasm:js-string.charCodeAt") +external WasmI32 _jsStringCharCodeAtImport(WasmExternRef? s, WasmI32 index); + +@pragma("wasm:import", "wasm:js-string.compare") +external WasmI32 _jsStringCompareImport(WasmExternRef? s1, WasmExternRef? s2); + +@pragma("wasm:import", "wasm:js-string.concat") +external WasmExternRef _jsStringConcatImport( + WasmExternRef? s1, WasmExternRef? s2); + +@pragma("wasm:import", "wasm:js-string.equals") +external WasmI32 _jsStringEqualsImport(WasmExternRef? s1, WasmExternRef? s2); + +@pragma("wasm:import", "wasm:js-string.fromCharCode") +external WasmExternRef _jsStringFromCharCodeImport(WasmI32 c); + +@pragma("wasm:import", "wasm:js-string.length") +external WasmI32 _jsStringLengthImport(WasmExternRef? s); + +@pragma("wasm:import", "wasm:js-string.substring") +external WasmExternRef _jsStringSubstringImport( + WasmExternRef? s, WasmI32 startIndex, WasmI32 endIndex); From 1b5368f3098e2440af7cd9f670fefd5cf6579519 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:07:23 +0000 Subject: [PATCH 7/7] Bump actions/checkout from 4.1.1 to 4.1.2 Closes https://github.com/dart-lang/sdk/pull/55223 GitOrigin-RevId: 426ae2e78599d253e86fe12bdf6e3df3b8c219c8 Change-Id: I19cc8e23cfb2772552ffccc0fa9ac3df28aa33c2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358140 Commit-Queue: Alexander Thomas Reviewed-by: Alexander Thomas --- .github/workflows/scorecards-analysis.yml | 2 +- .github/workflows/third-party-deps-scan.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index cf9f8e0d9b3a..37ff7d16b966 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -23,7 +23,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 with: persist-credentials: false diff --git a/.github/workflows/third-party-deps-scan.yml b/.github/workflows/third-party-deps-scan.yml index bdef218d2626..aaf5c5ff92ea 100644 --- a/.github/workflows/third-party-deps-scan.yml +++ b/.github/workflows/third-party-deps-scan.yml @@ -21,7 +21,7 @@ jobs: contents: read steps: - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 with: persist-credentials: false - name: "setup python"