Rails 8.1以降でRubyカバレッジ有効時のセグメンテーション違反を修正

viewcomponent/view_component

Rails 8.1以降でRubyのCoverageモジュールを有効にした際に発生するセグメンテーション違反を修正しました。この問題は、ERBテンプレートのコンパイル時に使用される負のlineno値とRubyのカバレッジ機能の相互作用によって引き起こされていました。

背景

v4.2.0では、Rails 8.1のERBテンプレートにおけるスタックトレースの行番号のずれを修正するため、class_evalに負のlineno値(-1)を導入しました。しかし、この変更がRubyのCoverageモジュールが有効な環境でセグメンテーション違反を引き起こしていました。

問題の根本原因は、3つの変更が重なったことにあります。Rails 8.1では rails/rails#53731 により、コンパイル済みERB出力にアノテーションコメントが追加され、1行余分に生成されるようになりました。v4.2.0はこの余分な1行を相殺するためlineno = -1を使用しましたが、Ruby 3.4にはevalclass_evalでの負の行番号がCoverageモジュール実行中にセグメンテーション違反を引き起こすバグ(bugs.ruby-lang.org/issues/19363)が存在します。

SimpleCovなどのカバレッジツールを使用するCI環境では、Rails 8.1 + Ruby 3.4 + view_component 4.2.0の組み合わせでクラッシュが発生していました。

技術的な変更

ファイルベースのテンプレートとインラインテンプレートで異なるアプローチが採用されました。

ファイルベーステンプレートの修正

ViewComponent::Template::Fileクラスでは、カバレッジ実行時にアノテーション行を削除する方式に変更されました。

変更前:

lineno =
  if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
    -1
  else
    0
  end

変更後:

@strip_annotation_line = false

lineno =
  if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
    if coverage_running? && ActionView::Base.annotate_rendered_view_with_filenames
      @strip_annotation_line = true
      0
    else
      -1
    end
  else
    0
  end

カバレッジが実行中で、かつアノテーションが有効な場合、linenoを0に設定し、代わりにcompiled_sourceメソッドでアノテーション行を削除します。

def compiled_source
  result = super
  result = result.sub(/\A[^\n]*\n/, "") if @strip_annotation_line
  result
end

この方式により、負のlineno値を使用せずに正確な行番号を維持できます。

インラインテンプレートの扱い

ViewComponent::Template::Inlineクラスでは特別な処理は不要です。インラインテンプレートはクラス内で定義されるため、常に2行目以降から開始します。1を引いても負の値にならないため、セグメンテーション違反は発生しません。

# Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
# Subtract 1 to compensate for correct line numbers in stack traces.
# Inline templates start at line 2+, so this won't result in negative values.
lineno =
  if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && inline_template.language == :erb
    inline_template.lineno - 1
  else
    inline_template.lineno
  end

リグレッションテストの追加

実際のCoverage.startCoverage.resultを使用した2つのテストが追加されました。

test "file-based templates compile without segfault when coverage is running" do
  skip unless Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0

  with_new_cache do
    with_coverage_running do
      ViewComponent::CompileCache.cache.delete(ErbComponent)
      render_inline(ErbComponent.new(message: "Foo bar"))
      assert_selector("div", text: "Foo bar")
    end
  end
end

このテストは、カバレッジ実行中にファイルベーステンプレートが正常にコンパイル・レンダリングできることを検証します。インラインテンプレート用の類似テストも追加され、バックトレースの行番号が正しいことも確認します。

設計判断

カバレッジ実行時のみアノテーション行を削除する方式が採用されました。

レビューのフィードバックにより、当初のlineno=1(不正確な行番号になる)アプローチから、アノテーション行を削除する方式に変更されています。この判断により、開発環境や本番環境のユーザーは-1による正確な行番号を得られ、CI/カバレッジ環境のユーザーもアノテーション行の削除により正確な行番号を維持できます。

coverage_running?チェックはFileクラスにのみ適用され、Inlineクラスからは削除されました。これは、インラインテンプレートの行番号が常に正の値であるという特性を活かした判断です。条件分岐を必要な箇所にのみ配置することで、コードの複雑性を最小限に抑えています。

まとめ

本PRは、Rails 8.1のアノテーション機能とRubyのカバレッジ機能の相互作用によるクラッシュを、テンプレートの種類に応じた適切な処理で解決しました。ファイルベーステンプレートではアノテーション行の動的削除、インラインテンプレートでは既存の仕組みの活用という、それぞれの特性に合わせたアプローチにより、すべての環境で正確な行番号とクラッシュ回避の両立を実現しています。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)」という理想的な3部構成が明確に守られています。各セクションの役割が明確で、非常に理解しやすいです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのシンタックスハイライト(```ruby:ファイルパス)や、PR番号・外部リンクの記法がガイドラインに完全に準拠しています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

セグメンテーション違反、lineno、Coverageモジュールなど、専門的なトピックを前提知識を持つエンジニア向けに簡潔かつ的確に解説しており、対象読者に完全に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、段落の冒頭にトピックセンテンスが配置されているため、非常に読みやすいです。1段落1トピックが守られ、段落長も適切です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

Diffで示された変更箇所(`File`クラスのlineno計算ロジックの変更、`compiled_source`メソッドの追加、リグレッションテストの追加)が、コードブロックを用いて正確に引用・解説されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「セグメンテーション違反」「Coverageモジュール」「lineno」「class_eval」など、PRで使われている技術用語を正確に使用しており、文脈に即した適切な用語選択ができています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「Rails 8.1の変更」「v4.2.0での対応」「Rubyのバグ」という3つの要因が重なって問題が発生した経緯と、テンプレート種別ごとに対応を変えた解決策が、技術的に正確かつ論理的に説明されています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(問題の原因、解決策、設計判断の経緯など)は、提供されたPRのDescriptionやDiff内容によって完全に裏付けられています。ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#2541)、関連するRailsのPR番号(#53731)、RubyのIssue番号(#19363)など、記事内に記載されている数値や固有名詞はすべて正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「Rails 8.1以降でRubyカバレッジ有効時のセグメンテーション違反を修正」は、PRのタイトル「Fix segfault when Ruby coverage is enabled with Rails 8.1 ERB templates」の内容を忠実に反映しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事で言及されているバージョン番号(Rails 8.1, Ruby 3.4, view_component 4.2.0)はすべてPR情報に含まれており、PRに記載のない外部知識を持ち込んでいません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

問題が発生した経緯(Railsの変更→view_componentの対応→今回の修正)や、設計判断の変更(当初の案→レビュー後の案)といった時間的な前後関係が、PR情報と一致しており、正確に記述されています。