From da1885a8729b8a5497eeda73163f1009fe04902d Mon Sep 17 00:00:00 2001 From: Mariano Simone Date: Wed, 21 Sep 2022 12:38:14 -0600 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20Support=20displaying=20varia?= =?UTF-8?q?bles=20with=20non-unicode=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/debug/server_dap.rb | 18 +++++++- test/protocol/binary_data_dap_test.rb | 59 +++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 test/protocol/binary_data_dap_test.rb diff --git a/lib/debug/server_dap.rb b/lib/debug/server_dap.rb index f30d28ae6..587782ddf 100644 --- a/lib/debug/server_dap.rb +++ b/lib/debug/server_dap.rb @@ -687,7 +687,23 @@ 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 + value = DEBUGGER__.safe_inspect obj, short: true, max_length: 4 * 1024 + + # Given that this is going to be transmitted in a JSON string, it needs to be unicode-encoded + # (and "UTF-8" is the default). + if value.encoding != Encoding::UTF_8 + # If the string we got is frozen, we need to make a copy first + value = value.dup if value.frozen? + value.force_encoding(Encoding::UTF_8) + end + + if value.valid_encoding? + value + else + # If a variable contains non-unicode data, at least we can send it partially and signal that + # the encoding was unexpected. + "[Invalid encoding] #{value.encode("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..e01778192 --- /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: /\[Invalid encoding\] /, 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 From e3310d827261005e4e719db7e3a5909b7c900b10 Mon Sep 17 00:00:00 2001 From: Mariano Simone Date: Mon, 24 Oct 2022 15:12:37 -0600 Subject: [PATCH 2/3] Update lib/debug/server_dap.rb Co-authored-by: Koichi Sasada --- lib/debug/server_dap.rb | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/debug/server_dap.rb b/lib/debug/server_dap.rb index 587782ddf..eb578615f 100644 --- a/lib/debug/server_dap.rb +++ b/lib/debug/server_dap.rb @@ -687,22 +687,12 @@ def register_vars vars, tid class ThreadClient def value_inspect obj # TODO: max length should be configuarable? - value = DEBUGGER__.safe_inspect obj, short: true, max_length: 4 * 1024 - - # Given that this is going to be transmitted in a JSON string, it needs to be unicode-encoded - # (and "UTF-8" is the default). - if value.encoding != Encoding::UTF_8 - # If the string we got is frozen, we need to make a copy first - value = value.dup if value.frozen? - value.force_encoding(Encoding::UTF_8) - end + str = DEBUGGER__.safe_inspect obj, short: true, max_length: 4 * 1024 - if value.valid_encoding? - value + if str.encoding == Encoding::UTF_8 + str.scrub else - # If a variable contains non-unicode data, at least we can send it partially and signal that - # the encoding was unexpected. - "[Invalid encoding] #{value.encode("UTF-8", invalid: :replace, undef: :replace)}" + str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) end end From 308855ab90b8693a1306fb494f045c111132965d Mon Sep 17 00:00:00 2001 From: Mariano Simone Date: Mon, 24 Oct 2022 15:32:38 -0600 Subject: [PATCH 3/3] Fix tests --- test/protocol/binary_data_dap_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/protocol/binary_data_dap_test.rb b/test/protocol/binary_data_dap_test.rb index e01778192..c874f1120 100644 --- a/test/protocol/binary_data_dap_test.rb +++ b/test/protocol/binary_data_dap_test.rb @@ -25,7 +25,7 @@ def test_binary_data_gets_encoded assert_locals_result( [ { name: '%self', value: 'main', type: 'Object' }, - { name: 'with_binary_data', value: /\[Invalid encoding\] /, type: 'PassthroughInspect' } + { name: 'with_binary_data', value: [8, 200, 1].pack('CCC').encode(Encoding::UTF_8, invalid: :replace, undef: :replace), type: 'PassthroughInspect' } ] ) req_terminate_debuggee