diff --git a/lib/debug/server_dap.rb b/lib/debug/server_dap.rb index f30d28ae6..eb578615f 100644 --- a/lib/debug/server_dap.rb +++ b/lib/debug/server_dap.rb @@ -687,7 +687,13 @@ def register_vars vars, tid class ThreadClient def value_inspect obj # TODO: max length should be configuarable? - DEBUGGER__.safe_inspect obj, short: true, max_length: 4 * 1024 + str = DEBUGGER__.safe_inspect obj, short: true, max_length: 4 * 1024 + + if str.encoding == Encoding::UTF_8 + str.scrub + else + str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) + end end def process_dap args diff --git a/test/protocol/binary_data_dap_test.rb b/test/protocol/binary_data_dap_test.rb new file mode 100644 index 000000000..c874f1120 --- /dev/null +++ b/test/protocol/binary_data_dap_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require_relative '../support/protocol_test_case' + +module DEBUGGER__ + class BinaryDataDAPTest < ProtocolTestCase + def test_binary_data_gets_encoded + program = <<~RUBY + 1| class PassthroughInspect + 2| def initialize(data) + 3| @data = data + 4| end + 5| + 6| def inspect + 7| @data + 8| end + 9| end + 10| + 11| with_binary_data = PassthroughInspect.new([8, 200, 1].pack('CCC')) + 12| with_binary_data + RUBY + run_protocol_scenario(program, cdp: false) do + req_add_breakpoint 12 + req_continue + assert_locals_result( + [ + { name: '%self', value: 'main', type: 'Object' }, + { name: 'with_binary_data', value: [8, 200, 1].pack('CCC').encode(Encoding::UTF_8, invalid: :replace, undef: :replace), type: 'PassthroughInspect' } + ] + ) + req_terminate_debuggee + end + end + + def test_frozen_strings_are_supported + # When `inspect` fails, `DEBUGGER__.safe_inspect` returns a frozen error message + # Just returning a frozen string wouldn't work, as `DEBUGGER__.safe_inspect` constructs + # the return value with a buffer. + program = <<~RUBY + 1| class Uninspectable + 2| def inspect; raise 'error'; end + 3| end + 4| broken_inspect = Uninspectable.new + 5| broken_inspect + RUBY + run_protocol_scenario(program, cdp: false) do + req_add_breakpoint 5 + req_continue + assert_locals_result( + [ + { name: '%self', value: 'main', type: 'Object' }, + { name: 'broken_inspect', value: /#inspect raises/, type: 'Uninspectable' } + ] + ) + req_terminate_debuggee + end + end + end +end