Skip to content

Fix segfault when Ruby coverage is enabled with Rails 8.1 ERB templates#2541

Merged
joelhawksley merged 3 commits intoViewComponent:mainfrom
george-bit:fix-coverage-segfault
Feb 11, 2026
Merged

Fix segfault when Ruby coverage is enabled with Rails 8.1 ERB templates#2541
joelhawksley merged 3 commits intoViewComponent:mainfrom
george-bit:fix-coverage-segfault

Conversation

@george-bit
Copy link
Contributor

@george-bit george-bit commented Feb 2, 2026

Summary

Fixes segmentation fault when running tests with code coverage enabled (e.g., SimpleCov) on Rails 8.1+ with Ruby 3.4+.

Problem

v4.2.0 introduced negative lineno values (-1) in class_eval to fix incorrect line numbers in stack traces for Rails 8.1 ERB templates. However, this causes a segmentation fault when Ruby's Coverage module is enabled.

Root Cause

Rails 8.1 change: Rails added annotation comments to compiled ERB output (rails/rails#53731), which added an extra line and caused stack trace line numbers to be off by 1.

v4.2.0 fix: Used lineno = -1 in class_eval to compensate for the extra line.

Ruby bug: Negative line numbers in eval/class_eval cause segmentation faults when Ruby's Coverage module is running (bugs.ruby-lang.org/issues/19363). This bug exists in Ruby 3.4 and has been known since 2022.

Result: Rails 8.1 + Ruby 3.4 + view_component 4.2.0 + SimpleCov = crash.

Solution

For file-based templates: When coverage is running AND annotations are enabled, strip the first line (the annotation) from the compiled template source instead of using a negative lineno. This maintains correct line numbers while avoiding the segfault.

For inline templates: No special handling needed—inline templates are defined inside classes, so they always start at line 2+. Subtracting 1 won't result in negative line numbers.

Trade-off

Most users (development, production) get correct line numbers with -1 lineno. CI/coverage users also get correct line numbers because we strip the annotation line instead.

Fixes #2540


Changes from Review Feedback

Based on @joelhawksley's review:

  1. Removed coverage_running? check from Inline class — Inline templates start at line 2+ (defined inside a class), so subtracting 1 will never result in negative line numbers that cause segfaults.

  2. Changed File class approach — Instead of using lineno=1 when coverage is running (which gives incorrect line numbers), the fix now strips the first line of compiled template source when coverage is running AND annotate_rendered_view_with_filenames is enabled. This maintains correct line numbers while avoiding the segfault.

  3. Added regression tests — Two tests verify components compile and render correctly when coverage is running, using actual Coverage.start/Coverage.result rather than mocking.

v4.2.0 introduced a fix for incorrect line numbers in stack traces on
Rails 8.1 by using negative lineno values (-1) in class_eval. However,
negative line numbers cause segmentation faults when Ruby's Coverage
module is enabled (e.g., when using SimpleCov in CI).

This is a known Ruby bug: https://bugs.ruby-lang.org/issues/19363

The fix detects whether coverage is running via Coverage.running? and
adapts the lineno value accordingly:

- Coverage OFF: Use -1 (correct line numbers in stack traces)
- Coverage ON: Use 1 (avoids segfault, line numbers off by ~2)

Note: lineno=0 was also tested but causes the same segfault, so 1 is
the minimum safe value when coverage is enabled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@george-bit
Copy link
Contributor Author

Question: Is there a cleaner solution?

While this fix works, I wanted to raise a question about the underlying approach.

The -1 lineno fix in v4.2.0 appears to compensate for extra lines added by Rails' annotate_rendered_view_with_filenames feature (the <!-- BEGIN ... --> / <!-- END ... --> comments). This creates a chain of workarounds:

  1. Rails 8.1 adds annotations → line numbers off by 1
  2. v4.2.0 uses lineno = -1 → line numbers correct, but segfaults with coverage
  3. This PR detects coverage → avoids segfault, but line numbers off when coverage is on

Alternative approaches worth considering:

  1. Could users just disable annotations? Setting config.action_view.annotate_rendered_view_with_filenames = false might avoid the issue entirely.

  2. Could view_component handle annotations differently? Perhaps strip them from compiled source, or account for them in a way that doesn't require negative line numbers.

  3. Is there something in the Rails 8.1 ERB compilation (beyond annotations) that requires the -1 offset?

I'm not deeply familiar with the history here, so I wanted to ask: is the current approach (adjusting lineno) the right long-term solution, or is there a cleaner way to handle Rails 8.1 compatibility that doesn't require these workarounds?

Copy link
Member

@joelhawksley joelhawksley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a crack at fixing this issue! I added a couple of comments, but one key thing is missing: a test to reproduce the issue to prevent regression in the future. Please add a failing test that this PR fixes ❤️

Copy link
Member

@joelhawksley joelhawksley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, mind looking at the CI errors?

…ineno

- Remove coverage_running? check from Inline class (inline templates start
  at line 2+, so subtracting 1 won't result in negative line numbers)
- For File templates, strip the first line of compiled source when coverage
  is running AND annotations are enabled, instead of using lineno=1
- Add regression tests for coverage segfault fix

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@joelhawksley joelhawksley merged commit 39b93bd into ViewComponent:main Feb 11, 2026
13 of 14 checks passed
joelhawksley added a commit that referenced this pull request Feb 12, 2026
In #2541, logic
was added to strip out the first template annotation line. Unfortunately,
the template annotations in Rails span multiple lines, causing
invalid Ruby to be eval'd.

This fix splits the compiled source on ";", which properly isolates
the template annotation for easy removal.
joelhawksley added a commit that referenced this pull request Feb 12, 2026
In #2541, logic
was added to strip out the first template annotation line. Unfortunately,
the template annotations in Rails span multiple lines, causing
invalid Ruby to be eval'd.

This fix splits the compiled source on ";", which properly isolates
the template annotation for easy removal.
joelhawksley added a commit that referenced this pull request Feb 12, 2026
In #2541, logic
was added to strip out the first template annotation line. Unfortunately,
the template annotations in Rails span multiple lines, causing
invalid Ruby to be eval'd.

This fix splits the compiled source on ";", which properly isolates
the template annotation for easy removal.
joelhawksley added a commit that referenced this pull request Feb 12, 2026
In #2541, logic
was added to strip out the first template annotation line. Unfortunately,
the template annotations in Rails span multiple lines, causing
invalid Ruby to be eval'd.

This fix splits the compiled source on ";", which properly isolates
the template annotation for easy removal.
joelhawksley added a commit that referenced this pull request Feb 12, 2026
In #2541, logic
was added to strip out the first template annotation line. Unfortunately,
the template annotations in Rails span multiple lines, causing
invalid Ruby to be eval'd.

This fix splits the compiled source on ";", which properly isolates
the template annotation for easy removal.
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