Version 2.16.0-64.0.dev

Merge commit '20f1a954fe6681c1edaddeb13bac4899d7048edd' into 'dev'
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index 156cd9d..7df673c 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -664,10 +664,17 @@
     // Wire up feature flags.
     OptionHandler(Flags.canary, passThrough),
     OptionHandler(Flags.noShipping, passThrough),
+    // Shipped features.
+    for (var feature in features.shipped)
+      OptionHandler('--${feature.flag}', passThrough),
+    for (var feature in features.shipped)
+      OptionHandler('--no-${feature.flag}', passThrough),
+    // Shipping features.
     for (var feature in features.shipping)
       OptionHandler('--${feature.flag}', passThrough),
     for (var feature in features.shipping)
       OptionHandler('--no-${feature.flag}', passThrough),
+    // Canary features.
     for (var feature in features.canary)
       OptionHandler('--${feature.flag}', passThrough),
     for (var feature in features.canary)
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index d0a06d4..e6db1d9 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -18,6 +18,7 @@
 }
 
 enum FeatureStatus {
+  shipped,
   shipping,
   canary,
 }
@@ -31,6 +32,10 @@
 /// passed. The [isNegativeFlag] bool flips things around so while in [canary]
 /// the [FeatureOption] is enabled unless explicitly disabled, and while in
 /// [staging] it is disabled unless explicitly enabled.
+///
+/// Finally, mature features can be moved to [shipped], at which point we ignore
+/// the flag, but throw if the value of the flag is unexpected(i.e. if a
+/// positive flag is disabled, or a negative flag is enabled).
 class FeatureOption {
   final String flag;
   final bool isNegativeFlag;
@@ -71,6 +76,9 @@
   /// Whether to generate code compliant with Content Security Policy.
   FeatureOption useContentSecurityPolicy = FeatureOption('csp');
 
+  /// [FeatureOption]s which are shipped and cannot be toggled.
+  late final List<FeatureOption> shipped = [];
+
   /// [FeatureOption]s which default to enabled.
   late final List<FeatureOption> shipping = [
     legacyJavaScript,
@@ -107,6 +115,7 @@
 
   /// Parses a [List<String>] and enables / disables features as necessary.
   void parse(List<String> options) {
+    _verifyShippedFeatures(options, shipped);
     _extractFeatures(options, shipping, FeatureStatus.shipping);
     _extractFeatures(options, canary, FeatureStatus.canary);
   }
@@ -908,4 +917,28 @@
   }
 }
 
+void _verifyShippedFeatures(
+    List<String> options, List<FeatureOption> features) {
+  for (var feature in features) {
+    String featureFlag = feature.flag;
+    String enableFeatureFlag = '--$featureFlag';
+    String disableFeatureFlag = '--no-$featureFlag';
+    bool enableFeature = _hasOption(options, enableFeatureFlag);
+    bool disableFeature = _hasOption(options, disableFeatureFlag);
+    if (enableFeature && disableFeature) {
+      throw ArgumentError("'$enableFeatureFlag' incompatible with "
+          "'$disableFeatureFlag'");
+    }
+    if (enableFeature && feature.isNegativeFlag) {
+      throw ArgumentError(
+          "$disableFeatureFlag has already shipped and cannot be enabled.");
+    }
+    if (disableFeature && !feature.isNegativeFlag) {
+      throw ArgumentError(
+          "$enableFeatureFlag has already shipped and cannot be disabled.");
+    }
+    feature.state = !feature.isNegativeFlag;
+  }
+}
+
 const String _UNDETERMINED_BUILD_ID = "build number could not be determined";
diff --git a/pkg/compiler/test/end_to_end/feature_options_test.dart b/pkg/compiler/test/end_to_end/feature_options_test.dart
index d950d19..db2c87e 100644
--- a/pkg/compiler/test/end_to_end/feature_options_test.dart
+++ b/pkg/compiler/test/end_to_end/feature_options_test.dart
@@ -12,6 +12,8 @@
 import 'package:compiler/src/options.dart' show FeatureOptions, FeatureOption;
 
 class TestFeatureOptions extends FeatureOptions {
+  FeatureOption f1 = FeatureOption('f1');
+  FeatureOption noF2 = FeatureOption('f2', isNegativeFlag: true);
   FeatureOption sf1 = FeatureOption('sf1');
   FeatureOption sf2 = FeatureOption('sf2');
   FeatureOption noSf3 = FeatureOption('sf3', isNegativeFlag: true);
@@ -22,6 +24,9 @@
   FeatureOption noCf4 = FeatureOption('cf4', isNegativeFlag: true);
 
   @override
+  List<FeatureOption> shipped;
+
+  @override
   List<FeatureOption> shipping;
 
   @override
@@ -29,6 +34,7 @@
 
   // Initialize feature lists.
   TestFeatureOptions() {
+    shipped = [f1, noF2];
     shipping = [sf1, sf2, noSf3, noSf4];
     canary = [cf1, cf2, noCf3, noCf4];
   }
@@ -40,8 +46,14 @@
   return tfo;
 }
 
+void expectShipped(TestFeatureOptions tfo) {
+  Expect.isTrue(tfo.f1.isEnabled);
+  Expect.isTrue(tfo.noF2.isDisabled);
+}
+
 void testShipping() {
   var tfo = test([]);
+  expectShipped(tfo);
   Expect.isTrue(tfo.sf1.isEnabled);
   Expect.isTrue(tfo.sf2.isEnabled);
   Expect.isTrue(tfo.noSf3.isDisabled);
@@ -54,6 +66,7 @@
 
 void testNoShipping() {
   var tfo = test([Flags.noShipping]);
+  expectShipped(tfo);
   Expect.isTrue(tfo.sf1.isDisabled);
   Expect.isTrue(tfo.sf2.isDisabled);
   Expect.isTrue(tfo.noSf3.isEnabled);
@@ -66,6 +79,7 @@
 
 void testCanary() {
   var tfo = test([Flags.canary]);
+  expectShipped(tfo);
   Expect.isTrue(tfo.sf1.isEnabled);
   Expect.isTrue(tfo.sf2.isEnabled);
   Expect.isTrue(tfo.noSf3.isDisabled);
@@ -78,6 +92,7 @@
 
 void testShippingDisabled() {
   var tfo = test(['--no-sf2', '--sf3']);
+  expectShipped(tfo);
   Expect.isTrue(tfo.sf1.isEnabled);
   Expect.isTrue(tfo.sf2.isDisabled);
   Expect.isTrue(tfo.noSf3.isEnabled);
@@ -90,6 +105,7 @@
 
 void testCanaryDisabled() {
   var tfo = test([Flags.canary, '--no-sf2', '--sf3', '--no-cf1', '--cf3']);
+  expectShipped(tfo);
   Expect.isTrue(tfo.sf1.isEnabled);
   Expect.isTrue(tfo.sf2.isDisabled);
   Expect.isTrue(tfo.noSf3.isEnabled);
@@ -102,6 +118,7 @@
 
 void testNoShippingEnabled() {
   var tfo = test([Flags.noShipping, '--sf1', '--no-sf3', '--cf2', '--no-cf3']);
+  expectShipped(tfo);
   Expect.isTrue(tfo.sf1.isEnabled);
   Expect.isTrue(tfo.sf2.isDisabled);
   Expect.isTrue(tfo.noSf3.isDisabled);
@@ -114,6 +131,7 @@
 
 void testNoCanaryEnabled() {
   var tfo = test(['--cf1', '--no-cf3']);
+  expectShipped(tfo);
   Expect.isTrue(tfo.sf1.isEnabled);
   Expect.isTrue(tfo.sf2.isEnabled);
   Expect.isTrue(tfo.noSf3.isDisabled);
@@ -128,6 +146,11 @@
   Expect.throwsArgumentError(() => test(['--cf1', '--no-cf1']));
 }
 
+void testNoShippedDisable() {
+  Expect.throwsArgumentError(() => test(['--no-f1']));
+  Expect.throwsArgumentError(() => test(['--f2']));
+}
+
 void flavorStringTest(List<String> options, String expectedFlavorString) {
   var tfo = test(options);
   Expect.equals(expectedFlavorString, tfo.flavorString());
@@ -159,6 +182,7 @@
   testNoCanaryEnabled();
   testNoShippingEnabled();
   testFlagCollision();
+  testNoShippedDisable();
 
   // Supplemental tests.
   flavorStringTests();
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 5f70e3b..6f24604 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -8,7 +8,6 @@
 
 import 'package:collection/collection.dart';
 import 'package:meta/meta.dart';
-import 'package:package_config/package_config.dart';
 import 'package:path/path.dart' as path;
 import 'package:vm_service/vm_service.dart' as vm;
 
@@ -949,12 +948,13 @@
   /// 'additionalProjectPaths' in the launch arguments. An editor should include
   /// the paths of all open workspace folders in 'additionalProjectPaths' to
   /// support this feature correctly.
-  bool isExternalPackageLibrary(Uri uri) {
+  Future<bool> isExternalPackageLibrary(ThreadInfo thread, Uri uri) async {
     if (!uri.isScheme('package')) {
       return false;
     }
-    final libraryPath = resolvePackageUri(uri);
-    if (libraryPath == null) {
+
+    final packagePath = await thread.resolveUriToPackageLibPath(uri);
+    if (packagePath == null) {
       return false;
     }
 
@@ -962,9 +962,10 @@
     // may have returned different casing (e.g. Windows drive letters). It's
     // almost certain a user wouldn't have a "local" package and an "external"
     // package with paths differing only be case.
-    final libraryPathLower = libraryPath.toLowerCase();
-    return !projectPaths.any((projectPath) =>
-        path.isWithin(projectPath.toLowerCase(), libraryPathLower));
+    final packagePathLower = packagePath.toLowerCase();
+    return !projectPaths
+        .map((projectPath) => projectPath.toLowerCase())
+        .any((projectPath) => path.isWithin(projectPath, packagePathLower));
   }
 
   /// Checks whether this library is from the SDK.
@@ -1002,10 +1003,10 @@
   ///
   /// Initial values are provided in the launch arguments, but may be updated
   /// by the `updateDebugOptions` custom request.
-  bool libaryIsDebuggable(Uri uri) {
+  Future<bool> libraryIsDebuggable(ThreadInfo thread, Uri uri) async {
     if (isSdkLibrary(uri)) {
       return _isolateManager.debugSdkLibraries;
-    } else if (isExternalPackageLibrary(uri)) {
+    } else if (await isExternalPackageLibrary(thread, uri)) {
       return _isolateManager.debugExternalPackageLibraries;
     } else {
       return true;
@@ -1024,12 +1025,6 @@
     sendResponse();
   }
 
-  /// Resolves a `package: URI` to the real underlying source path.
-  ///
-  /// Returns `null` if no mapping was possible, for example if the package is
-  /// not in the package mapping file.
-  String? resolvePackageUri(Uri uri) => _converter.resolvePackageUri(uri);
-
   /// restart is called by the client when the user invokes a restart (for
   /// example with the button on the debug toolbar).
   ///
@@ -1296,6 +1291,15 @@
           (frame) => frame.kind == vm.FrameKind.kAsyncSuspensionMarker,
         );
 
+        // Pre-resolve all URIs in batch so the call below does not trigger
+        // many requests to the server.
+        final allUris = frames
+            .map((frame) => frame.location?.script?.uri)
+            .whereNotNull()
+            .map(Uri.parse)
+            .toList();
+        await thread.resolveUrisToPathsBatch(allUris);
+
         Future<StackFrame> convert(int index, vm.Frame frame) async {
           return _converter.convertVmToDapStackFrame(
             thread,
@@ -1396,14 +1400,12 @@
 
   /// Sets the package config file to use for `package: URI` resolution.
   ///
-  /// TODO(dantup): Remove this once
-  ///   https://github.com/dart-lang/sdk/issues/45530 is done as it will not be
-  ///   necessary.
+  /// It is no longer necessary to call this method as the package config file
+  /// is no longer used. URI lookups are done via the VM Service.
+  @Deprecated('No longer necessary, URI lookups are done via VM Service')
   void usePackageConfigFile(File packageConfig) {
-    _converter.packageConfig = PackageConfig.parseString(
-      packageConfig.readAsStringSync(),
-      Uri.file(packageConfig.path),
-    );
+    // TODO(dantup): Remove this method after Flutter DA is updated not to use
+    // it.
   }
 
   /// [variablesRequest] is called by the client to request child variables for
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
index 3dca392..3560867 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
@@ -108,18 +108,6 @@
       ...?args.args,
     ];
 
-    // Find the package_config file for this script.
-    // TODO(dantup): Remove this once
-    //   https://github.com/dart-lang/sdk/issues/45530 is done as it will not be
-    //   necessary.
-    var possibleRoot = path.isAbsolute(args.program)
-        ? path.dirname(args.program)
-        : path.dirname(path.normalize(path.join(args.cwd ?? '', args.program)));
-    final packageConfig = findPackageConfigFile(possibleRoot);
-    if (packageConfig != null) {
-      this.usePackageConfigFile(packageConfig);
-    }
-
     // If the client supports runInTerminal and args.console is set to either
     // 'terminal' or 'runInTerminal' we won't run the process ourselves, but
     // instead call the client to run it for us (this allows it to run in a
@@ -166,18 +154,6 @@
       return;
     }
 
-    // Find the package_config file for this script.
-    // TODO(dantup): Remove this once
-    //   https://github.com/dart-lang/sdk/issues/45530 is done as it will not be
-    //   necessary.
-    final cwd = args.cwd;
-    if (cwd != null) {
-      final packageConfig = findPackageConfigFile(cwd);
-      if (packageConfig != null) {
-        this.usePackageConfigFile(packageConfig);
-      }
-    }
-
     final uri = vmServiceUri != null
         ? Uri.parse(vmServiceUri)
         : await waitForVmServiceInfoFile(logger, File(vmServiceInfoFile!));
diff --git a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
index 9e517da..2a63b38 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -6,7 +6,6 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:path/path.dart' as path;
 import 'package:pedantic/pedantic.dart';
 import 'package:vm_service/vm_service.dart' as vm;
 
@@ -118,18 +117,6 @@
       ...?args.args,
     ];
 
-    // Find the package_config file for this script.
-    // TODO(dantup): Remove this once
-    //   https://github.com/dart-lang/sdk/issues/45530 is done as it will not be
-    //   necessary.
-    var possibleRoot = path.isAbsolute(args.program)
-        ? path.dirname(args.program)
-        : path.dirname(path.normalize(path.join(args.cwd ?? '', args.program)));
-    final packageConfig = findPackageConfigFile(possibleRoot);
-    if (packageConfig != null) {
-      this.usePackageConfigFile(packageConfig);
-    }
-
     // TODO(dantup): Support passing env to both of these.
 
     logger?.call('Spawning $vmPath with $processArgs in ${args.cwd}');
diff --git a/pkg/dds/lib/src/dap/adapters/mixins.dart b/pkg/dds/lib/src/dap/adapters/mixins.dart
index 3b9991a..b9f307f 100644
--- a/pkg/dds/lib/src/dap/adapters/mixins.dart
+++ b/pkg/dds/lib/src/dap/adapters/mixins.dart
@@ -17,32 +17,13 @@
 mixin PackageConfigUtils {
   /// Find the `package_config.json` file for the program being launched.
   ///
-  /// TODO(dantup): Remove this once
-  ///   https://github.com/dart-lang/sdk/issues/45530 is done as it will not be
-  ///   necessary.
+  /// It is no longer necessary to call this method as the package config file
+  /// is no longer used. URI lookups are done via the VM Service.
+  @Deprecated('No longer necessary, URI lookups are done via VM Service')
   File? findPackageConfigFile(String possibleRoot) {
-    File? packageConfig;
-    while (true) {
-      packageConfig =
-          File(path.join(possibleRoot, '.dart_tool', 'package_config.json'));
-
-      // If this packageconfig exists, use it.
-      if (packageConfig.existsSync()) {
-        break;
-      }
-
-      final parent = path.dirname(possibleRoot);
-
-      // If we can't go up anymore, the search failed.
-      if (parent == possibleRoot) {
-        packageConfig = null;
-        break;
-      }
-
-      possibleRoot = parent;
-    }
-
-    return packageConfig;
+    // TODO(dantup): Remove this method after Flutter DA is updated not to use
+    // it.
+    return null;
   }
 }
 
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index 6e00463..6d678dc 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -4,8 +4,10 @@
 
 import 'dart:async';
 import 'dart:convert';
+import 'dart:io';
 
 import 'package:collection/collection.dart';
+import 'package:path/path.dart' as path;
 import 'package:vm_service/vm_service.dart' as vm;
 
 import 'adapters/dart.dart';
@@ -53,6 +55,9 @@
   /// [debugExternalPackageLibraries] in one step.
   bool debugExternalPackageLibraries = true;
 
+  /// The root of the Dart SDK containing the VM running the debug adapter.
+  late final String sdkRoot;
+
   /// Tracks breakpoints last provided by the client so they can be sent to new
   /// isolates that appear after initial breakpoints were sent.
   final Map<String, List<SourceBreakpoint>> _clientBreakpointsByUri = {};
@@ -93,7 +98,10 @@
   /// Any leading character matched in place of the dollar is in the first capture.
   final _braceNotPrefixedByDollarOrBackslashPattern = RegExp(r'(^|[^\\\$]){');
 
-  IsolateManager(this._adapter);
+  IsolateManager(this._adapter) {
+    final vmPath = Platform.resolvedExecutable;
+    sdkRoot = path.dirname(path.dirname(vmPath));
+  }
 
   /// A list of all current active isolates.
   ///
@@ -110,7 +118,7 @@
     await Future.wait(_threadsByThreadId.values.map(
       // debuggable libraries is the only thing currently affected by these
       // changable options.
-      (isolate) => _sendLibraryDebuggables(isolate.isolate),
+      (thread) => _sendLibraryDebuggables(thread),
     ));
   }
 
@@ -175,7 +183,7 @@
     final registrationCompleter =
         _isolateRegistrations.putIfAbsent(isolate.id!, () => Completer<void>());
 
-    final info = _threadsByIsolateId.putIfAbsent(
+    final thread = _threadsByIsolateId.putIfAbsent(
       isolate.id!,
       () {
         // The first time we see an isolate, start tracking it.
@@ -191,13 +199,13 @@
 
     // If it's just become runnable (IsolateRunnable), configure the isolate
     // by sending breakpoints etc.
-    if (eventKind == vm.EventKind.kIsolateRunnable && !info.runnable) {
-      info.runnable = true;
-      await _configureIsolate(isolate);
+    if (eventKind == vm.EventKind.kIsolateRunnable && !thread.runnable) {
+      thread.runnable = true;
+      await _configureIsolate(thread);
       registrationCompleter.complete();
     }
 
-    return info;
+    return thread;
   }
 
   /// Calls reloadSources for all isolates.
@@ -272,7 +280,7 @@
 
     // Send the breakpoints to all existing threads.
     await Future.wait(_threadsByThreadId.values
-        .map((isolate) => _sendBreakpoints(isolate.isolate, uri: uri)));
+        .map((thread) => _sendBreakpoints(thread, uri: uri)));
   }
 
   /// Records exception pause mode as one of 'None', 'Unhandled' or 'All'. All
@@ -282,7 +290,7 @@
 
     // Send to all existing threads.
     await Future.wait(_threadsByThreadId.values.map(
-      (isolate) => _sendExceptionPauseMode(isolate.isolate),
+      (thread) => _sendExceptionPauseMode(thread),
     ));
   }
 
@@ -320,12 +328,15 @@
 
   /// Configures a new isolate, setting it's exception-pause mode, which
   /// libraries are debuggable, and sending all breakpoints.
-  Future<void> _configureIsolate(vm.IsolateRef isolate) async {
+  Future<void> _configureIsolate(ThreadInfo thread) async {
+    // Libraries must be set as debuggable _before_ sending breakpoints, or
+    // they may fail for SDK sources.
     await Future.wait([
-      _sendLibraryDebuggables(isolate),
-      _sendExceptionPauseMode(isolate),
-      _sendBreakpoints(isolate),
+      _sendLibraryDebuggables(thread),
+      _sendExceptionPauseMode(thread),
     ], eagerError: true);
+
+    await _sendBreakpoints(thread);
   }
 
   /// Evaluates an expression, returning the result if it is a [vm.InstanceRef]
@@ -414,7 +425,7 @@
     // For PausePostRequest we need to re-send all breakpoints; this happens
     // after a hot restart.
     if (eventKind == vm.EventKind.kPausePostRequest) {
-      await _configureIsolate(isolate);
+      await _configureIsolate(thread);
       if (resumeIfStarting) {
         await resumeThread(thread.threadId);
       }
@@ -493,6 +504,26 @@
     }
   }
 
+  /// Attempts to resolve [uris] to file:/// URIs via the VM Service.
+  ///
+  /// This method calls the VM service directly. Most requests to resolve URIs
+  /// should go through [ThreadInfo]'s resolveXxx methods which perform caching
+  /// of results.
+  Future<List<Uri?>?> _lookupResolvedPackageUris<T extends vm.Response>(
+    vm.IsolateRef isolate,
+    List<Uri> uris,
+  ) async {
+    final isolateId = isolate.id!;
+    final uriStrings = uris.map((uri) => uri.toString()).toList();
+    final res = await _adapter.vmService
+        ?.lookupResolvedPackageUris(isolateId, uriStrings);
+
+    return res?.uris
+        ?.cast<String?>()
+        .map((uri) => uri != null ? Uri.parse(uri) : null)
+        .toList();
+  }
+
   /// Interpolates and prints messages for any log points.
   ///
   /// Log Points are breakpoints with string messages attached. When the VM hits
@@ -546,13 +577,13 @@
   /// 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<void> _sendBreakpoints(vm.IsolateRef isolate, {String? uri}) async {
+  Future<void> _sendBreakpoints(ThreadInfo thread, {String? uri}) async {
     final service = _adapter.vmService;
     if (!debug || service == null) {
       return;
     }
 
-    final isolateId = isolate.id!;
+    final isolateId = thread.isolate.id!;
 
     // 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
@@ -572,8 +603,14 @@
       final newBreakpoints = _clientBreakpointsByUri[uri] ?? const [];
       await Future.forEach<SourceBreakpoint>(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).toFilePath(),
+          );
+
           final vmBp = await service.addBreakpointWithScriptUri(
-              isolateId, uri, bp.line,
+              isolateId, vmUri.toString(), bp.line,
               column: bp.column);
           existingBreakpointsForIsolateAndUri.add(vmBp);
           _clientBreakpointsByVmId[vmBp.id!] = bp;
@@ -588,37 +625,47 @@
   }
 
   /// Sets the exception pause mode for an individual isolate.
-  Future<void> _sendExceptionPauseMode(vm.IsolateRef isolate) async {
+  Future<void> _sendExceptionPauseMode(ThreadInfo thread) async {
     final service = _adapter.vmService;
     if (!debug || service == null) {
       return;
     }
 
     await service.setIsolatePauseMode(
-      isolate.id!,
+      thread.isolate.id!,
       exceptionPauseMode: _exceptionPauseMode,
     );
   }
 
   /// Calls setLibraryDebuggable for all libraries in the given isolate based
   /// on the debug settings.
-  Future<void> _sendLibraryDebuggables(vm.IsolateRef isolateRef) async {
+  Future<void> _sendLibraryDebuggables(ThreadInfo thread) async {
     final service = _adapter.vmService;
     if (!debug || service == null) {
       return;
     }
 
-    final isolateId = isolateRef.id!;
+    final isolateId = thread.isolate.id!;
 
     final isolate = await service.getIsolate(isolateId);
     final libraries = isolate.libraries;
     if (libraries == null) {
       return;
     }
+
+    // Pre-resolve all URIs in batch so the call below does not trigger
+    // many requests to the server.
+    final allUris = libraries
+        .map((library) => library.uri)
+        .whereNotNull()
+        .map(Uri.parse)
+        .toList();
+    await thread.resolveUrisToPackageLibPathsBatch(allUris);
+
     await Future.wait(libraries.map((library) async {
       final libraryUri = library.uri;
       final isDebuggable = libraryUri != null
-          ? _adapter.libaryIsDebuggable(Uri.parse(libraryUri))
+          ? await _adapter.libraryIsDebuggable(thread, Uri.parse(libraryUri))
           : false;
       await service.setLibraryDebuggable(isolateId, library.id!, isDebuggable);
     }));
@@ -673,14 +720,23 @@
   /// breakpoint or exception that occur early on.
   bool hasBeenStarted = false;
 
-  // The most recent pauseEvent for this isolate.
+  /// The most recent pauseEvent for this isolate.
   vm.Event? pauseEvent;
 
-  // A cache of requests (Futures) to fetch scripts, so that multiple requests
-  // that require scripts (for example looking up locations for stack frames from
-  // tokenPos) can share the same response.
+  /// A cache of requests (Futures) to fetch scripts, so that multiple requests
+  /// that require scripts (for example looking up locations for stack frames from
+  /// tokenPos) can share the same response.
   final _scripts = <String, Future<vm.Script>>{};
 
+  /// A cache of requests (Futures) to resolve URIs to their local file paths.
+  ///
+  /// Used so that multiple requests that require them (for example looking up
+  /// locations for stack frames from tokenPos) can share the same response.
+  ///
+  /// Keys are URIs in string form.
+  /// Values are file paths (not file URIs!).
+  final _resolvedPaths = <String, Future<String?>>{};
+
   /// Whether this isolate has an in-flight resume request that has not yet
   /// been responded to.
   var hasPendingResume = false;
@@ -699,9 +755,207 @@
     return _scripts.putIfAbsent(script.id!, () => getObject<vm.Script>(script));
   }
 
+  /// Resolves a source file path into a URI for the VM.
+  ///
+  /// sdk-path/lib/core/print.dart -> dart:core/print.dart
+  ///
+  /// This is required so that when the user sets a breakpoint in an SDK source
+  /// (which they may have nagivated to via the Analysis Server) we generate a
+  /// vaid URI that the VM would create a breakpoint for.
+  Future<Uri?> resolvePathToUri(String filePath) async {
+    // We don't currently need to call lookupPackageUris because the VM can
+    // handle incoming file:/// URIs for packages, and also the org-dartlang-sdk
+    // URIs directly for SDK sources (we do not need to convert to 'dart:'),
+    // however this method is Future-returning in case this changes in future
+    // and we need to include a call to lookupPackageUris here.
+    return _convertPathToOrgDartlangSdk(filePath) ?? Uri.file(filePath);
+  }
+
+  /// Batch resolves source URIs from the VM to a file path for the package lib
+  /// folder.
+  ///
+  /// This method is more performant than repeatedly calling
+  /// [resolveUrisToPackageLibPath] because it resolves multiple URIs in a
+  /// single request to the VM.
+  ///
+  /// Results are cached and shared with [resolveUrisToPackageLibPath] (and
+  /// [resolveUriToPath]) so it's reasonable to call this method up-front and
+  /// then use [resolveUrisToPackageLibPath] (and [resolveUriToPath]) to read
+  /// the results later.
+  Future<List<String?>> resolveUrisToPackageLibPathsBatch(
+    List<Uri> uris,
+  ) async {
+    final results = await resolveUrisToPathsBatch(uris);
+    return results
+        .mapIndexed((i, filePath) => _trimPathToLibFolder(filePath, uris[i]))
+        .toList();
+  }
+
+  /// Batch resolves source URIs from the VM to a file path.
+  ///
+  /// This method is more performant than repeatedly calling [resolveUriToPath]
+  /// because it resolves multiple URIs in a single request to the VM.
+  ///
+  /// Results are cached and shared with [resolveUriToPath] so it's reasonable
+  /// to call this method up-front and then use [resolveUriToPath] to read
+  /// the results later.
+  Future<List<String?>> resolveUrisToPathsBatch(List<Uri> uris) async {
+    // First find the set of URIs we don't already have results for.
+    final requiredUris = uris
+        .where((uri) => !uri.isScheme('file'))
+        .where((uri) => !_resolvedPaths.containsKey(uri.toString()))
+        .toSet() // Take only distinct values.
+        .toList();
+
+    if (requiredUris.isNotEmpty) {
+      // Populate completers for each URI before we start the request so that
+      // concurrent calls to this method will not start their own requests.
+      final completers = Map<String, Completer<String?>>.fromEntries(
+        requiredUris.map((uri) => MapEntry('$uri', Completer<String?>())),
+      );
+      completers.forEach(
+        (uri, completer) => _resolvedPaths[uri] = completer.future,
+      );
+      final results =
+          await _manager._lookupResolvedPackageUris(isolate, requiredUris);
+      if (results == null) {
+        // If no result, all of the results are null.
+        completers.forEach((uri, completer) => completer.complete(null));
+      } else {
+        // Otherwise, complete each one by index with the corresponding value.
+        results.map(_convertUriToFilePath).forEachIndexed((i, result) {
+          final uri = requiredUris[i].toString();
+          completers[uri]!.complete(result);
+        });
+      }
+    }
+
+    // Finally, assemble a list of the values by using the cached futures and
+    // the original list. Any non-file URI is guaranteed to be in [_resolvedPaths]
+    // because they were either filtered out of [requiredUris] because they were
+    // already there, or we then populated completers for them above.
+    final futures = uris.map((uri) async {
+      return uri.isScheme('file')
+          ? uri.toFilePath()
+          : await _resolvedPaths[uri.toString()]!;
+    });
+    return Future.wait(futures);
+  }
+
+  /// Resolves a source URI to a file path for the lib folder of its package.
+  ///
+  /// package:foo/a/b/c/d.dart -> /code/packages/foo/lib
+  ///
+  /// This method is an optimisation over calling [resolveUriToPath] where only
+  /// the package root is required (for example when determining whether a
+  /// package is within the users workspace). This method allows results to be
+  /// cached per-package to avoid hitting the VM Service for each individual
+  /// library within a package.
+  Future<String?> resolveUriToPackageLibPath(Uri uri) async {
+    final result = await resolveUrisToPackageLibPathsBatch([uri]);
+    return result.first;
+  }
+
+  /// Resolves a source URI from the VM to a file path.
+  ///
+  /// dart:core/print.dart -> sdk-path/lib/core/print.dart
+  ///
+  /// This is required so that when the user stops (or navigates via a stack
+  /// frame) we open the same file on their local disk. If we downloaded the
+  /// source from the VM, they would end up seeing two copies of files (and they
+  /// would each have their own breakpoints) which can be confusing.
+  Future<String?> resolveUriToPath(Uri uri) async {
+    final result = await resolveUrisToPathsBatch([uri]);
+    return result.first;
+  }
+
   /// Stores some basic data indexed by an integer for use in "reference" fields
   /// that are round-tripped to the client.
   int storeData(Object data) => _manager.storeData(this, data);
+
+  /// Converts a URI in the form org-dartlang-sdk:///sdk/lib/collection/hash_set.dart
+  /// to a local file path based on the current SDK.
+  String? _convertOrgDartlangSdkToPath(Uri uri) {
+    // org-dartlang-sdk URIs can be in multiple forms:
+    //
+    //   - org-dartlang-sdk:///sdk/lib/collection/hash_set.dart
+    //   - org-dartlang-sdk:///runtime/lib/convert_patch.dart
+    //
+    // We currently only handle the sdk folder, as we don't know which runtime
+    // is being used (this code is shared) and do not want to map to the wrong
+    // sources.
+    if (uri.pathSegments.isNotEmpty && uri.pathSegments.first == 'sdk') {
+      // TODO(dantup): Do we need to worry about this content not matching
+      //   up with what's local (eg. for Flutter the VM running the app is
+      //   on another device to the VM running this DA).
+      final sdkRoot = _manager.sdkRoot;
+      return path.joinAll([sdkRoot, ...uri.pathSegments.skip(1)]);
+    }
+
+    return null;
+  }
+
+  /// Converts a file path inside the current SDK root into a URI in the form
+  /// org-dartlang-sdk:///sdk/lib/collection/hash_set.dart.
+  Uri? _convertPathToOrgDartlangSdk(String input) {
+    final sdkRoot = _manager.sdkRoot;
+    if (path.isWithin(sdkRoot, input)) {
+      final relative = path.relative(input, from: sdkRoot);
+      return Uri(
+        scheme: 'org-dartlang-sdk',
+        host: '',
+        pathSegments: ['sdk', ...path.split(relative)],
+      );
+    }
+
+    return null;
+  }
+
+  /// Converts a URI to a file path.
+  ///
+  /// Supports file:// URIs and org-dartlang-sdk:// URIs.
+  String? _convertUriToFilePath(Uri? input) {
+    if (input == null) {
+      return null;
+    } else if (input.isScheme('file')) {
+      return input.toFilePath();
+    } else if (input.isScheme('org-dartlang-sdk')) {
+      return _convertOrgDartlangSdkToPath(input);
+    } else {
+      return null;
+    }
+  }
+
+  /// Helper to remove a libraries path from the a file path so it points at the
+  /// lib folder.
+  ///
+  /// [uri] should be the equivalent package: URI and is used to know how many
+  /// segments to remove from the file path to get to the lib folder.
+  String? _trimPathToLibFolder(String? filePath, Uri uri) {
+    if (filePath == null) {
+      return null;
+    }
+
+    final fileUri = Uri.file(filePath);
+
+    // Track how many segments from the path are from the lib folder to the
+    // libary that will need to be removed later.
+    final libraryPathSegments = uri.pathSegments.length - 1;
+
+    // It should never be the case that the returned value doesn't have at
+    // least as many segments as the path of the URI.
+    assert(fileUri.pathSegments.length > libraryPathSegments);
+    if (fileUri.pathSegments.length <= libraryPathSegments) {
+      return filePath;
+    }
+
+    // Strip off the correct number of segments to the resulting path points
+    // to the root of the package:/ URI.
+    final keepSegments = fileUri.pathSegments.length - libraryPathSegments;
+    return fileUri
+        .replace(pathSegments: fileUri.pathSegments.sublist(0, keepSegments))
+        .toFilePath();
+  }
 }
 
 class _StoredData {
diff --git a/pkg/dds/lib/src/dap/protocol_converter.dart b/pkg/dds/lib/src/dap/protocol_converter.dart
index a3e836e..0bedd50 100644
--- a/pkg/dds/lib/src/dap/protocol_converter.dart
+++ b/pkg/dds/lib/src/dap/protocol_converter.dart
@@ -6,7 +6,6 @@
 import 'dart:io';
 
 import 'package:collection/collection.dart';
-import 'package:package_config/package_config_types.dart';
 import 'package:path/path.dart' as path;
 import 'package:vm_service/vm_service.dart' as vm;
 
@@ -26,11 +25,6 @@
   /// the debug session.
   final DartDebugAdapter _adapter;
 
-  /// Temporary PackageConfig used for resolving package: URIs.
-  /// TODO(dantup): Replace this implementation with one that calls the VM
-  ///   Service once https://github.com/dart-lang/sdk/issues/45530 is done.
-  PackageConfig packageConfig = PackageConfig.empty;
-
   ProtocolConverter(this._adapter);
 
   /// Converts an absolute path to one relative to the cwd used to launch the
@@ -388,13 +382,15 @@
     final tokenPos = location.tokenPos;
     final scriptRefUri = scriptRef?.uri;
     final uri = scriptRefUri != null ? Uri.parse(scriptRefUri) : null;
+    final uriIsDart = uri?.isScheme('dart') ?? false;
     final uriIsPackage = uri?.isScheme('package') ?? false;
-    final sourcePath = uri != null ? await convertVmUriToSourcePath(uri) : null;
+    final sourcePath = uri != null ? await thread.resolveUriToPath(uri) : null;
     var canShowSource = sourcePath != null && File(sourcePath).existsSync();
 
     // Download the source if from a "dart:" uri.
     int? sourceReference;
-    if (uri != null &&
+    if (!canShowSource &&
+        uri != null &&
         (uri.isScheme('dart') || uri.isScheme('org-dartlang-app')) &&
         scriptRef != null) {
       sourceReference = thread.storeData(scriptRef);
@@ -416,17 +412,18 @@
     // SDK and debugSdkLibraries=false) then we should also mark it as
     // deemphasized so that the editor can jump up the stack to the first frame
     // of debuggable code.
-    final isDebuggable = uri != null && _adapter.libaryIsDebuggable(uri);
+    final isDebuggable =
+        uri != null && await _adapter.libraryIsDebuggable(thread, uri);
     final presentationHint = isDebuggable ? null : 'deemphasize';
     final origin = uri != null && _adapter.isSdkLibrary(uri)
         ? 'from the SDK'
-        : uri != null && _adapter.isExternalPackageLibrary(uri)
+        : uri != null && await _adapter.isExternalPackageLibrary(thread, uri)
             ? 'from external packages'
             : null;
 
     final source = canShowSource
         ? dap.Source(
-            name: uriIsPackage
+            name: uriIsPackage || uriIsDart
                 ? uri!.toString()
                 : sourcePath != null
                     ? convertToRelativePath(sourcePath)
@@ -455,22 +452,6 @@
     );
   }
 
-  /// Converts the source URI from the VM to a file path.
-  ///
-  /// This is required so that when the user stops (or navigates via a stack
-  /// frame) we open the same file on their local disk. If we downloaded the
-  /// source from the VM, they would end up seeing two copies of files (and they
-  /// would each have their own breakpoints) which can be confusing.
-  Future<String?> convertVmUriToSourcePath(Uri uri) async {
-    if (uri.isScheme('file')) {
-      return uri.toFilePath();
-    } else if (uri.isScheme('package')) {
-      return resolvePackageUri(uri);
-    } else {
-      return null;
-    }
-  }
-
   /// Whether [kind] is a simple kind, and does not need to be mapped to a variable.
   bool isSimpleKind(String? kind) {
     return kind == 'String' ||
@@ -482,19 +463,6 @@
         kind == 'Closure';
   }
 
-  /// Resolves a `package: URI` to the real underlying source path.
-  ///
-  /// Returns `null` if no mapping was possible, for example if the package is
-  /// not in the package mapping file.
-  String? resolvePackageUri(Uri uri) {
-    // TODO(dantup): Replace this implementation with one that calls the VM
-    //   Service once https://github.com/dart-lang/sdk/issues/45530 is done.
-    // This implementation makes assumptions about the package file being used
-    // that might not be correct (for example if the user uses the --packages
-    // flag).
-    return packageConfig.resolve(uri)?.toFilePath();
-  }
-
   /// Invokes the toString() method on a [vm.InstanceRef] and converts the
   /// response to a user-friendly display string.
   ///
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index e323748..ef5124d 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -16,7 +16,6 @@
   devtools_shared: ^2.3.0
   json_rpc_2: ^3.0.0
   meta: ^1.1.8
-  package_config: ^2.0.0
   path: ^1.8.0
   pedantic: ^1.7.0
   shelf: ^1.0.0
diff --git a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
index 8aee55c..03475cb 100644
--- a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
+++ b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
@@ -2,6 +2,9 @@
 // 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:io';
+
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
 import 'test_client.dart';
@@ -24,6 +27,18 @@
       await client.hitBreakpoint(testFile, breakpointLine);
     });
 
+    test('stops at a line breakpoint in the SDK set via local sources',
+        () async {
+      final client = dap.client;
+      final testFile = dap.createTestFile(simpleBreakpointProgram);
+
+      // Add the breakpoint to the first line inside the SDK's print function.
+      final sdkFile = File(path.join(sdkRoot, 'lib', 'core', 'print.dart'));
+      final breakpointLine = lineWith(sdkFile, 'print(Object? object) {') + 1;
+
+      await client.hitBreakpoint(sdkFile, breakpointLine, entryFile: testFile);
+    });
+
     test('stops at a line breakpoint and can be resumed', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
diff --git a/pkg/dds/test/dap/integration/debug_test.dart b/pkg/dds/test/dap/integration/debug_test.dart
index d6df8e6..6655b58 100644
--- a/pkg/dds/test/dap/integration/debug_test.dart
+++ b/pkg/dds/test/dap/integration/debug_test.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import 'package:dds/src/dap/protocol_generated.dart';
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
 import 'test_client.dart';
@@ -159,6 +160,46 @@
       // Source code should contain the implementation/signature of print().
       final source = await client.getValidSource(topFrame.source!);
       expect(source.content, contains('void print(Object? object) {'));
+      // Skipped because this test is not currently valid as source for print
+      // is mapped to local sources.
+    }, skip: true);
+
+    test('can map SDK source code to a local path', () async {
+      final client = dap.client;
+      final testFile = dap.createTestFile(simpleBreakpointProgram);
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+
+      // Hit the initial breakpoint.
+      final stop = await dap.client.hitBreakpoint(
+        testFile,
+        breakpointLine,
+        launch: () => client.launch(
+          testFile.path,
+          debugSdkLibraries: true,
+        ),
+      );
+
+      // Step in to go into print.
+      final responses = await Future.wait([
+        client.expectStop('step', sourceName: 'dart:core/print.dart'),
+        client.stepIn(stop.threadId!),
+      ], eagerError: true);
+      final stopResponse = responses.first as StoppedEventBody;
+
+      // Fetch the top stack frame (which should be inside print).
+      final stack = await client.getValidStack(
+        stopResponse.threadId!,
+        startFrame: 0,
+        numFrames: 1,
+      );
+      final topFrame = stack.stackFrames.first;
+
+      // SDK sources that have been mapped have no sourceReference but a path.
+      expect(
+        topFrame.source!.path,
+        equals(path.join(sdkRoot, 'lib', 'core', 'print.dart')),
+      );
+      expect(topFrame.source!.sourceReference, isNull);
     });
 
     test('can shutdown during startup', () async {
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index 55f21c0..d960bec 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -488,11 +488,13 @@
   Future<StoppedEventBody> hitBreakpoint(
     File file,
     int line, {
+    File? entryFile,
     String? condition,
     String? cwd,
     List<String>? args,
     Future<Response> Function()? launch,
   }) async {
+    entryFile ??= file;
     final stop = expectStop('breakpoint', file: file, line: line);
 
     await Future.wait([
@@ -503,7 +505,7 @@
           breakpoints: [SourceBreakpoint(line: line, condition: condition)],
         ),
       ),
-      launch?.call() ?? this.launch(file.path, cwd: cwd, args: args),
+      launch?.call() ?? this.launch(entryFile.path, cwd: cwd, args: args),
     ], eagerError: true);
 
     return stop;
diff --git a/pkg/dds/test/dap/integration/test_support.dart b/pkg/dds/test/dap/integration/test_support.dart
index 9201798..d4c2a8b 100644
--- a/pkg/dds/test/dap/integration/test_support.dart
+++ b/pkg/dds/test/dap/integration/test_support.dart
@@ -43,6 +43,9 @@
 /// by the VM when not using --write-service-info.
 final vmServiceBannerPattern = RegExp(r'Observatory listening on ([^\s]+)\s');
 
+/// The root of the SDK containing the current running VM.
+final sdkRoot = path.dirname(path.dirname(Platform.resolvedExecutable));
+
 /// Expects the lines in [actual] to match the relevant matcher in [expected],
 /// ignoring differences in line endings and trailing whitespace.
 void expectLines(String actual, List<Object> expected) {
diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc
index 7425dd6..e18e2a3 100644
--- a/runtime/vm/dart.cc
+++ b/runtime/vm/dart.cc
@@ -7,6 +7,7 @@
 
 #include "vm/dart.h"
 
+#include "platform/thread_sanitizer.h"
 #include "vm/app_snapshot.h"
 #include "vm/code_observers.h"
 #include "vm/compiler/runtime_offsets_extracted.h"
@@ -49,6 +50,13 @@
 #include "vm/virtual_memory.h"
 #include "vm/zone.h"
 
+#if defined(USING_THREAD_SANITIZER)
+// TODO(https://github.com/dart-lang/sdk/issues/46699): Remove.
+// TODO(https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=83298): Remove.
+#include "unicode/uchar.h"
+#include "unicode/uniset.h"
+#endif
+
 namespace dart {
 
 DECLARE_FLAG(bool, print_class_table);
@@ -264,6 +272,13 @@
                      Dart_CodeObserver* observer,
                      Dart_PostTaskCallback post_task,
                      void* post_task_data) {
+#if defined(USING_THREAD_SANITIZER)
+  // Trigger lazy initialization in ICU to avoid spurious TSAN warning.
+  // TODO(https://github.com/dart-lang/sdk/issues/46699): Remove.
+  // TODO(https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=83298): Remove.
+  u_getPropertyValueEnum(UCHAR_SCRIPT, "foo");
+#endif
+
   CheckOffsets();
 
   if (!Flags::Initialized()) {
diff --git a/tools/VERSION b/tools/VERSION
index e453e6c..536a2a3 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 63
+PRERELEASE 64
 PRERELEASE_PATCH 0
\ No newline at end of file