Skip to content

load.c: report a refused irep from mrb_load_irep_cxt() - #7202

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:load/irep-cxt-error
Aug 16, 2026
Merged

load.c: report a refused irep from mrb_load_irep_cxt()#7202
matz merged 1 commit into
mruby:masterfrom
takumin:load/irep-cxt-error

Conversation

@takumin

@takumin takumin commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Three functions in src/load.c turn a byte stream into a running proc, and all of them go through load_irep(), which is where a refused irep is reported:

static mrb_value
load_irep(mrb_state *mrb, struct RProc *proc, mrb_ccontext *c)
{
  if (!proc || !proc->body.irep) {
    irep_error(mrb);
    return mrb_nil_value();
  }

Two of them hand it whatever the read returned:

MRB_API mrb_value
mrb_load_irep_buf_cxt(mrb_state *mrb, const void *buf, size_t bufsize, mrb_ccontext *c)
{
  return load_irep(mrb, mrb_proc_read_irep_buf(mrb, buf, bufsize), c);
}

MRB_API mrb_value
mrb_load_irep_file_cxt(mrb_state *mrb, FILE* fp, mrb_ccontext *c)
{
  return load_irep(mrb, mrb_proc_read_irep_file(mrb, fp), c);
}

mrb_load_irep_cxt() returns before it:

MRB_API mrb_value
mrb_load_irep_cxt(mrb_state *mrb, const uint8_t *bin, mrb_ccontext *c)
{
  struct RProc *proc = mrb_proc_read_irep(mrb, bin);
  if (!proc) return mrb_undef_value();
  return load_irep(mrb, proc, c);
}

so its caller gets undef with mrb->exc still clear. mrb_load_irep(), which every embedder reaches for and which the generated test drivers use, is that function with a NULL context.

It used to report. The early return came in with e55abd2 (2020-11-24), to skip allocating the temporary irep object when the read fails; before that commit NULL went into load_irep() and raised there:

-  struct RData *irep_obj = mrb_data_object_alloc(mrb, mrb->object_class, NULL, &tempirep_type);
+  struct RData *irep_obj;
   mrb_irep *irep = mrb_read_irep(mrb, bin);
   mrb_value ret;
 
+  if (!irep) return mrb_undef_value();
+  irep_obj = mrb_data_object_alloc(mrb, mrb->object_class, NULL, &tempirep_type);

Passing NULL into load_irep() is what the other two already do, so the allocation the commit was avoiding is not reintroduced by it.

What it costs to be silent

mrbgems/mruby-test/mrbgem.rake writes one mrb_load_irep() per test file and checks the state after each:

  mrb_load_irep(mrb2, gem_test_irep_mruby_bigint_0);
  if (mrb2->exc) {
    mrb_print_error(mrb2);
    mrb_close(mrb2);
    exit(EXIT_FAILURE);
  }

The check never fires, so a test file the VM cannot load is not a failure: it is subtracted from the run. mrbtest counts what it managed to load and reports 0 KO.

On an MRB_INT32 build that is not hypothetical. Today's master cannot load mrbgems/mruby-bigint/test/bigint.rb there, and says so only if the file is fed to mruby -b, which reaches the buf sibling. rake test over the same build says nothing:

$ build/host-m32/bin/mrbc -o b.mrb mrbgems/mruby-bigint/test/bigint.rb
$ build/host-m32/bin/mruby -b b.mrb
trace (most recent call last):
mrbgems/mruby-toplevel-ext/mrblib/toplevel.rb:1: irep load error (ScriptError)
$ rake test
...
  Total: 2292
     KO: 0

With this commit the same build stops on it:

$ rake test
...
mrbgems/mruby-numeric-ext/mrblib/numeric_ext.rb:64: irep load error (ScriptError)
rake aborted!
Command failed with status (1): [build/host-m32/bin/mrbtest]

What made that irep unloadable is #7201, and with both applied the same build is green at 2313 tests, 0 KO. This commit is what would have made it a failure rather than 20 assertions quietly not run.

host-m32 there is an i686-linux-gnu-gcc cross toolchain with -m32 and conf.gembox 'full-core', spelled out in #7201; build_config/host-m32.rb needs a multilib gcc this machine has no -m32 runtime for.

Return value

undef becomes nil on the failing path, which is what mrb_load_irep_buf() and mrb_load_irep_file() already return there. Nothing in the tree reads the value of a failed mrb_load_irep(); the callers are mrbgems/mruby-test/driver.c and the drivers mrbgem.rake generates, and all of them look at mrb->exc. include/mruby/irep.h documents no return value for the failing case.

Tests

None. mrb_load_irep() takes a pointer to a literal and no Ruby-visible path reaches it, so what this changes can only be seen from C or through mrbtest's own drivers, which is the transcript above.

Verification

rake -m test over ci/gcc-clang, every build green, 0 KO, 0 crash, no new warnings. The counts are master's to the test, since nothing here is reached unless a load fails.

build master with this commit
full-debug 2312 tests, 3 skip 2312 tests, 3 skip
bintest 2312 tests, 11 skip, plus 117 bintests 2312 tests, 11 skip, plus 117 bintests
cxx_abi 2312 tests, 11 skip 2312 tests, 11 skip
byte-string 2243 tests, 48 skip 2243 tests, 48 skip
ascii-case 2309 tests, 13 skip 2309 tests, 13 skip

On an MRB_INT32 build the run stops, as above, until #7201 is applied; with both, host-m32 (-m32, full-core) is 2313 tests, 0 KO, 4 skip, and host-i32 (-DMRB_INT32 -DMRB_NO_BOXING, full-core) is 2313 tests, 0 KO, 12 skip.

Environment

Versions
OS Ubuntu 24.04.4 LTS, Linux 7.0.0-28-generic x86_64
CPU AMD Ryzen 9 5950X, 16 cores
C compiler gcc 13.3.0 and i686-linux-gnu-gcc 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04.1)
Linker GNU ld 2.47.20260726, GNU ld 2.42 for i686, and g++ for cxx_abi
CRuby 4.0.6 (2026-07-14) +PRISM, running rake
Compile lines for src/load.c

