Skip to content

Commit e7cff2e

Browse files
rwstaunertekknolagi
authored andcommitted
ZJIT: Register builtin CMEs before prelude to avoid prepend crash
Split rb_zjit_init into rb_zjit_init_builtin_cmes (called before ruby_init_prelude) and rb_zjit_init (called after). The prelude may load bundler via BUNDLER_SETUP which can call Kernel.prepend, moving core methods to an origin iclass. Registering method annotations before the prelude ensures we capture CMEs while core classes are pristine.
1 parent bf3bc12 commit e7cff2e

File tree

3 files changed

+24
-6
lines changed

3 files changed

+24
-6
lines changed

ruby.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,10 @@ ruby_opt_init(ruby_cmdline_options_t *opt)
18241824
#if USE_YJIT
18251825
rb_yjit_init_builtin_cmes();
18261826
#endif
1827+
#if USE_ZJIT
1828+
extern void rb_zjit_init_builtin_cmes(void);
1829+
rb_zjit_init_builtin_cmes();
1830+
#endif
18271831

18281832
ruby_init_prelude();
18291833

test/ruby/test_zjit.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ def test_zjit_disable
112112
RUBY
113113
end
114114

115+
def test_zjit_prelude_kernel_prepend
116+
# Simulate what bundler/setup can do: prepend a module to Kernel during
117+
# the prelude via the BUNDLER_SETUP mechanism in rubygems.rb:
118+
# require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler)
119+
Tempfile.create(["kernel_prepend", ".rb"]) do |f|
120+
f.write("Kernel.prepend(Module.new)\n")
121+
f.flush
122+
assert_separately([{ "BUNDLER_SETUP" => f.path }, "--enable=gems", "--zjit"], "", ignore_stderr: true)
123+
end
124+
end
125+
115126
def test_zjit_enable_respects_existing_options
116127
assert_separately(['--zjit-disable', '--zjit-stats-quiet'], <<~RUBY)
117128
refute_predicate RubyVM::ZJIT, :enabled?

zjit/src/state.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,25 +292,28 @@ impl ZJITState {
292292
}
293293
}
294294

295-
/// Initialize ZJIT at boot. This is called even if ZJIT is disabled.
295+
/// Initialize IDs and annotate builtin C method entries.
296+
/// Must be called at boot before ruby_init_prelude() since the prelude
297+
/// could redefine core methods (e.g. Kernel.prepend via bundler).
296298
#[unsafe(no_mangle)]
297-
pub extern "C" fn rb_zjit_init(zjit_enabled: bool) {
299+
pub extern "C" fn rb_zjit_init_builtin_cmes() {
298300
use InitializationState::*;
299301

300302
debug_assert!(
301303
matches!(unsafe { &ZJIT_STATE }, Uninitialized),
302-
"rb_zjit_init should only be called once during boot",
304+
"rb_zjit_init_builtin_cmes should only be called once during boot",
303305
);
304306

305-
// Initialize IDs and method annotations.
306-
// cruby_methods::init() must be called at boot,
307-
// as cmes could have been re-defined after boot.
308307
cruby::ids::init();
309308

310309
let method_annotations = cruby_methods::init();
311310

312311
unsafe { ZJIT_STATE = Initialized(method_annotations); }
312+
}
313313

314+
/// Initialize ZJIT at boot. This is called even if ZJIT is disabled.
315+
#[unsafe(no_mangle)]
316+
pub extern "C" fn rb_zjit_init(zjit_enabled: bool) {
314317
// If --zjit, enable ZJIT immediately
315318
if zjit_enabled {
316319
zjit_enable();

0 commit comments

Comments
 (0)