diff --git a/CHANGELOG.md b/CHANGELOG.md index 069dd158f1..46e71ca052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +## v0.5.1160 — fix(hir): `queueMicrotask`/`structuredClone`/`atob`/`btoa` as first-class function values (#5015) + +Fixes #5015 — react-reconciler 0.33 production build threw `TypeError: value is +not a function` inside `updateContainerSync` on the first render, blocking every +custom React renderer (ink, react-three-fiber, react-pdf, …). + +**Root cause.** Four callable global helpers — `queueMicrotask`, +`structuredClone`, `atob`, `btoa` — were callable directly +(`queueMicrotask(fn)` is picked off in `expr_call/globals.rs`) but were never +added to `is_builtin_global_value_name` in `crates/perry-hir/src/analysis/builtins.rs`. +So a bare *value* read — `const m = queueMicrotask`, or the object-literal +property `{ scheduleMicrotask: queueMicrotask }` — fell through the ident +lowering to the `GlobalGet(0)` sentinel and evaluated to the **number `0`**. +`typeof` reported `"number"` and calling the stored value threw "value is not a +function". (The companion `typeof`-of-bare-ident fold and the +`globalThis.` thunk table already covered these names; only the value-read +list was missing them — a gap left over from #3986.) + +react-reconciler stores the host config's `scheduleMicrotask: queueMicrotask` +into a module var and later invokes it from +`scheduleImmediateRootScheduleTask` (reached via `updateContainerSync → +updateContainerImpl → scheduleUpdateOnFiber → ensureRootIsScheduled`). The +stored `0` was the non-callable the issue's backtrace pinpointed. + +**Fix.** Add the four names to `is_builtin_global_value_name` so a bare value +read lowers to `PropertyGet { GlobalGet(0), }`, resolving through the +existing globalThis thunk (the same path `fetch`/`parseInt`/`eval` already use). +This fixes both the bare value read and the object-literal property form, and +is value-read-only — direct calls are unaffected. Regression test: +`test-files/test_gap_global_fn_values_5015.ts` (byte-for-byte vs Node). + +**Follow-up wall (separate bug, not in this PR).** With this fix +`updateContainerSync` succeeds; the minimal repro's subsequent `flushSyncWork()` +now reaches `commitRoot`, where `flushPendingEffects` — a forward-referenced, +hoisted sibling function declaration captured by `commitRoot` inside the +huge `module.exports = function($$$config){…}` factory — reads back as +`undefined` (a distinct closure-capture/box issue, independent of +`queueMicrotask`: it reproduces with an arrow `scheduleMicrotask` too). To be +filed as its own issue. + ## v0.5.1159 — security: fix path traversal / arbitrary file write in `perry publish` (GHSA-x55v-q459-68ch) Security release. `perry publish` trusted the build server's diff --git a/CLAUDE.md b/CLAUDE.md index cf373ab269..39171039e8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1159 +**Current Version:** 0.5.1160 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index fc518b399f..caf0cc80a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5289,7 +5289,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "base64", @@ -5344,14 +5344,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "cc", "libc", @@ -5359,7 +5359,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "log", @@ -5374,7 +5374,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "perry-hir", @@ -5383,7 +5383,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "perry-hir", @@ -5391,7 +5391,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "perry-dispatch", @@ -5401,7 +5401,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "perry-hir", @@ -5410,7 +5410,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "base64", @@ -5423,7 +5423,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "perry-hir", @@ -5431,7 +5431,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "async-trait", @@ -5460,14 +5460,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "serde", "serde_json", @@ -5475,7 +5475,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1159" +version = "0.5.1160" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5486,7 +5486,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "clap", @@ -5501,14 +5501,14 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-argon2" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "argon2", "perry-ffi", @@ -5516,7 +5516,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "reqwest", @@ -5525,7 +5525,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "bcrypt", "perry-ffi", @@ -5533,7 +5533,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "rusqlite", @@ -5541,7 +5541,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "scraper", @@ -5549,7 +5549,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "perry-runtime", @@ -5557,7 +5557,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "chrono", "cron 0.16.0", @@ -5567,7 +5567,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "chrono", "perry-ffi", @@ -5575,7 +5575,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "rust_decimal", @@ -5583,7 +5583,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "serde_json", @@ -5591,7 +5591,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "rand 0.8.6", @@ -5599,7 +5599,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "perry-runtime", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "bytes", "http-body-util", @@ -5631,7 +5631,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "lazy_static", "perry-ffi", @@ -5643,7 +5643,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "lazy_static", "perry-ext-http-server", @@ -5656,7 +5656,7 @@ dependencies = [ [[package]] name = "perry-ext-http-server" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "bytes", "h2", @@ -5679,7 +5679,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "lazy_static", "perry-ffi", @@ -5689,7 +5689,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "jsonwebtoken", @@ -5700,7 +5700,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "lru", "perry-ffi", @@ -5708,7 +5708,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "chrono", "perry-ffi", @@ -5716,7 +5716,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "bson", "futures-util", @@ -5728,7 +5728,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "chrono", "perry-ffi", @@ -5738,7 +5738,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "nanoid", "perry-ffi", @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "perry-runtime", @@ -5759,7 +5759,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "lettre", "perry-ffi", @@ -5769,7 +5769,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "printpdf", @@ -5777,7 +5777,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "sqlx", @@ -5786,7 +5786,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "governor", "perry-ffi", @@ -5794,7 +5794,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "image", @@ -5803,14 +5803,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "lazy_static", "perry-ffi", @@ -5819,7 +5819,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "uuid", @@ -5827,7 +5827,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ffi", "regex", @@ -5837,7 +5837,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "futures-util", "lazy_static", @@ -5849,7 +5849,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "brotli", "flate2", @@ -5858,7 +5858,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "dashmap", "once_cell", @@ -5867,7 +5867,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "perry-api-manifest", @@ -5884,7 +5884,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "perry-diagnostics", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "base64", @@ -5928,7 +5928,7 @@ dependencies = [ [[package]] name = "perry-stdlib" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "aes 0.8.4", "aes-gcm", @@ -6020,7 +6020,7 @@ dependencies = [ [[package]] name = "perry-transform" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "perry-hir", @@ -6030,7 +6030,7 @@ dependencies = [ [[package]] name = "perry-types" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "anyhow", "thiserror 1.0.69", @@ -6038,14 +6038,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "itoa", @@ -6062,7 +6062,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "rand 0.8.6", "serde", @@ -6072,7 +6072,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "cairo-rs", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "block2", @@ -6111,7 +6111,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "block2", @@ -6126,7 +6126,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1159" +version = "0.5.1160" [[package]] name = "perry-ui-test" @@ -6134,11 +6134,11 @@ version = "0.1.0" [[package]] name = "perry-ui-testkit" -version = "0.5.1159" +version = "0.5.1160" [[package]] name = "perry-ui-tvos" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "block2", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "block2", @@ -6170,7 +6170,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "block2", "libc", @@ -6183,7 +6183,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "libc", @@ -6200,14 +6200,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "base64", "ed25519-dalek", @@ -6221,7 +6221,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1159" +version = "0.5.1160" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index df70a4497d..9bb262a625 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -201,7 +201,7 @@ codegen-units = 16 opt-level = "s" # Optimize for size in stdlib [workspace.package] -version = "0.5.1159" +version = "0.5.1160" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/crates/perry-hir/src/analysis/builtins.rs b/crates/perry-hir/src/analysis/builtins.rs index 52281eea46..36860f3202 100644 --- a/crates/perry-hir/src/analysis/builtins.rs +++ b/crates/perry-hir/src/analysis/builtins.rs @@ -157,6 +157,21 @@ pub(crate) fn is_builtin_global_value_name(name: &str) -> bool { // thunk (no dedicated HIR variant). Used by `qs` (→ `stripe`). | "escape" | "unescape" + // #5015: callable global helpers that have a `globalThis.` + // thunk and are recognized as known globals + folded to typeof + // "function" (#3986), but were never added here — so a bare *value* + // read (`const m = queueMicrotask`, `{ scheduleMicrotask: + // queueMicrotask }`) fell through to the `GlobalGet(0)` sentinel and + // evaluated to the number `0`, not a function. react-reconciler's + // host config (`scheduleMicrotask: queueMicrotask`) hit exactly this: + // `updateContainerSync` → `scheduleImmediateRootScheduleTask` called + // the stored `0` and threw "value is not a function". Direct CALLS + // (`queueMicrotask(fn)`, `btoa(s)`) are picked off earlier in + // expr_call/globals.rs, so this only affects value reads. + | "queueMicrotask" + | "structuredClone" + | "atob" + | "btoa" ) } diff --git a/test-files/test_gap_global_fn_values_5015.ts b/test-files/test_gap_global_fn_values_5015.ts new file mode 100644 index 0000000000..feb5e3fbf2 --- /dev/null +++ b/test-files/test_gap_global_fn_values_5015.ts @@ -0,0 +1,35 @@ +// #5015 — callable global helpers used as first-class VALUES. +// queueMicrotask / structuredClone / atob / btoa were callable directly +// (`queueMicrotask(fn)`) but a bare value read (`const m = queueMicrotask`, +// `{ scheduleMicrotask: queueMicrotask }`) fell through to the GlobalGet(0) +// sentinel and evaluated to the number 0 — so `typeof` was "number" and +// calling the stored value threw "value is not a function". This is exactly +// what react-reconciler's host config (`scheduleMicrotask: queueMicrotask`) +// hit inside updateContainerSync → scheduleImmediateRootScheduleTask. + +// Bare value reads: typeof must be "function". +const qm: any = queueMicrotask; +const sc: any = structuredClone; +const a: any = atob; +const b: any = btoa; +console.log(typeof qm, typeof sc, typeof a, typeof b); + +// As object-literal property values (the react-reconciler host-config shape), +// then extracted and called. +const cfg: any = { + scheduleMicrotask: queueMicrotask, + clone: structuredClone, + decode: atob, + encode: btoa, +}; +console.log(typeof cfg.scheduleMicrotask, typeof cfg.clone, typeof cfg.decode, typeof cfg.encode); + +// Call through the stored value. +console.log(JSON.stringify(cfg.clone({ x: 1, y: [2, 3] }))); +console.log(cfg.encode("hi"), cfg.decode(cfg.encode("hi"))); +cfg.scheduleMicrotask(() => console.log("microtask ran")); + +// Direct calls still work (these were always fine — guard against regressions). +console.log(btoa("ok"), atob("b2s=")); +console.log(JSON.stringify(structuredClone([1, { a: 2 }]))); +queueMicrotask(() => console.log("direct microtask ran"));