Skip to content

Handle nil backtrace_locations in generate_snippet#80

Merged
mame merged 1 commit into
ruby:masterfrom
marcoroth:fix-nil-backtrace-locations
Jul 8, 2026
Merged

Handle nil backtrace_locations in generate_snippet#80
mame merged 1 commit into
ruby:masterfrom
marcoroth:fix-nil-backtrace-locations

Conversation

@marcoroth

Copy link
Copy Markdown
Contributor

This pull request fixes a crash in ErrorHighlight::CoreExt#generate_snippet when an exception has a backtrace but no backtrace_locations.

Exception#backtrace_locations can be nil even when backtrace is set, most commonly when an exception is rebuilt via Marshal or set_backtrace.

A real-world case: the parallel gem marshals exceptions from forked workers back to the parent process, where backtrace survives as strings but backtrace_locations is nil.

When an ArgumentError message matches one of the keyword-argument patterns (wrong number of arguments, unknown keyword, …), generate_snippet calls locs.size without checking for nil first. Displaying such an exception then raises:

error_highlight/core_ext.rb:8:in 'ErrorHighlight::CoreExt#generate_snippet':
  undefined method 'size' for nil (NoMethodError)

Since this fires inside detailed_message, it masks the original exception entirely, e.g. Rake's error display crashes and the user never sees the real error (example CI failure):

/opt/hostedtoolcache/Ruby/4.0.1/x64/lib/ruby/4.0.0/error_highlight/core_ext.rb:8:in 'ErrorHighlight::CoreExt#generate_snippet': undefined method 'size' for nil (NoMethodError)

        return "" if locs.size < 2
                         ^^^^^
	from /opt/hostedtoolcache/Ruby/4.0.1/x64/lib/ruby/4.0.0/error_highlight/core_ext.rb:43:in 'ErrorHighlight::CoreExt#detailed_message'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/lib/rake/application.rb:264:in 'Rake::Application#display_exception_message_details'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/lib/rake/application.rb:240:in 'Rake::Application#display_exception_details'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/lib/rake/application.rb:231:in 'Rake::Application#display_error_message'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/lib/rake/application.rb:218:in 'Rake::Application#standard_exception_handling'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/commands/rake/rake_command.rb:28:in 'block in Rails::Command::RakeCommand.perform'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/commands/rake/rake_command.rb:49:in 'block in Rails::Command::RakeCommand.with_rake'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/lib/rake/rake_module.rb:59:in 'Rake.with_application'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/commands/rake/rake_command.rb:46:in 'Rails::Command::RakeCommand.with_rake'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/commands/rake/rake_command.rb:20:in 'Rails::Command::RakeCommand.perform'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/command.rb:150:in 'Rails::Command.invoke_rake'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/command.rb:67:in 'block in Rails::Command.invoke'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/command.rb:143:in 'Rails::Command.with_argv'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/command.rb:63:in 'Rails::Command.invoke'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/bundler/gems/rails-fac8810bcdec/railties/lib/rails/commands.rb:18:in '<main>'
	from /opt/hostedtoolcache/Ruby/4.0.1/x64/lib/ruby/4.0.0/bundled_gems.rb:60:in 'Kernel.require'
	from /opt/hostedtoolcache/Ruby/4.0.1/x64/lib/ruby/4.0.0/bundled_gems.rb:60:in 'block (2 levels) in Kernel#replace_require'
	from /home/runner/work/rubyevents/rubyevents/vendor/bundle/ruby/4.0.0/gems/bootsnap-1.23.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:33:in 'Kernel#require'
	from bin/rails:4:in '<main>'

Repro:

def explode(file_path:) = nil

begin
  explode(file_path: "x", document: nil)
rescue ArgumentError => e
  Marshal.load(Marshal.dump(e)).detailed_message # => NoMethodError
end

This pull request adds the same nil guard ErrorHighlight.spot already uses in base.rb, plus a regression test using the Marshal round-trip.

marcoroth added a commit to rubyevents/rubyevents that referenced this pull request Jul 8, 2026
@mame mame merged commit 3e223e3 into ruby:master Jul 8, 2026
7 checks passed
@mame

mame commented Jul 8, 2026

Copy link
Copy Markdown
Member

Thank you!

@marcoroth marcoroth deleted the fix-nil-backtrace-locations branch July 8, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants