Ruby Coverage実行時のSegmentation Fault修正

viewcomponent/view_component

Rails 8.1環境でテンプレートアノテーションを無効化した状態でRuby Coverageを実行すると発生していたSegmentation Faultが修正されました。#2541の修正が不完全だったため、CI環境などアノテーションが無効化されている環境で問題が残っていました。

背景

#2541では、Ruby Coverageが有効な環境でのSegmentation Faultを修正するため、class_evallineno引数を条件分岐で制御する実装が追加されました。しかし、この修正には見落としがありました。

アノテーションはローカル開発環境では通常有効化されているため、開発者の手元では問題なく動作します。一方、CI環境ではパフォーマンスやログの簡潔性のため、アノテーションを無効化するのが一般的です。#2541の条件分岐は、アノテーション有効時のみに対応したものでした。

従来の条件式では、Coverageが動作していても、アノテーションが無効な場合はlineno = -1が使用されていました:

if coverage_running? && ActionView::Base.annotate_rendered_view_with_filenames
  @strip_annotation_line = true
  0
else
  -1
end

この実装では、coverage_running?annotate_rendered_view_with_filenamesの両方が真の場合のみSegmentation Faultを回避できます。CI環境のようにアノテーションが無効な場合、Coverage実行時でもlineno = -1となり、依然としてSegmentation Faultが発生していました。

技術的な変更

lib/view_component/template.rblineno設定ロジックが修正され、Coverage実行時は常にlineno = 0を使用するよう変更されました。

変更後:

lineno =
  if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
    if coverage_running?
      # Can't use negative lineno with coverage (causes segfault on Linux).
      # Strip annotation line if enabled to preserve correct line numbers.
      @strip_annotation_line = ActionView::Base.annotate_rendered_view_with_filenames
      0
    else
      -1
    end
  else
    1
  end

coverage_running?が真の場合、必ずlineno = 0を返すよう単純化されました。@strip_annotation_lineフラグの設定はアノテーション有効時のみ行われますが、linenoの値自体はアノテーション設定に関係なく0になります。

この変更により、以下の動作が保証されます:

  • Coverage無効時: lineno = -1でスタックトレースの行番号が正確
  • Coverage有効・アノテーション有効時: lineno = 0、アノテーション行を削除して行番号を正確に保つ
  • Coverage有効・アノテーション無効時: lineno = 0でSegmentation Faultを回避(今回追加された保護)

アノテーション行の削除処理は依然としてアノテーション有効時のみ実行されるため、不要な処理は発生しません。

テストの追加

test/sandbox/test/inline_template_test.rbに、アノテーション無効時のCoverage実行を検証するテストケースが追加されました。

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

  without_template_annotations do
    with_coverage_running do
      # Force recompilation with coverage "enabled" but annotations disabled
      ViewComponent::CompileCache.cache.delete(ErbComponent)

      # This would segfault in v4.3.0 because it only avoided -1 lineno
      # when annotations were enabled
      render_inline(ErbComponent.new(message: "Foo bar"))

      assert_selector("div", text: "Foo bar")
    end
  end
end

このテストはwithout_template_annotationsヘルパーを使用してアノテーションを明示的に無効化し、Coverage実行時の動作を検証します。コンポーネントのコンパイルキャッシュをクリアすることで、テスト実行時に確実に再コンパイルが行われます。

Segmentation FaultはLinux環境でのみ再現するため、macOS開発環境では直接検証できません。しかし、このテストケースがあれば、CI環境での検証が可能になり、同様の問題の再発を防げます。

設計判断

Coverage実行時は常に非負のlinenoを使用するという原則が明確化されました。

この制約は、Diff内のコメントで「Can't use negative lineno with coverage (causes segfault on Linux).」と示されている通り、負の行番号とCoverageの組み合わせがLinuxでSegmentation Faultを引き起こす問題に起因します。

当初の#2541では、Coverage実行とアノテーション有効の両方の条件でのみlineno = 0を使用していましたが、本PRでCoverageが動作している場合は無条件でlineno = 0とする方針に統一されました。これにより、条件分岐がシンプルになり、見落としやすいエッジケースを排除できています。

行番号の正確性は、アノテーション行の削除処理で維持されます。アノテーションが無効な場合は削除すべき行も存在しないため、lineno = 0でも実質的な問題は発生しません。

まとめ

本PRは、CI環境などアノテーションが無効化された環境でのSegmentation Faultを修正しました。条件分岐を単純化し、Coverage実行時は常にlineno = 0とすることで、環境設定の組み合わせによる予期しない動作を排除しています。アノテーション無効時の回帰テストの追加により、同様の問題の再発防止も実現されました。

記事メタデータ

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の存在と明確さ

「総論→各論→結論」の構成が明確です。リード文、背景、技術的な変更、設計判断、まとめの各要素がすべて含まれており、理想的な構成です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(`ruby:filepath`)やGitHubのPRリンク記法(`[#123](URL)`)がガイドライン通りに正しく使用されています。

対象読者への適合性 ✓ PASS

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

「Segmentation Fault」や「Ruby Coverage」、「lineno」といった用語が前提知識として扱われており、専門知識を持つエンジニアという対象読者に適切です。

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

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

各セクションが総論・各論で構成され、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすい記事になっています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているすべてのコードブロック(変更前のロジック、変更後のコード、追加されたテストコード)は、提供されたDiff情報と正確に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Segmentation Fault」、「Ruby Coverage」、「lineno」、「テンプレートアノテーション」などの技術用語が、文脈に応じて正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

修正の背景、原因(アノテーション無効時の問題)、解決策(`lineno=0`への統一)に関する説明は、PR情報とDiffの内容に完全に裏付けられており、技術的に正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張(#2541の修正が不完全だった点、CI環境の特性、Linuxでのみ再現する問題など)は、PRのDescriptionやDiffの内容に直接基づいており、ハルシネーションは見られません。

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

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

PR番号(#2541, #2552)、Railsのバージョン条件、linenoの値(0, -1)など、記事中のすべての数値と固有名詞は正確に記載されています。

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

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

記事のタイトル「Ruby Coverage実行時のSegmentation Fault修正」は、元のPRの主題を的確に要約しており、内容との整合性も取れています。

外部知識の正確性 ✓ PASS

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

PR情報に含まれない外部知識(バージョンのサポート状況、リリース日程など)の追加はなく、すべての情報源がPRに限定されています。

時間表現の正確性 ✓ PASS

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

「発生していた」「問題が残っていました」など、過去の事象を示す時間表現が正確に使用されており、PRの文脈と一致しています。