Skip to content

fix: guard circular import/load_file against C-stack SIGSEGV (#496)#513

Merged
InauguralPhysicist merged 1 commit into
mainfrom
fix/496-circular-import-guard
Jul 9, 2026
Merged

fix: guard circular import/load_file against C-stack SIGSEGV (#496)#513
InauguralPhysicist merged 1 commit into
mainfrom
fix/496-circular-import-guard

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Problem

A mutual or self-referential import (a→b→a) or load_file (a↔b) recursed through vm_execute until the C stack overflowed — SIGSEGV, rc=139, uncatchable. The module cache is only populated after a load finishes, so a re-entrant load of a still-loading path missed the cache and recursed.

Reproduced on v0.28.0:

# a.eigs: import b   |   b.eigs: import a   |   main.eigs: import a
$ eigenscript main.eigs
Segmentation fault (core dumped)   # rc=139

Fix

A per-EigsState in-flight load stack (eigs_loading_active / enter / leave) records paths whose load is currently on the C stack:

  • the import opcode checks it on cache-miss;
  • load_file (which has no cache and re-executes every call) keys on the canonical realpath;
  • shared by both, so a cycle that crosses the two (importload_fileimport) is caught.

On a cycle the loader raises a catchable io error instead of recursing:

import: circular dependency — 'a' is already being loaded

Repeated sequential loads of the same file stay legal — the entry pops when each load completes; only active re-entrancy is a cycle. Diamond imports (a→c, b→c) still resolve c once via the cache.

Tests

New suite section [115] (generated multi-file fixtures): mutual import raises + is try/catch-able, mutual load_file raises, none segfault.

  • Release suite: 2625/2625
  • ASan + detect_leaks=1: 2625/2625, leak tally 0

Closes #496

🤖 Generated with Claude Code

A mutual or self-referential `import` (a→b→a) or `load_file` (a↔b)
recursed through vm_execute until the C stack overflowed — SIGSEGV,
rc=139, uncatchable. The module cache is only populated after a load
completes, so a re-entrant load of a still-loading path missed the cache
and recursed.

Add a per-EigsState in-flight load stack (eigs_loading_active/enter/leave)
recording paths whose load is currently on the C stack. The import opcode
checks it on cache-miss; load_file keys on the canonical realpath. On a
cycle the loader raises a catchable EK_IO error instead of recursing.
Shared by both loaders so a cross-loader cycle (import → load_file →
import) is caught too. Repeated *sequential* loads stay legal — the entry
pops when each load completes; only active re-entrancy is a cycle.

Regression: suite section [115] (generated fixtures — mutual import
raises + is try/catch-able, mutual load_file raises, no SIGSEGV).
Full release + ASan/leak suites green (2625/2625, leak tally 0).

Closes #496

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@InauguralPhysicist InauguralPhysicist merged commit 650b175 into main Jul 9, 2026
17 checks passed
@InauguralPhysicist InauguralPhysicist deleted the fix/496-circular-import-guard branch July 9, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

import/load_file: circular dependency recurses until SIGSEGV (no cycle guard)

1 participant