You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
AOT: compile eval / new Function to a deferred runtime error (with compile-time notice) instead of refusing the build; add strict mode to block #5206
Context: surfaced by real-app AOT probing (opencode/openclaw) in our internal suite.
Problem
perry refuses new Function(...) (#1677) and eval(...) at compile time, which aborts the entire build. In real apps a single such call in a cold path blocks compilation of the whole program.
Concrete: bundling opencode (bun build) resolved its full import graph (179 modules → 1.24 MB) with no import wall. The first hard failure was one call —
// src/cli/cmd/debug/agent.handler.ts — a --params fallback, never hit in normal usereturnnewFunction(`return (${trimmed})`)();
That single line currently blocks the entire binary.
Proposed default: defer to a runtime error, don't refuse the build
When perry encounters eval / new Function (and other runtime code-eval), compile it to a binding that throws a descriptive error at runtime, and only if it is actually invoked, e.g.:
Error: eval()/new Function() cannot run in an ahead-of-time compiled binary (src/.../agent.handler.ts:NN)
Everything else compiles and runs normally; only the eval path fails, and only when reached.
Compile-time transparency (required, not silent)
The user must be told at compile time exactly what is being deferred and where:
notice: 1 runtime-eval site compiled to a deferred runtime error (throws only if reached):
- new Function(...) src/cli/cmd/debug/agent.handler.ts:NN
(summary count + per-site file:line). The build succeeds, but the user knows the capability is degraded and exactly where.
Strict mode (opt-in hard failure)
A flag/config to restore today's behavior — treat any eval/new Function as a compile-time error that blocks the build — for users who require a guarantee that the binary contains no runtime-eval paths. e.g. CLI --strict-eval (or a broader --strict), or perry.eval: "error" / perry.strict: true in package.json. Default = deferred-runtime-error + notice; strict = block.
Why
Runtime-computed code is the irreducible AOT boundary — but refusing at compile time over-applies it to the whole binary. Deferring to runtime, with a clear compile-time heads-up, lets the (vast majority of an) app that doesn't depend on eval compile and run, while keeping the hard guarantee available via strict mode. This was the single biggest unlock observed when probing real apps.
Related: #1677 (new Function refusal), #668 (require under compile).
Activity
feat(eval): defer runtime-unknown eval/new Function to a throw-on-rea…
feat(eval): defer runtime-unknown eval/new Function to a throw-on-rea…
feat(eval): defer runtime-unknown eval/new Function to a throw-on-rea…
feat(compile): defer non-resolvable dynamic import() to a throw-on-re…
feat(compile): defer `.wasm` ESM imports to throw-on-call stubs (#5235)…
feat(compile): defer recognized-but-unimplemented node/stdlib APIs to…
runtime: scoped interpreter for new Function — ajv / fast-json-string…