diff --git a/pkg/dds/lib/src/dap/protocol_converter.dart b/pkg/dds/lib/src/dap/protocol_converter.dart index dacb1a3e2f0..863026f3a99 100644 --- a/pkg/dds/lib/src/dap/protocol_converter.dart +++ b/pkg/dds/lib/src/dap/protocol_converter.dart @@ -369,6 +369,11 @@ class ProtocolConverter { allowCallingToString: allowCallingToString, format: format, ); + } else if (response is vm.ErrorRef) { + final errorMessage = response.message; + return errorMessage != null + ? _adapter.extractUnhandledExceptionMessage(errorMessage) + : response.kind ?? ''; } else if (response is vm.Sentinel) { return ''; } else { diff --git a/pkg/dds/test/dap/integration/debug_variables_test.dart b/pkg/dds/test/dap/integration/debug_variables_test.dart index a8992bdf569..b221d4dfe6b 100644 --- a/pkg/dds/test/dap/integration/debug_variables_test.dart +++ b/pkg/dds/test/dap/integration/debug_variables_test.dart @@ -577,6 +577,38 @@ void main() { ); }); + test('handles errors in toString() on custom classes', () async { + final client = dap.client; + final testFile = dap.createTestFile(''' +class Foo { + toString() => throw UnimplementedError('NYI!'); +} + +void main() { + final myVariable = Foo(); + print('Hello!'); $breakpointMarker +} + '''); + final breakpointLine = lineWith(testFile, breakpointMarker); + + final stop = await client.hitBreakpoint( + testFile, + breakpointLine, + launch: () => client.launch( + testFile.path, + evaluateToStringInDebugViews: true, + ), + ); + + await client.expectScopeVariables( + await client.getTopFrameId(stop.threadId!), + 'Locals', + r''' + myVariable: Foo (UnimplementedError: NYI!), eval: myVariable + ''', + ); + }); + test('does not use toString() result if "Instance of Foo"', () async { // When evaluateToStringInDebugViews=true, we should discard the result of // calling toString() when it's just 'Instance of Foo' because we're already