diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 3795cea43..e3137bc36 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:) @@ -194,7 +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{ |i| i.encode(Encoding.default_external) } + 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 commands | completion_data end diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index c9a0eafa3..3bdf920da 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -314,4 +314,34 @@ 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_regexp_completor_handles_encoding_errors_gracefully + 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_encoding = Encoding.default_external + + begin + Encoding.default_external = Encoding::UTF_8 + + completor = IRB::RegexpCompletor.new + + 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 end 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