Version 2.18.0-263.0.dev

Merge commit '2a440e14f1af4dbc1fb36ad3583bf485c0235d6f' into 'dev'
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index b028eb1..bbfd626 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -1815,6 +1815,33 @@
     }
   }
 
+  /// Helper to convert to InstanceRef to a complete untruncated String,
+  /// handling [vm.InstanceKind.kNull] which is the type for the unused fields
+  /// of a log event.
+  Future<String?> getFullString(ThreadInfo thread, vm.InstanceRef? ref) async {
+    if (ref == null || ref.kind == vm.InstanceKind.kNull) {
+      return null;
+    }
+    return _converter
+        .convertVmInstanceRefToDisplayString(
+      thread,
+      ref,
+      // Always allow calling toString() here as the user expects the full
+      // string they logged regardless of the evaluateToStringInDebugViews
+      // setting.
+      allowCallingToString: true,
+      allowTruncatedValue: false,
+      includeQuotesAroundString: false,
+    )
+        .catchError((e) {
+      // Fetching strings from the server may throw if they have been
+      // collected since (for example if a Hot Restart occurs while
+      // we're running this). Log the error and just return null so
+      // nothing is shown.
+      logger?.call('$e');
+    });
+  }
+
   /// Handles a dart:developer log() event, sending output to the client.
   @protected
   @mustCallSuper
@@ -1825,40 +1852,13 @@
       return;
     }
 
-    /// Helper to convert to InstanceRef to a String, taking into account
-    /// [vm.InstanceKind.kNull] which is the type for the unused fields of a
-    /// log event.
-    Future<String?> asString(vm.InstanceRef? ref) async {
-      if (ref == null || ref.kind == vm.InstanceKind.kNull) {
-        return null;
-      }
-      return _converter
-          .convertVmInstanceRefToDisplayString(
-        thread,
-        ref,
-        // Always allow calling toString() here as the user expects the full
-        // string they logged regardless of the evaluateToStringInDebugViews
-        // setting.
-        allowCallingToString: true,
-        allowTruncatedValue: false,
-        includeQuotesAroundString: false,
-      )
-          .catchError((e) {
-        // Fetching strings from the server may throw if they have been
-        // collected since (for example if a Hot Restart occurs while
-        // we're running this). Log the error and just return null so
-        // nothing is shown.
-        logger?.call('$e');
-      });
-    }
-
-    var loggerName = await asString(record.loggerName);
+    var loggerName = await getFullString(thread, record.loggerName);
     if (loggerName?.isEmpty ?? true) {
       loggerName = 'log';
     }
-    final message = await asString(record.message);
-    final error = await asString(record.error);
-    final stack = await asString(record.stackTrace);
+    final message = await getFullString(thread, record.message);
+    final error = await getFullString(thread, record.error);
+    final stack = await getFullString(thread, record.stackTrace);
 
     final prefix = '[$loggerName] ';
 
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index 443d148..a926025 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -501,14 +501,20 @@
       // If we stopped at an exception, capture the exception instance so we
       // can add a variables scope for it so it can be examined.
       final exception = event.exception;
+      String? text;
       if (exception != null) {
         _adapter.storeEvaluateName(exception, threadExceptionExpression);
         thread.exceptionReference = thread.storeData(exception);
+        text = await _adapter.getFullString(thread, exception);
       }
 
       // Notify the client.
       _adapter.sendEvent(
-        StoppedEventBody(reason: reason, threadId: thread.threadId),
+        StoppedEventBody(
+          reason: reason,
+          threadId: thread.threadId,
+          text: text,
+        ),
       );
     }
   }
diff --git a/pkg/dds/test/dap/integration/debug_exceptions_test.dart b/pkg/dds/test/dap/integration/debug_exceptions_test.dart
index 157f136..50d2647 100644
--- a/pkg/dds/test/dap/integration/debug_exceptions_test.dart
+++ b/pkg/dds/test/dap/integration/debug_exceptions_test.dart
@@ -30,7 +30,7 @@
           .join();
       expectLinesStartWith(output, [
         'Unhandled exception:',
-        'error',
+        'Exception: error text',
       ]);
     });
 
@@ -42,6 +42,7 @@
       await client.pauseOnException(
         testFile,
         exceptionPauseMode: 'Unhandled',
+        expectText: '_Exception (Exception: error text)',
       );
     });
 
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index 1dc5b9e..68ca595 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -546,9 +546,10 @@
   Future<StoppedEventBody> pauseOnException(
     File file, {
     String? exceptionPauseMode, // All, Unhandled, None
+    String? expectText,
     Future<Response> Function()? launch,
   }) async {
-    final stop = expectStop('exception', file: file);
+    final stopFuture = expectStop('exception', file: file);
 
     await Future.wait([
       initialize(),
@@ -560,6 +561,10 @@
       launch?.call() ?? this.launch(file.path),
     ], eagerError: true);
 
+    final stop = await stopFuture;
+    if (expectText != null) {
+      expect(stop.text, expectText);
+    }
     return stop;
   }
 
diff --git a/pkg/dds/test/dap/integration/test_scripts.dart b/pkg/dds/test/dap/integration/test_scripts.dart
index 84eea06..7abf38f 100644
--- a/pkg/dds/test/dap/integration/test_scripts.dart
+++ b/pkg/dds/test/dap/integration/test_scripts.dart
@@ -186,7 +186,7 @@
 /// A simple Dart script that throws in user code.
 const simpleThrowingProgram = r'''
   void main(List<String> args) async {
-    throw 'error';
+    throw Exception('error text');
   }
 ''';
 
diff --git a/tools/VERSION b/tools/VERSION
index d82a1f5..45baf3f 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 262
+PRERELEASE 263
 PRERELEASE_PATCH 0
\ No newline at end of file