バグ報告ドラフト — `prepare_callable_method_entry`での`Ruby::Box` SIGSEGV
bugs.ruby-lang.org向けのドラフト(How To Reportに従う)。ADR-39スライス5(プラグインのターゲットライブラリ分離のためにRigorのアナライザーをRUBY_BOX=1のもとで実行する)のプロトタイピング中に表面化した。
カテゴリ: core
対象バージョン: master / 4.0
ruby -v: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [arm64-darwin25]
実験的なRuby::Boxを有効化(RUBY_BOX=1)して大きなプログラムを実行すると、VMのメソッドルックアップパス(rb_vm_search_method_slowpath → callable_method_entry_or_negative → prepare_callable_method_entry)の内部でSIGSEGV(0x0でのnullポインタ参照)でクラッシュする。同一のプログラムをRUBY_BOX=1なしで実行すると正常に完了する。つまりRuby::Boxの有効化は、十分に複雑なワークロードでNULLを参照しうる形でメソッドエントリの解決を変える。
自己完結した純粋Rubyのリプロデューサーはまだ切り出せていない(「メモ」を参照);最小の信頼できる再現は、rigor静的アナライザーの単一ファイル実行である(これは入力をパースして静的に解析するだけで、決して実行しない):
ruby 4.0.5、arm64-darwin(macOS)。- 十分に複雑なRubyファイルを1つ(ここではRedmineの
app/models/issue.rb、約2,140行)、ボックスを有効にして静的解析する:RUBY_BOX=1 bundle exec rigor check app/models/issue.rb # SIGSEGVbundle exec rigor check app/models/issue.rb # exit 0Ruby::Box.newのサブボックスは作成されない——プロセス全体のRUBY_BOX=1フラグが設定されるだけである;クラッシュはアナライザーの実行中の通常の(メインボックスの)メソッドディスパッチで起きる。
再現手順の結果
Section titled “再現手順の結果”SIGSEGV。クラッシュレポート(抜粋):
... [BUG] Segmentation fault at 0x0000000000000000ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [arm64-darwin25]
-- C level backtrace information -------------------------------------------libruby-4.0.5.dylib(rb_vm_bugreport+0xbc8)libruby-4.0.5.dylib(rb_bug_for_fatal_signal)libruby-4.0.5.dylib(sigsegv)libsystem_platform.dylib(_sigtramp+0x38)libruby-4.0.5.dylib(prepare_callable_method_entry) <-- crashlibruby-4.0.5.dylib(prepare_callable_method_entry)libruby-4.0.5.dylib(callable_method_entry_or_negative)libruby-4.0.5.dylib(rb_vm_search_method_slowpath)libruby-4.0.5.dylib(vm_exec_core)libruby-4.0.5.dylib(rb_vm_exec)... (deep rb_yield / rb_ary_each / vm_exec_core recursion below)Rubyレベルの制御フレームは深い再帰的なeach駆動の評価を示している;フォルトはその再帰中にメソッドを解決している際に発生する。終了ステータス139(SIGSEGV)。
期待される結果
Section titled “期待される結果”プログラムは(RUBY_BOX=1なしのときのように)完了するか、通常のRubyレベルの例外を発生させるべきである。VMはprepare_callable_method_entryでNULLを参照してはならない;Ruby::Boxの有効化が、動作するプログラムをsegfaultに変えるべきではない。
メモ/最小化の状況
Section titled “メモ/最小化の状況”二分探索によって確立されたこと:
RUBY_BOX=1がトリガー。上記のすべてのコマンドはそれなしでは0で終了する。Ruby::Box.newのサブボックスとは独立 — プロセス全体のフラグだけを設定して再現する;ユーザーボックスは作成されない。- 単一の入力ファイルに二分探索。 Redmineの
app/全体でのクラッシュ →app/models/(86ファイル)に絞り込み → 単一ファイルapp/models/issue.rbまで二分探索した。その約2,140行のファイル1つだけがRUBY_BOX=1のもとでsegfaultする;アナライザー自身のソース(rigor check lib、248ファイル)はRUBY_BOX=1のもとでクラッシュしない。したがってトリガーはファイル数ではなく、RUBY_BOX=1+ 十分に複雑な1ファイルの解析の組み合わせである。 - これは「劣化/RBS環境なし」パスではない: 意図的に不正な形式の
sig/(RBS環境構築が失敗するように)で248ファイルをRUBY_BOX=1のもとで解析してもクラッシュしない。
それを再現しなかったもの(RUBY_BOX=1のもとでの純粋Rubyの試み):
- 単純な深い再帰(
def f = f; f→ 通常のSystemStackError)。 - 300個のモジュールをインクルードしたクラスにわたるメガモルフィックディスパッチ + 再帰。
- メガモルフィックなノードごとのディスパッチを伴う、ネストされた
eachブロック経由で再帰的に走査される131kノードのツリー。 - 40段の深さの
superミックスインチェイン × 120サブクラス +GC.stress。 require "rbs"/Ruby::Box.new+box.require(問題なくロードされる)。
したがってフォルトは、単純な合成では捉えられないアナライザーのホットパスに固有のメソッド解決パターンを必要とする;自己完結した最小リプロデューサーはまだ未解決である。完全なmacOSクラッシュレポート(~/Library/Logs/DiagnosticReports)を添付できる。RUBY_BOXのもとでprepare_callable_method_entryが何をNULLとして参照するかについてのポインタを歓迎する——Cバックトレースはフォルトをrb_vm_search_method_slowpath → callable_method_entry_or_negative → prepare_callable_method_entryに位置づけている。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.