-MMD -c, -I and -o dropped.

# full-debug, -O0 because enable_debug appends -g3 -O0
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -g3 -O0 -DMRB_GC_STRESS -DMRB_USE_DEBUG_HOOK -DMRB_DEBUG -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER src/load.c

# bintest
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_GC_FIXED_ARENA -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER -DMRB_USE_DEBUG_HOOK src/load.c

# cxx_abi, gcc -x c++ rather than g++, which only links
gcc -g -O3 -Wall -Wundef -Wwrite-strings -x c++ -std=gnu++03 -DMRB_GC_FIXED_ARENA -DMRB_USE_CXX_EXCEPTION -DMRB_USE_CXX_ABI -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER src/load.c

# byte-string
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER src/load.c

# ascii-case
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_USE_ASCII_CASE -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER src/load.c

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling when loading executable code fails, ensuring failures are processed consistently.
    • Removed an unnecessary fallback result that could mask loading errors.

`load_irep()` answers a `NULL` proc by raising `irep load error`, and
`mrb_load_irep_buf_cxt()` and `mrb_load_irep_file_cxt()` both reach it that
way. `mrb_load_irep_cxt()` returns before it instead, so a caller of
`mrb_load_irep()` is handed `undef` with `mrb->exc` still clear and nothing to
say why.

It used to report. The early return was added in e55abd2 to skip
allocating the temporary irep object when the read fails; before that, `NULL`
went into `load_irep()` and raised there. Pass it there again, which is also
what leaves the three of them the same shape.
@takumin
takumin requested a review from matz as a code owner August 16, 2026 16:02
@github-actions github-actions Bot added the core label Aug 16, 2026
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9be10784-2676-40df-be2c-f84807d29513

📥 Commits

Reviewing files that changed from the base of the PR and between 9710e46 and 3694edb.

📒 Files selected for processing (1)
  • src/load.c

Included review availability: Your plan includes up to 8 reviews per rolling hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

mrb_load_irep_cxt now passes the result of mrb_proc_read_irep directly to load_irep. load_irep handles procedure-load failures.

Changes

Irep loading

Layer / File(s) Summary
Delegate procedure loading failure handling
src/load.c
mrb_load_irep_cxt no longer checks for a null procedure or returns mrb_undef_value(). It delegates the result directly to load_irep.

Estimated code review effort: 2 (Simple) | ~5 minutes

Merge Risk: ⚪ Minimal · up to 3694e

This localized change makes refused irep loads report an error consistently instead of silently returning, improving test and embedder failure detection without introducing an actionable merge-blocking risk.

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: reporting a refused irep from mrb_load_irep_cxt().
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matz
matz merged commit 2bd3377 into mruby:master Aug 16, 2026
20 of 21 checks passed
@takumin
takumin deleted the load/irep-cxt-error branch August 16, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants