diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a5b1fd7a820..c36c3a2d5aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,52 @@ +## 3.9.4 + +**Released on:** 2025-09-30 + +#### Pub +- `dart pub get --example` will now resolve example/ folders in the entire + workspace, not only in the root. This fixes: + https://github.com/dart-lang/pub/issues/4674 that made `flutter pub get` + crash if the examples had not been resolved before resolving the workspace. + +## 3.9.3 + +**Released on:** 2025-09-09 + +### Tools + +#### Dart Development Compiler (dartdevc) +- Fixes a pattern that could lead to exponentially slow compile times when static calls are deeply nested within a closure. When present this led to builds timing out or taking several minutes rather than several seconds. + +## 3.9.2 + +**Released on:** 2025-08-27 + +### Tools + +#### Dart Development Compiler (dartdevc) +- Fixes an inadvertent invocation of class static getters during a hot reload + in a web development environment. This led to possible side effects being + triggered early or crashes during the hot reload if the getter throws an + exception. + +## 3.9.1 + +**Released on:** 2025-08-20 + +This is a patch release that: + +- Fixes an issue in DevTools when users click 'Clear' on the Network Screen. + The terminal can be spammed with assertion errors (#442260). +- Fixes miscompilation on ARM32 with large numbers of literals (issue [flutter/flutter#172626]) +- Fixes an issue with tag_pattern dependencies, where the pubspec.lock would + not be stable under repeated `pub get` (issue [dart-lang/pub#4644]) + +[#442260]: https://dart-review.googlesource.com/c/sdk/+/442260 +[flutter/flutter#172626]: https://github.com/flutter/flutter/issues/172626 +[dart-lang/pub#4644]: https://github.com/dart-lang/pub/issues/4644 ## 3.9.0 -**Released on:** Unreleased +**Released on:** 2025-08-13 ### Language @@ -81,8 +127,6 @@ constraint][language version] lower bound to 3.9 or greater (`sdk: '^3.9.0'`). Added [cross-compilation][] support for target architectures of `arm` (ARM32) and `riscv64` (RV64GC) when the target OS is Linux. -Note that Linux ARM cross-compilation is -not available on Windows host platforms. [cross-compilation]: https://dart.dev/tools/dart-compile#cross-compilation-exe @@ -122,6 +166,28 @@ not available on Windows host platforms. [Git dependencies]: https://dart.dev/tools/pub/dependencies#git-packages +## 3.8.3 + +**Released on:** 2025-07-31 + +This is a patch release that: + +- Fixes an issue with the DevTools Network screen and Hot Restart (issue [flutter/devtools#9203]) +- Fixes an issue when clearing the DevTools network screen (issue [#61187]) + +[flutter/devtools#9203]: https://github.com/flutter/devtools/issues/9203 +[#61187]: https://github.com/dart-lang/sdk/issues/61187 + +## 3.8.2 + +**Released on:** 2025-07-16 + +This is a patch release that: + +- Fixes an issue with the size of cross-compiled binaries (issue [#61097]) + +[#61097]: https://github.com/dart-lang/sdk/issues/61097 + ## 3.8.1 **Released on:** 2025-05-28 @@ -869,6 +935,7 @@ The AOT snapshot can be used as follows to run DDC: [pub#4445]: https://github.com/dart-lang/pub/issues/4445 [#57084]: https://github.com/dart-lang/sdk/issues/57084 [#56552]: https://github.com/dart-lang/sdk/issues/56552 +>>>>>>> BASE (d73f7bdb4366b447eb9874fe80381b8efd03c718 Generate `GeneralizingAstVisitor`, `TimedAstVisitor`, `Analy) ## 3.6.0 diff --git a/DEPS b/DEPS index 7ffa30e80493..e3eaa8f014d1 100644 --- a/DEPS +++ b/DEPS @@ -136,13 +136,13 @@ vars = { "dartdoc_rev": "882aea9351262d618c955322f4c9aafe9540b848", "ecosystem_rev": "d5233c6dd0767cffa5742e32c4bc7c230c9c4b12", "flute_rev": "e4ea0459a7debae5e9592c85141707b01fac86c9", - "http_rev": "4209e846100cb22d60dea7e48727334b8c55be34", + "http_rev": "7d2d87ebfba86035a9ca6b79160ccc2ac1253c0c", "i18n_rev": "c45e050426bdeaaa120e5ce856abb486863d0476", "leak_tracker_rev": "f5620600a5ce1c44f65ddaa02001e200b096e14c", # rolled manually "material_color_utilities_rev": "799b6ba2f3f1c28c67cc7e0b4f18e0c7d7f3c03e", "native_rev": "723cd56a5edc89699db32bca1b5bf91aa0bcf0cf", # rolled manually while native assets are experimental "protobuf_rev": "bce362dec347ae3cca085bd28db2aa2649f754e5", - "pub_rev": "c3e50919d11896f014cb971e1776d00a0e2d18b3", # rolled manually + "pub_rev": "52f512315bc886de487061091ef82056abf9dab8", # rolled manually "shelf_rev": "082d3ac2d13a98700d8148e8fad8f3e12a6fd0e1", "sync_http_rev": "c07f96f89a7eec7e3daac641fa6c587224fcfbaa", "tar_rev": "5a1ea943e70cdf3fa5e1102cdbb9418bd9b4b81a", diff --git a/pkg/dart2wasm/lib/dry_run.dart b/pkg/dart2wasm/lib/dry_run.dart index 3bdb31621d81..b2512a7024d9 100644 --- a/pkg/dart2wasm/lib/dry_run.dart +++ b/pkg/dart2wasm/lib/dry_run.dart @@ -10,16 +10,17 @@ import 'package:kernel/ast.dart'; import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/core_types.dart'; import 'package:kernel/target/targets.dart' show DiagnosticReporter; -import 'package:kernel/type_environment.dart'; enum _DryRunErrorCode { noDartHtml(0), noDartJs(1), interopChecksError(2), + // ignore: unused_field isTestValueError(3), + // ignore: unused_field isTestTypeError(4), - isTestGenericTypeError(5), - ; + // ignore: unused_field + isTestGenericTypeError(5); const _DryRunErrorCode(this.code); @@ -105,17 +106,11 @@ class DryRunSummarizer { return collector.errors; } - List<_DryRunError> _analyzeComponent() { - final analyzer = _AnalysisVisitor(coreTypes, classHierarchy); - component.accept(analyzer); - return analyzer.errors; - } - bool summarize() { final errors = [ ..._analyzeImports(), ..._interopChecks(), - ..._analyzeComponent(), + // TODO: Add additional checks here. ]; if (errors.isNotEmpty) { @@ -149,85 +144,3 @@ class _CollectingDiagnosticReporter errorSourceUri: libraryUri ?? fileUri, errorLocation: location)); } } - -class _AnalysisVisitor extends RecursiveVisitor { - Library? _enclosingLibrary; - late StaticTypeContext _context; - final TypeEnvironment _typeEnvironment; - final DartType _jsAnyType; - final List<_DryRunError> errors = []; - - _AnalysisVisitor(CoreTypes coreTypes, ClassHierarchy hierarchy) - : _typeEnvironment = TypeEnvironment(coreTypes, hierarchy), - _jsAnyType = ExtensionType( - coreTypes.index.getExtensionType('dart:js_interop', 'JSAny'), - Nullability.nonNullable); - - @override - void visitLibrary(Library node) { - if (node.importUri.scheme == 'dart') return; - _enclosingLibrary = node; - _context = StaticTypeContext.forAnnotations(node, _typeEnvironment); - super.visitLibrary(node); - _enclosingLibrary = null; - } - - @override - void visitProcedure(Procedure node) { - _context = StaticTypeContext(node, _typeEnvironment); - super.visitProcedure(node); - } - - @override - void visitIsExpression(IsExpression node) { - final operandStaticType = node.operand.getStaticType(_context); - if (_typeEnvironment.isSubtypeOf( - operandStaticType.withDeclaredNullability(Nullability.nonNullable), - _jsAnyType)) { - errors.add(_DryRunError( - _DryRunErrorCode.isTestValueError, - 'Should not perform an `is` test on a JS value. Use `isA` with a JS ' - 'value type instead.', - errorSourceUri: _enclosingLibrary?.importUri, - errorLocation: node.location)); - } - if (_typeEnvironment.isSubtypeOf( - node.type.withDeclaredNullability(Nullability.nonNullable), - _jsAnyType)) { - errors.add(_DryRunError( - _DryRunErrorCode.isTestTypeError, - 'Should not perform an `is` test against a JS value type. ' - 'Use `isA` instead.', - errorSourceUri: _enclosingLibrary?.importUri, - errorLocation: node.location)); - } else if (_hasJsTypeArguments(node.type)) { - errors.add(_DryRunError( - _DryRunErrorCode.isTestGenericTypeError, - 'Should not perform an `is` test against a generic DartType with JS ' - 'type arguments.', - errorSourceUri: _enclosingLibrary?.importUri, - errorLocation: node.location)); - } - super.visitIsExpression(node); - } - - bool _hasJsTypeArguments(DartType type) { - // Check InterfaceType and ExtensionType - if (type is TypeDeclarationType) { - final arguments = type.typeArguments; - if (arguments.any((e) => _typeEnvironment.isSubtypeOf( - e.withDeclaredNullability(Nullability.nonNullable), _jsAnyType))) { - return true; - } - return arguments.any(_hasJsTypeArguments); - } else if (type is RecordType) { - final fields = type.positional.followedBy(type.named.map((t) => t.type)); - if (fields.any((e) => _typeEnvironment.isSubtypeOf( - e.withDeclaredNullability(Nullability.nonNullable), _jsAnyType))) { - return true; - } - return fields.any(_hasJsTypeArguments); - } - return false; - } -} diff --git a/pkg/dart2wasm/test/dry_run/testcases/malformed_is.dart b/pkg/dart2wasm/test/dry_run/testcases/malformed_is.dart deleted file mode 100644 index cc48c32f8e79..000000000000 --- a/pkg/dart2wasm/test/dry_run/testcases/malformed_is.dart +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:js_interop'; - -void main() { - JSAny? jsValue; - // DRY_RUN: 3, Should not perform an `is` test on a JS value - if (jsValue is String) { - print(jsValue); - // DRY_RUN: 3, Should not perform an `is` test on a JS value - // DRY_RUN: 4, Should not perform an `is` test against a JS value type - } else if (jsValue is JSArray) { - print(jsValue); - } - - Object? dartValue; - if (dartValue is String) { - print(dartValue); - // DRY_RUN: 4, Should not perform an `is` test against a JS value type - } else if (dartValue is JSArray) { - print(dartValue); - // DRY_RUN: 5, Should not perform an `is` test against a generic DartType with JS type arguments - } else if (dartValue is List) { - print(dartValue); - // DRY_RUN: 5, Should not perform an `is` test against a generic DartType with JS type arguments - } else if (dartValue is (JSString,)) { - print(dartValue); - // DRY_RUN: 5, Should not perform an `is` test against a generic DartType with JS type arguments - } else if (dartValue is List<(JSString,)>) { - print(dartValue); - } -} diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart index ab36839d18d8..cecd0cc4ee05 100644 --- a/pkg/dartdev/lib/src/commands/compile.dart +++ b/pkg/dartdev/lib/src/commands/compile.dart @@ -467,8 +467,7 @@ class CompileNativeCommand extends CompileSubcommandCommand { static const String exeCmdName = 'exe'; static const String aotSnapshotCmdName = 'aot-snapshot'; static final supportedTargetPlatforms = { - // ARM cross-compilation is not supported on Windows currently. - if (!Platform.isWindows) Target.linuxArm, + Target.linuxArm, Target.linuxArm64, Target.linuxRiscv64, Target.linuxX64 diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md index 02accf283d7a..413ca5fcfdf7 100644 --- a/pkg/dds/CHANGELOG.md +++ b/pkg/dds/CHANGELOG.md @@ -1,3 +1,6 @@ +# 5.0.5 +- [DAP] The change in DDS 5.0.4 to individually add/remove breakpoints has been reverted and may be restored in a future version. + # 5.0.4 - [DAP] Breakpoints are now added/removed individually instead of all being cleared and re-added during a `setBreakpoints` request. This improves performance and can avoid breakpoints flickering between unresolved/resolved when adding new breakpoints in the same file. diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart index a27d86592fcf..5909aa16d993 100644 --- a/pkg/dds/lib/src/dap/adapters/dart.dart +++ b/pkg/dds/lib/src/dap/adapters/dart.dart @@ -1577,70 +1577,26 @@ abstract class DartDebugAdapter(); - // Map the provided breakpoints onto either new or existing instances of - // [ClientBreakpoint] that we use to track the clients breakpoints - // internally. - final clientBreakpoints = breakpoints.map((bp) { - return - // First try to match an existing breakpoint so we can avoid deleting - // and re-creating all breakpoints if a new one is added to a file. - isolateManager.findExistingClientBreakpoint(uri, bp) ?? - ClientBreakpoint(bp, completer.future); - }).toList(); - - // Any breakpoints that are not in our new set will need to be removed from - // the VM. - // - // Because multiple client breakpoints may resolve to the same VM breakpoint - // we must exclude any that still remain in one of the kept breakpoints. - final referencedVmBreakpoints = - clientBreakpoints.map((bp) => bp.forThread.values).toSet(); - final breakpointsToRemove = isolateManager.clientBreakpointsByUri[uri] - ?.toSet() - // Remove any we're reusing. - .difference(clientBreakpoints.toSet()) - // Remove any that map to VM breakpoints that are still referenced - // because we'll want to keep them. - .where((clientBreakpoint) => clientBreakpoint.forThread.values - .none(referencedVmBreakpoints.contains)); - - // Store this new set of breakpoints as the current set for this URI. - isolateManager.recordLatestClientBreakpoints(uri, clientBreakpoints); - - // Prepare the response with the existing values before we start updating. - final breakpointResponse = SetBreakpointsResponseBody( + final clientBreakpoints = breakpoints + .map((bp) => ClientBreakpoint(bp, completer.future)) + .toList(); + await isolateManager.setBreakpoints(uri, clientBreakpoints); + + sendResponse(SetBreakpointsResponseBody( breakpoints: clientBreakpoints + // Send breakpoints back as unverified and with our generated IDs so we + // can update them with a 'breakpoint' event when we get the + // 'BreakpointAdded'/'BreakpointResolved' events from the VM. .map((bp) => Breakpoint( id: bp.id, - verified: bp.verified, - line: bp.verified ? bp.resolvedLine : null, - column: bp.verified ? bp.resolvedColumn : null, - message: bp.verified ? null : bp.verifiedMessage, - reason: bp.verified ? null : bp.verifiedReason)) + verified: false, + message: 'Breakpoint has not yet been resolved', + reason: 'pending')) .toList(), - ); - - // Update the breakpoints for all existing threads. - await Future.wait(isolateManager.threads.map((thread) async { - // Remove the deleted breakpoints. - if (breakpointsToRemove != null) { - await Future.wait(breakpointsToRemove.map((clientBreakpoint) => - isolateManager.removeBreakpoint(clientBreakpoint, thread))); - } - - // Add the new breakpoints. - await Future.wait(clientBreakpoints.map((clientBreakpoint) async { - if (!clientBreakpoint.isKnownToVm) { - await isolateManager.addBreakpoint(clientBreakpoint, thread, uri); - } - })); - })); - - sendResponse(breakpointResponse); + )); completer.complete(); } diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart index d135634cc2cb..bfc2f49ab9cc 100644 --- a/pkg/dds/lib/src/dap/isolate_manager.dart +++ b/pkg/dds/lib/src/dap/isolate_manager.dart @@ -63,9 +63,9 @@ class IsolateManager { /// [debugExternalPackageLibraries] in one step. bool debugExternalPackageLibraries = true; - /// Tracks breakpoints last provided by the client (by the client URI) so they - /// can be sent to new isolates that appear after initial breakpoints were sent. - final Map> clientBreakpointsByUri = {}; + /// Tracks breakpoints last provided by the client so they can be sent to new + /// isolates that appear after initial breakpoints were sent. + final Map> _clientBreakpointsByUri = {}; /// Tracks client breakpoints by the ID assigned by the VM so we can look up /// conditions/logpoints when hitting breakpoints. @@ -89,11 +89,22 @@ class IsolateManager { /// breakpoint). /// /// When new breakpoints are added by the client, we must check this map to - /// see it's an already-resolved breakpoint so that we can send resolution + /// see it's al already-resolved breakpoint so that we can send resolution /// info to the client. final Map<_UniqueVmBreakpointId, vm.Event> _breakpointResolvedEventsByVmId = {}; + /// Tracks breakpoints created in the VM so they can be removed when the + /// editor sends new breakpoints (currently the editor just sends a new list + /// and not requests to add/remove). + /// + /// Breakpoints are indexed by their ID so that duplicates are not stored even + /// if multiple client breakpoints resolve to a single VM breakpoint. + /// + /// IsolateId -> Uri -> breakpointId -> VM Breakpoint. + final Map>> + _vmBreakpointsByIsolateIdAndUri = {}; + /// The exception pause mode last provided by the client. /// /// This will be sent to isolates as they are created, and to all existing @@ -496,27 +507,32 @@ class IsolateManager { )); } - // Tracks the latest set of breakpoints sent by the client that should - // be sent to any new isolates that start. - void recordLatestClientBreakpoints( + /// Records breakpoints for [uri]. + /// + /// [breakpoints] represents the new set and entirely replaces anything given + /// before. + Future setBreakpoints( String uri, List breakpoints, - ) { - clientBreakpointsByUri[uri] = breakpoints; + ) async { + // Track the breakpoints to get sent to any new isolates that start. + _clientBreakpointsByUri[uri] = breakpoints; + + // Send the breakpoints to all existing threads. + await Future.wait(_threadsByThreadId.values + .map((thread) => _sendBreakpoints(thread, uri: uri))); } /// Clears all breakpoints. Future clearAllBreakpoints() async { - // Group all breakpoints for all URIs before clearing the list. - final clientBreakpointsToDelete = - clientBreakpointsByUri.values.expand((bps) => bps).toList(); - clientBreakpointsByUri.clear(); - - // Remove all breakpoints for all threads. - await Future.wait(clientBreakpointsToDelete.map((clientBreakpoint) { - return Future.wait(_threadsByThreadId.values - .map((thread) => removeBreakpoint(clientBreakpoint, thread))); - })); + // Clear all breakpoints for each URI. Do not remove the items from the map + // as that will stop them being tracked/sent by the call below. + _clientBreakpointsByUri.updateAll((key, value) => []); + + // Send the breakpoints to all existing threads. + await Future.wait( + _threadsByThreadId.values.map((thread) => _sendBreakpoints(thread)), + ); } /// Records exception pause mode as one of 'None', 'Unhandled' or 'All'. All @@ -576,12 +592,7 @@ class IsolateManager { _sendExceptionPauseMode(thread), ], eagerError: true); - await Future.wait(clientBreakpointsByUri.entries.map((mapEntry) async { - var clientUri = mapEntry.key; - var clientBreakpoints = mapEntry.value; - await Future.wait(clientBreakpoints.map((clientBreakpoint) => - addBreakpoint(clientBreakpoint, thread, clientUri))); - })); + await _sendBreakpoints(thread); } on vm.SentinelException { // It's possible during these async requests that the isolate went away // (for example a shutdown/restart) and we no longer care about @@ -879,19 +890,12 @@ class IsolateManager { // This is always resolved because of the check above. final location = breakpoint.location; final resolvedLocation = location as vm.SourceLocation; - - // Track that this breakpoint has been resolved, so that if we are asked to - // set the same breakpoint in future, we can immediately return - // resolved=true in the response. - clientBreakpoint.resolved(resolvedLocation.line, resolvedLocation.column); - final updatedBreakpoint = Breakpoint( id: clientBreakpoint.id, - line: clientBreakpoint.resolvedLine, - column: clientBreakpoint.resolvedColumn, - verified: clientBreakpoint.verified, + line: resolvedLocation.line, + column: resolvedLocation.column, + verified: true, ); - // Ensure we don't send the breakpoint event until the client has been // given the breakpoint ID by queueing it. clientBreakpoint.queueAction( @@ -928,18 +932,11 @@ class IsolateManager { userMessage = terseMessageMatch.group(1) ?? userMessage; } - // Record the failure state only if we don't already have a success state - // because if we set a breakpoint in some isolates but not others, we do not - // want to show that as unresolved. - if (!clientBreakpoint.verified) { - clientBreakpoint.unresolved(reason: 'failed', message: userMessage); - } - final updatedBreakpoint = Breakpoint( id: clientBreakpoint.id, - verified: clientBreakpoint.verified, - message: clientBreakpoint.verifiedMessage, - reason: clientBreakpoint.verifiedReason, + verified: false, + message: userMessage, + reason: 'failed', ); // Ensure we don't send the breakpoint event until the client has been // given the breakpoint ID by queueing it. @@ -1042,58 +1039,13 @@ class IsolateManager { await service.reloadSources(isolateId); } - /// Tries to find an existing [ClientBreakpoint] matching the location of the - /// provided [breakpoint] so that it can be reused/kept when updating - /// breakpoints. - ClientBreakpoint? findExistingClientBreakpoint( - String clientUri, SourceBreakpoint breakpoint) { - var clientBreakpoints = clientBreakpointsByUri[clientUri]; - if (clientBreakpoints == null) { - return null; - } - - return clientBreakpoints.firstWhereOrNull((clientBreakpoint) => - // These conditions must cover all fields that would be sent to the VM. - // They do not need to include things like `condition` which we check - // DAP-side. - - // We always compare breakpoints based on the original location and not - // the resolved location, because clients will not update the underlying - // breakpoints with resolution, they will only assign a temporary - // overriden location. - // - // See https://github.com/microsoft/vscode/issues/250453. - clientBreakpoint.breakpoint.line == breakpoint.line && - clientBreakpoint.breakpoint.column == breakpoint.column); - } - - /// Converts local Google3 or SDK file paths to URIs VM can recognize. - /// - /// ``` - /// sdk-path/lib/core/print.dart -> org-dartlang-sdk://sdk/lib/core/print.dart - /// google/*/google3/ -> google3:// - /// ``` - /// - /// VM is capable of setting breakpoints using both original (`package` - /// scheme) URIs or their resolved variants - which means no convertion - /// is necessary if local path is the same as the resolved path known to the - /// VM. - /// - /// Google3 paths and Dart SDK paths however require special handling: - /// because in both cases resolved paths we given using *multi-root - /// filesystem* accessed via a special scheme (`google3` and - /// `org-dartlang-sdk` respectively) to hide local file layout from the - /// front-end. - Uri _fixSDKOrGoogle3Paths(Uri sourcePathUri) { - return _adapter.convertUriToOrgDartlangSdk(sourcePathUri) ?? - _convertPathToGoogle3Uri(sourcePathUri) ?? - sourcePathUri; - } - - /// Creates a breakpoint in [clientUri] for [thread] in the VM that - /// corresponds to [clientBreakpoint] received from the client. - Future addBreakpoint(ClientBreakpoint clientBreakpoint, - ThreadInfo thread, String clientUri) async { + /// Sets breakpoints for an individual isolate. + /// + /// If [uri] is provided, only breakpoints for that URI will be sent (used + /// when breakpoints are modified for a single file in the editor). Otherwise + /// breakpoints for all previously set URIs will be sent (used for + /// newly-created isolates). + Future _sendBreakpoints(ThreadInfo thread, {String? uri}) async { final service = _adapter.vmService; if (!debug || service == null) { return; @@ -1101,63 +1053,78 @@ class IsolateManager { final isolateId = thread.isolate.id!; - try { - // Some file URIs (like SDK sources) need to be converted to - // appropriate internal URIs to be able to set breakpoints. - final vmUri = _fixSDKOrGoogle3Paths(Uri.parse(clientUri)); - final vmBp = await service.addBreakpointWithScriptUri( - isolateId, vmUri.toString(), clientBreakpoint.breakpoint.line, - column: clientBreakpoint.breakpoint.column); - clientBreakpoint.forThread[thread] = vmBp; - final uniqueBreakpointId = (isolateId: isolateId, breakpointId: vmBp.id!); - - // Store this client breakpoint by the VM ID, so when we get events - // from the VM we can map them back to client breakpoints (for example - // to send resolved events). - _clientBreakpointsByVmId - .putIfAbsent(uniqueBreakpointId, () => []) - .add(clientBreakpoint); - - // Queue any resolved events that may have already arrived - // (either because the VM sent them before responding to us, or - // because it gave us an existing VM breakpoint because it resolved to - // the same location as another). - final resolvedEvent = _breakpointResolvedEventsByVmId[uniqueBreakpointId]; - if (resolvedEvent != null) { - queueBreakpointResolutionEvent(resolvedEvent, clientBreakpoint); - } - } catch (e) { - // Swallow errors setting breakpoints rather than failing the whole - // request as it's very easy for editors to send us breakpoints that - // aren't valid any more. - _adapter.logger?.call('Failed to add breakpoint $e'); - queueFailedBreakpointEvent(e, clientBreakpoint); - } - } - - /// Removes [clientBreakpoint] from [thread] in the VM. - Future removeBreakpoint( - ClientBreakpoint clientBreakpoint, ThreadInfo thread) async { - final service = _adapter.vmService; - if (!debug || service == null) { - return; - } - - final isolateId = thread.isolate.id!; - final vmBreakpoint = clientBreakpoint.forThread[thread]; - if (vmBreakpoint == null) { - // This isolate didn't have this breakpoint. - return; - } + // If we were passed a single URI, we should send breakpoints only for that + // (this means the request came from the client), otherwise we should send + // all of them (because this is a new/restarting isolate). + final uris = uri != null ? [uri] : _clientBreakpointsByUri.keys.toList(); + + for (final uri in uris) { + // Clear existing breakpoints. + final existingBreakpointsForIsolate = + _vmBreakpointsByIsolateIdAndUri.putIfAbsent(isolateId, () => {}); + final existingBreakpointsForIsolateAndUri = + existingBreakpointsForIsolate.putIfAbsent(uri, () => {}); + // Before doing async work, take a copy of the breakpoints to remove + // and remove them from the list, so any subsequent calls here don't + // try to remove the same ones multiple times. + final breakpointsToRemove = + existingBreakpointsForIsolateAndUri.values.toList(); + existingBreakpointsForIsolateAndUri.clear(); + await Future.forEach(breakpointsToRemove, (bp) async { + try { + await service.removeBreakpoint(isolateId, bp.id!); + } catch (e) { + // Swallow errors removing breakpoints rather than failing the whole + // request as it's very possible that an isolate exited while we were + // sending this and the request will fail. + _adapter.logger?.call('Failed to remove old breakpoint $e'); + } + }); + + // Set new breakpoints. + final newBreakpoints = _clientBreakpointsByUri[uri] ?? const []; + await Future.forEach(newBreakpoints, (bp) async { + try { + // Some file URIs (like SDK sources) need to be converted to + // appropriate internal URIs to be able to set breakpoints. + final vmUri = await thread.resolvePathToUri(Uri.parse(uri)); + + if (vmUri == null) { + return; + } - try { - await service.removeBreakpoint(isolateId, vmBreakpoint.id!); - clientBreakpoint.forThread.remove(thread); - } catch (e) { - // Swallow errors removing breakpoints rather than failing the whole - // request as it's very possible that an isolate exited while we were - // sending this and the request will fail. - _adapter.logger?.call('Failed to remove old breakpoint $e'); + final vmBp = await service.addBreakpointWithScriptUri( + isolateId, vmUri.toString(), bp.breakpoint.line, + column: bp.breakpoint.column); + final vmBpId = vmBp.id!; + final uniqueBreakpointId = + (isolateId: isolateId, breakpointId: vmBp.id!); + existingBreakpointsForIsolateAndUri[vmBpId] = vmBp; + + // Store this client breakpoint by the VM ID, so when we get events + // from the VM we can map them back to client breakpoints (for example + // to send resolved events). + _clientBreakpointsByVmId + .putIfAbsent(uniqueBreakpointId, () => []) + .add(bp); + + // Queue any resolved events that may have already arrived + // (either because the VM sent them before responding to us, or + // because it gave us an existing VM breakpoint because it resolved to + // the same location as another). + final resolvedEvent = + _breakpointResolvedEventsByVmId[uniqueBreakpointId]; + if (resolvedEvent != null) { + queueBreakpointResolutionEvent(resolvedEvent, bp); + } + } catch (e) { + // Swallow errors setting breakpoints rather than failing the whole + // request as it's very easy for editors to send us breakpoints that + // aren't valid any more. + _adapter.logger?.call('Failed to add breakpoint $e'); + queueFailedBreakpointEvent(e, bp); + } + }); } } @@ -1403,32 +1370,49 @@ class ThreadInfo with FileUtils { return _currentEvaluationZoneId; } - /// Resolves a source file URI into a original `package://` or SDK URI. + /// Resolves a source file path (or URI) into a URI for the VM. /// - /// ``` - /// sdk-path/lib/core/print.dart -> org-dartlang-sdk://sdk/lib/core/print.dart + /// sdk-path/lib/core/print.dart -> dart:core/print.dart /// c:\foo\bar -> package:foo/bar - /// ``` - /// - /// This helper is used when trying to find [vm.Script] by matching its - /// `uri`. - Future _convertToPackageOrSdkPath(Uri sourcePathUri) async { - final uri = _manager._fixSDKOrGoogle3Paths(sourcePathUri); - if (uri.isScheme('org-dartlang-sdk')) { - return uri; // No package path exists for SDK sources. + /// dart-macro+file:///c:/foo/bar -> dart-macro+package:foo/bar + /// + /// This is required so that when the user sets a breakpoint in an SDK source + /// (which they may have navigated to via the Analysis Server) we generate a + /// valid URI that the VM would create a breakpoint for. + /// + /// Because the VM supports using `file:` URIs in many places, we usually do + /// not need to convert file paths into `package:` URIs, however this will + /// be done if [forceResolveFileUris] is `true`. + Future resolvePathToUri( + Uri sourcePathUri, { + bool forceResolveFileUris = false, + }) async { + final sdkUri = _manager._adapter.convertUriToOrgDartlangSdk(sourcePathUri); + if (sdkUri != null) { + return sdkUri; } + final google3Uri = _convertPathToGoogle3Uri(sourcePathUri); + final uri = google3Uri ?? sourcePathUri; + + // As an optimisation, we don't resolve file -> package URIs in many cases + // because the VM can set breakpoints for file: URIs anyway. However for + // G3 or if [forceResolveFileUris] is set, we will. + final performResolve = google3Uri != null || forceResolveFileUris; + // TODO(dantup): Consider caching results for this like we do for // resolveUriToPath (and then forceResolveFileUris can be removed and just // always used). - final packageUriList = await _manager._adapter.vmService - ?.lookupPackageUris(isolate.id!, [uri.toString()]); + final packageUriList = performResolve + ? await _manager._adapter.vmService + ?.lookupPackageUris(isolate.id!, [uri.toString()]) + : null; final packageUriString = packageUriList?.uris?.firstOrNull; if (packageUriString != null) { // Use package URI if we resolved something return Uri.parse(packageUriString); - } else if (uri.isScheme('google3')) { + } else if (google3Uri != null) { // If we failed to resolve and was a Google3 URI, return null return null; } else { @@ -1615,6 +1599,28 @@ class ThreadInfo with FileUtils { /// that are round-tripped to the client. int storeData(Object data) => _manager.storeData(this, data); + Uri? _convertPathToGoogle3Uri(Uri input) { + // TODO(dantup): Do we need to handle non-file here? Eg. can we have + // dart-macro+file:/// for a google3 path? + if (!input.isScheme('file')) { + return null; + } + final inputPath = input.toFilePath(); + + const search = '/google3/'; + if (inputPath.startsWith('/google') && inputPath.contains(search)) { + var idx = inputPath.indexOf(search); + var remainingPath = inputPath.substring(idx + search.length); + return Uri( + scheme: 'google3', + host: '', + path: remainingPath, + ); + } + + return null; + } + /// Converts a VM-returned URI to a file-like URI, taking org-dartlang-sdk /// schemes into account. /// @@ -1700,9 +1706,12 @@ class ThreadInfo with FileUtils { Future getLibraryForFileUri(Uri scriptFileUri) async { // We start with a file URI and need to find the Library (via the script). // - // We need to handle mismatched drive letters, and also file vs package + // We need to handle msimatched drive letters, and also file vs package // URIs. - final scriptResolvedUri = await _convertToPackageOrSdkPath(scriptFileUri); + final scriptResolvedUri = await resolvePathToUri( + scriptFileUri, + forceResolveFileUris: true, + ); final candidateUris = { scriptFileUri.toString(), normalizeUri(scriptFileUri).toString(), @@ -1740,45 +1749,6 @@ class ClientBreakpoint { final SourceBreakpoint breakpoint; final int id; - /// Whether this breakpoint has been sent to the VM (and not removed). - bool get isKnownToVm => forThread.isNotEmpty; - - /// A map of [ThreadInfo] -> [vm.Breakpoint] recording the VM breakpoint for - /// each isolate for this client breakpoint. - /// - /// Note: It's possible for multiple [ClientBreakpoint]s to resolve to the - /// same VM breakpoint! - Map forThread = {}; - - /// Whether this breakpoint was previously been verified. - bool get verified => _verified; - - bool _verified = false; - - /// A user-friendly explanation of why this breakpoint could not be resolved - /// (if [verified] is `false`, otherwise `null`). - String? get verifiedMessage => _verifiedMessage; - - String? _verifiedMessage = 'Breakpoint has not yet been resolved'; - - /// A machine-readable reason (specified by DAP) of why this breakpoint is not - /// resolved (if [verified] is `false`, otherwise `null`). - String? get verifiedReason => _verifiedReason; - - String? _verifiedReason = 'pending'; - - /// The line that this breakpoint resolved to, or the request line if not - /// resolved. - int get resolvedLine => _resolvedLine ?? breakpoint.line; - - int? _resolvedLine; - - /// The column that this breakpoint resolved to, or the request column if not - /// resolved. - int? get resolvedColumn => _resolvedColumn ?? breakpoint.column; - - int? _resolvedColumn; - /// A [Future] that completes with the last action that sends breakpoint /// information to the client, to ensure breakpoint events are always sent /// in-order and after the initial response sending the IDs to the client. @@ -1795,22 +1765,6 @@ class ClientBreakpoint { _lastActionFuture = actionFuture; return actionFuture; } - - /// Marks that this breakpoint was resolved. - void resolved(int? line, int? column) { - _verified = true; - _verifiedReason = null; - _verifiedMessage = null; - _resolvedLine = line; - _resolvedColumn = column; - } - - /// Marks that this breakpoint was not resolved and why. - void unresolved({required String reason, required String message}) { - _verified = false; - _verifiedReason = reason; - _verifiedMessage = message; - } } /// Tracks actions resulting from `BreakpointAdded`/`BreakpointResolved` events @@ -1843,25 +1797,3 @@ class StoredData { StoredData(this.thread, this.data); } - -Uri? _convertPathToGoogle3Uri(Uri input) { - // TODO(dantup): Do we need to handle non-file here? Eg. can we have - // dart-macro+file:/// for a google3 path? - if (!input.isScheme('file')) { - return null; - } - final inputPath = input.toFilePath(); - - const search = '/google3/'; - if (inputPath.startsWith('/google') && inputPath.contains(search)) { - var idx = inputPath.indexOf(search); - var remainingPath = inputPath.substring(idx + search.length); - return Uri( - scheme: 'google3', - host: '', - path: remainingPath, - ); - } - - return null; -} diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml index eb5e983b6cae..89c4c0ef0c18 100644 --- a/pkg/dds/pubspec.yaml +++ b/pkg/dds/pubspec.yaml @@ -1,5 +1,5 @@ name: dds -version: 5.0.4 +version: 5.0.5 description: >- A library used to spawn the Dart Developer Service, used to communicate with a Dart VM Service instance. diff --git a/pkg/dds/test/dap/integration/dart_test_test.dart b/pkg/dds/test/dap/integration/dart_test_test.dart index 228d9d66bf6b..401182462a76 100644 --- a/pkg/dds/test/dap/integration/dart_test_test.dart +++ b/pkg/dds/test/dap/integration/dart_test_test.dart @@ -203,7 +203,7 @@ main() { // Add breakpoints to the 4 lines after the current one, one at a time. // Capture the IDs of all breakpoints added. - final breakpointLinesToSend = []; + final breakpointLinesToSend = [breakpointLine]; final addedBreakpoints = {}; for (var i = 1; i <= 4; i++) { breakpointLinesToSend.add(breakpointLine + i); diff --git a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart index 533c0efb6784..365e60d9d5e9 100644 --- a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart +++ b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart @@ -51,7 +51,7 @@ main() { // Add breakpoints to the 4 lines after the current one, one at a time. // Capture the IDs of all breakpoints added. - final breakpointLinesToSend = []; + final breakpointLinesToSend = [breakpointLine]; final addedBreakpoints = {}; for (var i = 1; i <= 4; i++) { breakpointLinesToSend.add(breakpointLine + i); @@ -75,86 +75,6 @@ main() { expect(resolvedBreakpoints, addedBreakpoints); }); - testWithUriConfigurations(() => dap, - 'does not re-resolve existing breakpoints when new ones are added', - () async { - final client = dap.client; - final testFile = dap.createTestFile(simpleMultiBreakpointProgram); - final breakpointLine = lineWith(testFile, breakpointMarker); - - // Start the app and hit the initial breakpoint. - await client.hitBreakpoint(testFile, breakpointLine); - - // Collect any breakpoint events in a simple text format for verifying. - final breakpointEvents = []; - final breakpointResolveSubscription = - client.breakpointChangeEvents.listen((event) { - var breakpoint = event.breakpoint; - var id = breakpoint.id!; - var verified = breakpoint.verified; - var reason = breakpoint.reason; - var description = verified ? 'verified' : 'not verified ($reason)'; - breakpointEvents.add('Breakpoint $id $description'); - }); - - // Test adding breakpoints to the 4 lines after the first breakpoint, one - // at a time. Each request contains the total set of breakpoints (so the - // first request has one breakpoint and the last request has all 4). In - // each response, we expect the previous breakpoints to be - // already-verified and to not get events for them. For the last one - // breakpoint, it will not be verified and we will then get an event. - var breakpointLines = []; - var seenBreakpointIds = {}; - for (var i = 1; i <= 4; i++) { - breakpointEvents.clear(); // Clear any events from previous iterations. - - // Add an additional breakpoint on the next line. - breakpointLines.add(breakpointLine + i); - final response = await client.setBreakpoints(testFile, breakpointLines); - expect(response.breakpoints, hasLength(i)); - - // Wait up to a few seconds for a resolved events to come through. - final testUntil = - DateTime.now().toUtc().add(const Duration(seconds: 5)); - while (DateTime.now().toUtc().isBefore(testUntil) && - breakpointEvents.isEmpty) { - await pumpEventQueue(times: 5000); - } - - // Verify the results for this iteration. - for (var j = 0; j < i; j++) { - // j is zero-based but i is one-based - final breakpoint = response.breakpoints[j]; - final id = breakpoint.id!; - - // All but the last should be verified already and have existing IDs. - if (j == i - 1) { - expect(seenBreakpointIds.contains(id), isFalse, - reason: - 'Last breakpoint (index $j) should have a new unseen ID'); - expect(breakpoint.verified, isFalse, - reason: - 'Last breakpoint (index $j) should not yet be verified'); - seenBreakpointIds.add(id); - } else { - expect(seenBreakpointIds.contains(id), isTrue, - reason: - 'Non-last breakpoint (index $j) should have an already-seen ID because it was reused'); - expect(breakpoint.verified, isTrue, - reason: - 'Non-last breakpoint (index $j) should already be verified'); - } - } - - // We should have had one event for that last one to be verified (others - // were already verified). - expect(breakpointEvents, - ['Breakpoint ${response.breakpoints.last.id} verified']); - } - - await breakpointResolveSubscription.cancel(); - }); - testWithUriConfigurations( () => dap, 'provides reason for failed breakpoints', () async { final client = dap.client; diff --git a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart index 9303f38e8b21..a2521a7f8775 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart @@ -246,6 +246,29 @@ class LibraryBundleCompiler implements old.Compiler { } } +/// Tracks the state of which branch the compiler is on for hot reload checks. +/// +/// This allows hot reload generation checks to be batched into a single +/// branch statement. +/// +/// This batched branching allows us to avoid exponential behavior when +/// recursing on deeply nested checked calls. Otherwise each branch makes 2 +/// copies of all the sub-branches leading to 2^n checks. +enum HotReloadBranchState { + /// The compiler is not along any hot reload check branch yet. + none, + + /// The compiler is along the branch with no extra checks. The check rewrite + /// logic will be skipped and the normal call will be generated. The root of + /// this branch will include a hot reload generation check. + uncheckedBranch, + + /// The compiler is along the branch with extra checks. The check rewrite + /// logic will be applied for every call that needs it. The root of this + /// branch will include a hot reload generation check. + checkedBranch, +} + class LibraryCompiler extends ComputeOnceConstantVisitor with OnceConstantVisitorDefaultMixin implements @@ -254,6 +277,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor final Options _options; final SymbolData _symbolData; + HotReloadBranchState hotReloadCheckedBranch = HotReloadBranchState.none; + /// Maps each `Class` node compiled in the module to the `Identifier`s used to /// name the class in JavaScript. /// @@ -5205,7 +5230,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor // the sub-expressions will have the correct mapping applied. return jsExpression.toStatement()..sourceInformation = continueSourceMap; } - return _visitExpression(expr).toStatement(); + return jsExpression.toStatement(); } @override @@ -6175,10 +6200,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor // Since there are no arguments (unlike methods) the dynamic get path can // be reused for the hot reload checks on a getter. var checkedGet = _emitCast( - _emitDynamicGet( - _visitExpression(receiver), - _emitMemberName(memberName), - ), + _emitDynamicGet(jsReceiver, _emitMemberName(memberName)), node.resultType, )..sourceInformation = _nodeStart(node); return _emitHotReloadSafeInvocation(instanceGet, checkedGet); @@ -7633,21 +7655,41 @@ class LibraryCompiler extends ComputeOnceConstantVisitor } } - var fn = _emitStaticTarget(target); - var args = _emitArgumentList(node.arguments, target: target); - var staticCall = js_ast.Call(fn, args) - ..sourceInformation = _nodeStart(node); - if (_shouldRewriteInvocationWithHotReloadChecks(target)) { - var checkedCall = _rewriteInvocationWithHotReloadChecks( + js_ast.Call generateCall(js_ast.PropertyAccess fn) { + var args = _emitArgumentList(node.arguments, target: target); + return js_ast.Call(fn, args)..sourceInformation = _nodeStart(node); + } + + // Only consider checks if we're not in the unchecked branch. + if (_shouldRewriteInvocationWithHotReloadChecks(target) && + hotReloadCheckedBranch != HotReloadBranchState.uncheckedBranch) { + final fn = _emitStaticTarget(target); + + var checkedInvocation = _rewriteInvocationWithHotReloadChecks( target, node.arguments, node.getStaticType(_staticTypeContext), _nodeStart(node), ); + + // If we're within the checked branch (i.e. not at the root) then return + // the checked call as-is. + if (hotReloadCheckedBranch == HotReloadBranchState.checkedBranch) { + return checkedInvocation; + } + + // We're at the root of the branch so we need to generate the unchecked + // branch as well. + hotReloadCheckedBranch = HotReloadBranchState.uncheckedBranch; + final invocation = generateCall(fn); + hotReloadCheckedBranch = HotReloadBranchState.none; + // As an optimization, avoid extra checks when the invocation code was // compiled in the same generation that it is running. - return _emitHotReloadSafeInvocation(staticCall, checkedCall); + return _emitHotReloadSafeInvocation(invocation, checkedInvocation); } + + final staticCall = generateCall(_emitStaticTarget(target)); return _isNullCheckableJsInterop(target) ? _wrapWithJsInteropNullCheck(staticCall) : staticCall; @@ -7697,6 +7739,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor DartType expectedReturnType, SourceLocation? originalCallSiteSourceLocation, ) { + final savedCheckedBranch = hotReloadCheckedBranch; + hotReloadCheckedBranch = HotReloadBranchState.checkedBranch; var hoistedPositionalVariables = []; var hoistedNamedVariables = {}; js_ast.Expression? letAssignments; @@ -7780,7 +7824,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor ])..sourceInformation = originalCallSiteSourceLocation; // Cast the result of the checked call or the value returned from a // `NoSuchMethod` invocation. - return js_ast.Binary( + final result = js_ast.Binary( ',', letAssignments, js.call('# == # ? # : #', [ @@ -7790,6 +7834,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor _emitCast(checkResult, expectedReturnType), ]), ); + + hotReloadCheckedBranch = savedCheckedBranch; + return result; } js_ast.Expression _emitJSObjectGetPrototypeOf( diff --git a/runtime/vm/compiler/assembler/assembler_arm.cc b/runtime/vm/compiler/assembler/assembler_arm.cc index c576660e712a..a66263c9abac 100644 --- a/runtime/vm/compiler/assembler/assembler_arm.cc +++ b/runtime/vm/compiler/assembler/assembler_arm.cc @@ -14,13 +14,6 @@ #include "vm/instructions.h" #include "vm/tags.h" -// An extra check since we are assuming the existence of /proc/cpuinfo below. -#if !defined(DART_INCLUDE_SIMULATOR) && !defined(__linux__) && \ - !defined(ANDROID) && !defined(DART_HOST_OS_IOS) && \ - !defined(DART_HOST_OS_MACOS) -#error ARM cross-compile only supported on Linux, Android, iOS, and Mac -#endif - // For use by LR related macros (e.g. CLOBBERS_LR). #define __ this-> diff --git a/runtime/vm/compiler/assembler/assembler_arm.h b/runtime/vm/compiler/assembler/assembler_arm.h index f4c62789f99f..f096c0f606cf 100644 --- a/runtime/vm/compiler/assembler/assembler_arm.h +++ b/runtime/vm/compiler/assembler/assembler_arm.h @@ -260,13 +260,15 @@ class Address : public ValueObject { kind_ = Immediate; base_ = rn; offset_ = offset; - // If the offset can't be encoded in fewer bits, then it'll conflict with - // the encoding of the mode and we won't be able to retrieve it later. - ASSERT(Utils::MagnitudeIsUint(kOpcodeShift, offset)); + // The offset might overflow what can be encoded temporarily before being + // split by PrepareLargeAddress. Make sure this doesn't lead to corruption + // of the mode. + constexpr int32_t kOffsetMask = (1 << kOpcodeShift) - 1; if (offset < 0) { - encoding_ = (am ^ (1 << kUShift)) | -offset; // Flip U to adjust sign. + // Flip U to adjust sign. + encoding_ = (am ^ (1 << kUShift)) | ((-offset) & kOffsetMask); } else { - encoding_ = am | offset; + encoding_ = am | (offset & kOffsetMask); } encoding_ |= ArmEncode::Rn(rn); } diff --git a/runtime/vm/compiler/assembler/assembler_arm_test.cc b/runtime/vm/compiler/assembler/assembler_arm_test.cc index 931f56ce8e0c..12f46fb56961 100644 --- a/runtime/vm/compiler/assembler/assembler_arm_test.cc +++ b/runtime/vm/compiler/assembler/assembler_arm_test.cc @@ -4077,6 +4077,30 @@ intptr_t RegRegImmTests::Asr(intptr_t value, intptr_t shift, OperandSize sz) { return ExtendValue(SignExtendValue(value, sz) >> shift, sz); } +// Regression test for https://github.com/flutter/flutter/issues/172626. +ASSEMBLER_TEST_GENERATE(LargeOffsets, assembler) { + __ LoadFromOffset(R0, PP, 0x208577); // Has bit 21 set. + __ LoadDFromOffset(D0, PP, 0x208577); + __ StoreToOffset(R0, PP, 0x208577); + __ StoreDToOffset(D0, PP, 0x208577); +} + +ASSEMBLER_TEST_RUN(LargeOffsets, test) { + EXPECT_DISASSEMBLY( + "e285c982 add tmp, pp, #2129920\n" + "e59c0577 ldr r0, [tmp, #+1399]\n" + "e308c403 movw tmp, #0x8403\n" + "e340c020 movt tmp, #0x20\n" + "e085c00c add tmp, pp, tmp\n" + "ed9c0b5d vldrd d0, [tmp, #+372]\n" + "e285c982 add tmp, pp, #2129920\n" + "e58c0577 str r0, [tmp, #+1399]\n" + "e308c403 movw tmp, #0x8403\n" + "e340c020 movt tmp, #0x20\n" + "e085c00c add tmp, pp, tmp\n" + "ed8c0b5d vstrd d0, [tmp, #+372]\n"); +} + } // namespace compiler } // namespace dart diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 17b62222bd3f..33252c7bffb8 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -879,15 +879,11 @@ group("create_full_sdk") { # Do not include gen_snapshot binaries for cross-compilation into # SDK, but add them as a dependency, so that they are built. public_deps += [ + "../runtime/bin:gen_snapshot_product_linux_arm", "../runtime/bin:gen_snapshot_product_linux_arm64", "../runtime/bin:gen_snapshot_product_linux_riscv64", "../runtime/bin:gen_snapshot_product_linux_x64", ] - - # assembler_arm.cc disallows ARM cross-compilation on Windows. - if (!is_win) { - public_deps += [ "../runtime/bin:gen_snapshot_product_linux_arm" ] - } } } diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart index 9136db63d447..a32cbc8f81a5 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart @@ -1579,10 +1579,12 @@ declareClass(library, classIdentifier, classDeclaration) { /// /// Called from generated code. void deleteClassMembers(Object oldClass, Object newClass) { - for (var name in getOwnNamesAndSymbols(oldClass)) { - if (JS('', '#.#', newClass, name) == null && - !isStateBearingSymbol(name)) { - JS('', 'delete #.#', oldClass, name); + var oldClassNamesAndSymbols = getOwnNamesAndSymbols(oldClass); + var newClassNamesAndSymbols = getOwnNamesAndSymbols(newClass); + for (var property in oldClassNamesAndSymbols) { + if (JS('', '!#.includes(#)', newClassNamesAndSymbols, property) && + !isStateBearingSymbol(property)) { + JS('', 'delete #.#', oldClass, property); } } } diff --git a/sdk/lib/io/network_profiling.dart b/sdk/lib/io/network_profiling.dart index 5bacb4c8e753..6bd6910d7ba9 100644 --- a/sdk/lib/io/network_profiling.dart +++ b/sdk/lib/io/network_profiling.dart @@ -271,7 +271,6 @@ abstract class _SocketProfile { // Skip any socket that started before `_enableSocketProfiling` was turned // on. final stats = _idToSocketStatistic[idKey]; - assert(stats != null, '"$idKey" not found in "_idToSocketStatistic" map'); if (stats == null) return; switch (type) { case _SocketProfileType.endTime: diff --git a/tests/hot_reload/regress61341_test/main.0.dart b/tests/hot_reload/regress61341_test/main.0.dart new file mode 100644 index 000000000000..cb1b0ce62336 --- /dev/null +++ b/tests/hot_reload/regress61341_test/main.0.dart @@ -0,0 +1,23 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Regression test for a bug that could be the cause for +/// https://github.com/dart-lang/sdk/issues/61341. + +/// Accidental invocation of getters during hot reload could trigger side +/// effects during the operation. The throwing getter should never be called. + +class C { + static int get staticGetter => throw 'Do not invoke this getter!'; + static int get anotherStaticGetter => 42; +} + +Future main() async { + Expect.equals(42, C.anotherStaticGetter); + await hotReload(); + Expect.equals(99, C.anotherStaticGetter); +} diff --git a/tests/hot_reload/regress61341_test/main.1.dart b/tests/hot_reload/regress61341_test/main.1.dart new file mode 100644 index 000000000000..ebcb039aa001 --- /dev/null +++ b/tests/hot_reload/regress61341_test/main.1.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:expect/expect.dart'; +import 'package:reload_test/reload_test_utils.dart'; + +/// Regression test for a bug that could be the cause for +/// https://github.com/dart-lang/sdk/issues/61341. + +/// Accidental invocation of getters during hot reload could trigger side +/// effects during the operation. The throwing getter should never be called. + +class C { + static int get staticGetter => throw 'Do not invoke this getter!'; + static int get anotherStaticGetter => 99; +} + +Future main() async { + Expect.equals(42, C.anotherStaticGetter); + await hotReload(); + Expect.equals(99, C.anotherStaticGetter); +} + +/** DIFF **/ +/* + + class C { + static int get staticGetter => throw 'Do not invoke this getter!'; +- static int get anotherStaticGetter => 42; ++ static int get anotherStaticGetter => 99; + } + + Future main() async { +*/ diff --git a/tests/web/nested_closure_invocations_test.dart b/tests/web/nested_closure_invocations_test.dart new file mode 100644 index 000000000000..573120d24606 --- /dev/null +++ b/tests/web/nested_closure_invocations_test.dart @@ -0,0 +1,225 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +abstract class Either { + Either(); + + B fold(B ifLeft(L l), B ifRight(R r)); + + static Either map20< + L, + A, + A2 extends A, + B, + B2 extends B, + C, + C2 extends C, + D, + D2 extends D, + E, + E2 extends E, + F, + F2 extends F, + G, + G2 extends G, + H, + H2 extends H, + I, + I2 extends I, + J, + J2 extends J, + K, + K2 extends K, + LL, + LL2 extends LL, + M, + M2 extends M, + N, + N2 extends N, + O, + O2 extends O, + P, + P2 extends P, + Q, + Q2 extends Q, + R, + R2 extends R, + S, + S2 extends S, + T, + T2 extends T, + U + >( + Either fa, + Either fb, + Either fc, + Either fd, + Either fe, + Either ff, + Either fg, + Either fh, + Either fi, + Either fj, + Either fk, + Either fl, + Either fm, + Either fn, + Either fo, + Either fp, + Either fq, + Either fr, + Either fs, + Either ft, + U fun( + A a, + B b, + C c, + D d, + E e, + F f, + G g, + H h, + I i, + J j, + K k, + LL l, + M m, + N n, + O o, + P p, + Q q, + R r, + S s, + T t, + ), + ) => fa.fold( + left, + (a) => fb.fold( + left, + (b) => fc.fold( + left, + (c) => fd.fold( + left, + (d) => fe.fold( + left, + (e) => ff.fold( + left, + (f) => fg.fold( + left, + (g) => fh.fold( + left, + (h) => fi.fold( + left, + (i) => fj.fold( + left, + (j) => fk.fold( + left, + (k) => fl.fold( + left, + (l) => fm.fold( + left, + (m) => fn.fold( + left, + (n) => fo.fold( + left, + (o) => fp.fold( + left, + (p) => fq.fold( + left, + (q) => fr.fold( + left, + (r) => fs.fold( + left, + (s) => ft.fold( + left, + (t) => right( + fun( + a, + b, + c, + d, + e, + f, + g, + h, + i, + j, + k, + l, + m, + n, + o, + p, + q, + r, + s, + t, + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ); +} + +Either left(L l) => new Left(l); +Either right(R r) => new Right(r); + +class Left extends Either { + final L _l; + + B fold(B ifLeft(L l), B ifRight(R r)) => ifLeft(_l); + + Left(this._l); +} + +class Right extends Either { + final R _r; + + B fold(B ifLeft(L l), B ifRight(R r)) => ifRight(_r); + + Right(this._r); +} + +void main() { + Either.map20( + left(0), + left(1), + left(2), + left(3), + left(4), + left(5), + left(6), + left(7), + left(8), + left(9), + left(10), + left(11), + left(12), + left(13), + left(14), + left(15), + left(16), + left(17), + left(18), + left(19), + (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t) => 20, + ); +} diff --git a/tools/VERSION b/tools/VERSION index 5429ee7dbcbf..fbd09e81c2c6 100644 --- a/tools/VERSION +++ b/tools/VERSION @@ -23,9 +23,9 @@ # * Making cherry-picks to stable channel # - increase PATCH by 1 # -CHANNEL main +CHANNEL stable MAJOR 3 MINOR 9 -PATCH 0 +PATCH 4 PRERELEASE 0 PRERELEASE_PATCH 0