From 578dfaeec195a451b0edfb8a01c47cc5495c243f Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Wed, 8 Jul 2026 11:43:00 +0200 Subject: [PATCH] Handle `nil` `backtrace_locations` in `generate_snippet` --- lib/error_highlight/core_ext.rb | 2 +- test/test_error_highlight.rb | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb index c3354f4..c82857f 100644 --- a/lib/error_highlight/core_ext.rb +++ b/lib/error_highlight/core_ext.rb @@ -5,7 +5,7 @@ module CoreExt private def generate_snippet if ArgumentError === self && message =~ /\A(?:wrong number of arguments|missing keyword[s]?|unknown keyword[s]?|no keywords accepted)\b/ locs = self.backtrace_locations - return "" if locs.size < 2 + return "" if locs.nil? || locs.size < 2 callee_loc, caller_loc = locs callee_spot = ErrorHighlight.spot(self, backtrace_location: callee_loc, point_type: :name) caller_spot = ErrorHighlight.spot(self, backtrace_location: caller_loc, point_type: :name) diff --git a/test/test_error_highlight.rb b/test/test_error_highlight.rb index 5de70f5..76db103 100644 --- a/test/test_error_highlight.rb +++ b/test/test_error_highlight.rb @@ -1815,6 +1815,30 @@ def test_detailed_message_does_not_raise_when_argument_error_is_rewrapped end end + def test_detailed_message_does_not_raise_when_backtrace_locations_is_nil + # This reproduces a real-world crash: when an exception crosses a process + # boundary via Marshal, `backtrace` survives as strings but + # `backtrace_locations` becomes nil. A keyword ArgumentError then crashed + # CoreExt#generate_snippet with `undefined method 'size' for nil`. + begin + def_with_required_keyword + rescue ArgumentError => original + exc = Marshal.load(Marshal.dump(original)) + end + + assert_nil exc.backtrace_locations + + msg = nil + assert_nothing_raised do + msg = exc.detailed_message(highlight: false) + end + assert_match("missing keyword", msg) + + assert_nothing_raised do + exc.full_message(highlight: false) + end + end + private def find_node_by_id(node, node_id)