From 04d2cf557577bc7d5dc7e4fe280d2e6988da1538 Mon Sep 17 00:00:00 2001 From: ksaito Date: Tue, 3 Jun 2025 22:05:22 +0900 Subject: [PATCH 1/4] Fix methods defined with invalid encoding are not displayed in completion --- lib/irb/completion.rb | 11 ++++++++-- test/irb/test_completion.rb | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 3795cea43..63d6f3805 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -193,8 +193,15 @@ def completion_candidates(preposing, target, postposing, bind:) # It doesn't make sense to propose commands with other preposing commands = [] unless preposing.empty? - - completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + @encoding_warning_shown ||= false + + completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map do |i| + i.encode(Encoding.default_external) + rescue Encoding::UndefinedConversionError + warn "Warning: Invalid encoding in method name '#{i}'. can't be converted to the locale #{Encoding.default_external}." unless @encoding_warning_shown + @encoding_warning_shown = true + nil + end.compact commands | completion_data end diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index c9a0eafa3..2f7e1e558 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -314,4 +314,47 @@ def test_retrieve_completion_data assert_equal(IRB::InputCompletor.retrieve_completion_data('a.abs', bind: bind, doc_namespace: true), 'Integer.abs') end end + + class InvalidEncodingMethodTest < TestCase + def test_invalid_encoding_method_warning + if RUBY_ENGINE == 'truffleruby' + omit "TruffleRuby does not support invalid encoding methods." + end + + invalid_method_name = "b\xff".force_encoding(Encoding::ASCII_8BIT) + + test_obj = Object.new + test_obj.define_singleton_method(invalid_method_name) {} + test_bind = test_obj.instance_eval { binding } + + original_stderr = $stderr + original_encoding = Encoding.default_external + original_verbose = $VERBOSE + + begin + stderr_buffer = StringIO.new + $stderr = stderr_buffer + $VERBOSE = nil + Encoding.default_external = Encoding::UTF_8 + $VERBOSE = original_verbose + + completor = IRB::RegexpCompletor.new + result = completor.completion_candidates('', 'b', '', bind: test_bind) + + assert_equal("Warning: Invalid encoding in method name 'b\xff'. can't be converted to the locale UTF-8.\n".force_encoding(Encoding::ASCII_8BIT), stderr_buffer.string.force_encoding(Encoding::ASCII_8BIT)) + assert_not_include(result, nil) + + stderr_buffer.truncate(0) + stderr_buffer.rewind + stderr_buffer = StringIO.new + + completor.completion_candidates('', 'b', '', bind: test_bind) + assert_empty(stderr_buffer.string) + ensure + $stderr = original_stderr + Encoding.default_external = original_encoding + $VERBOSE = original_verbose + end + end + end end From f8ed14af5c0a9984e21df23337056fddedbd3b1b Mon Sep 17 00:00:00 2001 From: ksaito Date: Sat, 28 Jun 2025 13:58:24 +0900 Subject: [PATCH 2/4] Ignore invalid encoding in completion data retrieval --- lib/irb/completion.rb | 4 +--- test/irb/test_completion.rb | 25 ++++++------------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 63d6f3805..77fe676ca 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -193,13 +193,11 @@ def completion_candidates(preposing, target, postposing, bind:) # It doesn't make sense to propose commands with other preposing commands = [] unless preposing.empty? - @encoding_warning_shown ||= false completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map do |i| i.encode(Encoding.default_external) rescue Encoding::UndefinedConversionError - warn "Warning: Invalid encoding in method name '#{i}'. can't be converted to the locale #{Encoding.default_external}." unless @encoding_warning_shown - @encoding_warning_shown = true + # If the string cannot be converted, we just ignore it nil end.compact commands | completion_data diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 2f7e1e558..3bdf920da 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -316,7 +316,7 @@ def test_retrieve_completion_data end class InvalidEncodingMethodTest < TestCase - def test_invalid_encoding_method_warning + def test_regexp_completor_handles_encoding_errors_gracefully if RUBY_ENGINE == 'truffleruby' omit "TruffleRuby does not support invalid encoding methods." end @@ -327,33 +327,20 @@ def test_invalid_encoding_method_warning test_obj.define_singleton_method(invalid_method_name) {} test_bind = test_obj.instance_eval { binding } - original_stderr = $stderr original_encoding = Encoding.default_external - original_verbose = $VERBOSE begin - stderr_buffer = StringIO.new - $stderr = stderr_buffer - $VERBOSE = nil Encoding.default_external = Encoding::UTF_8 - $VERBOSE = original_verbose completor = IRB::RegexpCompletor.new - result = completor.completion_candidates('', 'b', '', bind: test_bind) - assert_equal("Warning: Invalid encoding in method name 'b\xff'. can't be converted to the locale UTF-8.\n".force_encoding(Encoding::ASCII_8BIT), stderr_buffer.string.force_encoding(Encoding::ASCII_8BIT)) - assert_not_include(result, nil) - - stderr_buffer.truncate(0) - stderr_buffer.rewind - stderr_buffer = StringIO.new - - completor.completion_candidates('', 'b', '', bind: test_bind) - assert_empty(stderr_buffer.string) + assert_nothing_raised do + result = completor.completion_candidates('', 'b', '', bind: test_bind) + assert_include(result, 'block_given?') + assert_not_include(result, nil) + end ensure - $stderr = original_stderr Encoding.default_external = original_encoding - $VERBOSE = original_verbose end end end From 4897ccf15bca749f2ae943af95fc75efc662e572 Mon Sep 17 00:00:00 2001 From: ksaito Date: Sat, 28 Jun 2025 14:35:41 +0900 Subject: [PATCH 3/4] Handle Encoding::UndefinedConversionError gracefully in completion candidates --- lib/irb/completion.rb | 9 ++++++++- test/irb/test_type_completor.rb | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 77fe676ca..8c981e0e2 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -107,7 +107,14 @@ def completion_candidates(preposing, target, _postposing, bind:) return commands unless result - commands | result.completion_candidates.map { target + _1 } + encoded_candidates = result.completion_candidates.filter_map do |i| + encoded = i.encode(Encoding.default_external) + target + encoded + rescue Encoding::UndefinedConversionError + # If the string cannot be converted, we just ignore it + nil + end + commands | encoded_candidates end def doc_namespace(preposing, matched, _postposing, bind:) diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb index 3d0e25d19..6389bf1f6 100644 --- a/test/irb/test_type_completor.rb +++ b/test/irb/test_type_completor.rb @@ -80,6 +80,28 @@ def test_command_completion assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source') assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') end + + def test_type_completor_handles_encoding_errors_gracefully + invalid_method_name = "b\xff".dup.force_encoding(Encoding::ASCII_8BIT) + + test_obj = Object.new + test_obj.define_singleton_method(invalid_method_name) {} + test_bind = test_obj.instance_eval { binding } + + original_encoding = Encoding.default_external + + begin + Encoding.default_external = Encoding::UTF_8 + + assert_nothing_raised do + result = @completor.completion_candidates('', 'b', '', bind: test_bind) + assert_include(result, 'block_given?') + assert_not_include(result, nil) + end + ensure + Encoding.default_external = original_encoding + end + end end class TypeCompletorIntegrationTest < IntegrationTestCase From 8a1a81b129a5ec5f0c198c5ed915cc4225a830a3 Mon Sep 17 00:00:00 2001 From: ksaito Date: Sat, 28 Jun 2025 14:39:10 +0900 Subject: [PATCH 4/4] Replace map and compact with filter_map --- lib/irb/completion.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 8c981e0e2..e3137bc36 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -201,12 +201,12 @@ def completion_candidates(preposing, target, postposing, bind:) # It doesn't make sense to propose commands with other preposing commands = [] unless preposing.empty? - completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map do |i| + completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.filter_map do |i| i.encode(Encoding.default_external) rescue Encoding::UndefinedConversionError # If the string cannot be converted, we just ignore it nil - end.compact + end commands | completion_data end