Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Support displaying variables with non-unicode data #758

Merged
merged 3 commits into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion lib/debug/server_dap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions test/protocol/binary_data_dap_test.rb
Original file line number Diff line number Diff line change
@@ -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