Skip to content

Commit 2759a0a

Browse files
committed
fix: don't crash on utf18 autocompletion
fixes #52
1 parent 3893f18 commit 2759a0a

File tree

2 files changed

+30
-5
lines changed

2 files changed

+30
-5
lines changed

lib/irb/completion.rb

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:)
280280
rescue EncodingError
281281
# ignore
282282
end
283-
candidates.grep(/^#{Regexp.quote(sym)}/)
283+
safe_grep(candidates, /^#{Regexp.quote(sym)}/)
284284
end
285285
when /^::([A-Z][^:\.\(\)]*)$/
286286
# Absolute Constant or class methods
@@ -291,7 +291,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:)
291291
if doc_namespace
292292
candidates.find { |i| i == receiver }
293293
else
294-
candidates.grep(/^#{Regexp.quote(receiver)}/).collect{|e| "::" + e}
294+
safe_grep(candidates, /^#{Regexp.quote(receiver)}/).collect{|e| "::" + e}
295295
end
296296

297297
when /^([A-Z].*)::([^:.]*)$/
@@ -378,7 +378,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:)
378378
if doc_namespace
379379
all_gvars.find{ |i| i == gvar }
380380
else
381-
all_gvars.grep(Regexp.new(Regexp.quote(gvar)))
381+
safe_grep(all_gvars, Regexp.new(Regexp.quote(gvar)))
382382
end
383383

384384
when /^([^.:"].*)(\.|::)([^.]*)$/
@@ -453,16 +453,28 @@ def retrieve_completion_data(input, bind:, doc_namespace:)
453453
else
454454
candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
455455
candidates |= RubyLex::RESERVED_WORDS.map(&:to_s)
456-
candidates.grep(/^#{Regexp.quote(input)}/).sort
456+
safe_grep(candidates, /^#{Regexp.quote(input)}/).sort
457457
end
458458
end
459459
end
460460

461461
# Set of available operators in Ruby
462462
Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~]
463463

464+
def safe_grep(candidates, pattern)
465+
target_encoding = Encoding.default_external
466+
candidates.filter_map do |candidate|
467+
converted = candidate.encoding == target_encoding ? candidate : candidate.encode(target_encoding)
468+
469+
converted if pattern.match?(converted)
470+
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError, Encoding::CompatibilityError, EncodingError
471+
# Skip candidates that cannot be converted to the target encoding
472+
nil
473+
end
474+
end
475+
464476
def select_message(receiver, message, candidates, sep = ".")
465-
candidates.grep(/^#{Regexp.quote(message)}/).collect do |e|
477+
safe_grep(candidates, /^#{Regexp.quote(message)}/).collect do |e|
466478
case e
467479
when /^[a-zA-Z_]/
468480
receiver + sep + e

test/irb/test_completion.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,5 +343,18 @@ def test_regexp_completor_handles_encoding_errors_gracefully
343343
Encoding.default_external = original_encoding
344344
end
345345
end
346+
347+
def test_utf16_method_name_does_not_crash
348+
# Reproduces issue #52: https://github.com/ruby/irb/issues/52
349+
method_name = "a".encode(Encoding::UTF_16)
350+
test_obj = Object.new
351+
test_obj.define_singleton_method(method_name) {}
352+
test_bind = test_obj.instance_eval { binding }
353+
354+
completor = IRB::RegexpCompletor.new
355+
assert_nothing_raised do
356+
completor.completion_candidates('', 'a', '', bind: test_bind)
357+
end
358+
end
346359
end
347360
end

0 commit comments

Comments
 (0)