Skip to content

fix(runtime): restore js_node_system_error_value dropped by #5112 (unbreaks ALL auto-opt http/https/http2 compilation)#5122

Closed
proggeramlug wants to merge 1 commit into
mainfrom
fix/restore-js-node-system-error-value
Closed

fix(runtime): restore js_node_system_error_value dropped by #5112 (unbreaks ALL auto-opt http/https/http2 compilation)#5122
proggeramlug wants to merge 1 commit into
mainfrom
fix/restore-js-node-system-error-value

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Severity: HIGH — breaks ALL auto-optimize http/https/http2 compilation at HEAD

PR #5112 ("Next.js standalone walls 31-35") refactored `crates/perry-runtime/src/error.rs` and deleted the `#[no_mangle] pub unsafe extern "C" fn js_node_system_error_value` (originally added by #5078) along with its `#[used]` symbol-retention static. But `crates/perry-ffi/src/error.rs` still declares it `extern` and calls it from `system_error_value()`. So any build that links perry-ffi's `system_error_value` (which lives in `libperry_ext_http.a`) fails to link:

```
"_js_node_system_error_value", referenced from:
perry_ffi::error::system_error_value ... in libperry_ext_http.a
Error: Linking failed
```

Since auto-optimize is the default, this breaks compiling every http / https / http2 program from a cold cache. It merged green because CI never links that path and warm auto-opt caches masked it locally; a fresh `--no-cache` http compile reproduces it immediately.

This does not revert #5112 (a large, legitimate Next.js fix) — it only restores the one symbol #5112 dropped.

Fix

Re-added the deleted function verbatim (no #5112 replacement for system-error construction exists) next to the sibling `#[used]` keepalive anchors (`KEEP_JS_ERROR_VALUE_WITH_CODE` / `KEEP_JS_THROW_ERROR_WITH_CODE`), plus its own `KEEP_JS_NODE_SYSTEM_ERROR_VALUE` retention static. All helper signatures it calls are unchanged post-#5112:

  • `js_error_value_with_code(msg, len, code, len, kind: i32) -> f64`
  • `js_string_from_bytes(data, len: u32) -> *mut StringHeader`
  • `crate::value::js_nanbox_string`
  • `crate::object::js_object_set_field_by_name`

The extern contract perry-ffi imports — `js_node_system_error_value(msg,len,code,len,syscall,len,errno)->f64` — is restored as a `#[no_mangle]` symbol with `#[used]` retention.

Verification

Regression confirmed on pristine origin/main (built clean):
```
"_js_node_system_error_value", referenced from:
Error: Linking failed
```

Fixed tree — fresh `--no-cache` auto-opt http link now succeeds:
```
Wrote executable: /tmp/v
Binary size: 14.6MB
```
and the binary runs (status-codes.ts prints expected output).

http/https/http2 sweep (all `.ts`, `--no-cache`, diffed vs node v26):

  • COMPILE_FAIL: 0 ← the link is fixed
  • DIFF: 4, all behavioral/timing, none compile failures:
    • `http/server/aliased-create-server.ts` (segfaults — pre-existing, independent of this change; only newly observable now that http compiles again; does not touch the transport-error path)
    • `http2/{plaintext/loopback-streams, session/sequential-session-cleanup, session/controls}.ts` (known http2 session-timing tests)

cargo test `-p perry-runtime -p perry-ffi -p perry-stdlib`: 1032 passed; the 3 perry-runtime failures (`dynamic_props` GC-scanner isolation flakes + `builtin_prototype_methods_reject_dynamic_new`) are pre-existing — verified identical on pristine origin/main.

Summary by CodeRabbit

Release Notes

  • Improvements
    • Enhanced system error handling with more detailed error information, including error codes and system call context.

PR #5112's error.rs refactor deleted the #[no_mangle]
js_node_system_error_value (added by #5078) and its #[used] retention
static, but perry-ffi/src/error.rs still declares it extern and calls it
from system_error_value(). Any build that links perry-ffi's
system_error_value (libperry_ext_http.a) fails to link:

  undefined reference to `js_node_system_error_value'
  Error: Linking failed

This breaks ALL auto-optimize (default) http/https/http2 compilation at
HEAD. It was masked by warm auto-opt caches and never linked in CI, so it
merged green; a fresh --no-cache http compile reproduces it.

Restored verbatim (no #5112 replacement exists) next to the sibling
keepalive anchors; all helper signatures it calls
(js_error_value_with_code, js_string_from_bytes, js_nanbox_string,
js_object_set_field_by_name) are unchanged post-#5112.
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new #[no_mangle] FFI function js_node_system_error_value is added to crates/perry-runtime/src/error.rs. It delegates to the existing js_error_value_with_code to build a base error, then casts the result to an ObjectHeader pointer and installs a conditional syscall string property and an always-present errno number property. A #[used] keepalive static prevents the symbol from being dead-stripped.

Changes

Node system error FFI entrypoint

Layer / File(s) Summary
js_node_system_error_value FFI function and keepalive
crates/perry-runtime/src/error.rs
Adds the exported js_node_system_error_value function that builds a base error via js_error_value_with_code, conditionally sets a syscall string property, always sets an errno number property on the resulting object, and includes a #[used] static to prevent link-time dead-strip of the symbol.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • PerryTS/perry#5078: Both PRs add js_node_system_error_value in crates/perry-runtime/src/error.rs to populate .code, .syscall, and .errno on emitted error objects, indicating a direct overlap in implementation.

Poem

🐇 Hop, hop, a syscall appears,
With errno and code, calming fears,
The NaN-boxed error now speaks Node's tongue,
.syscall and .errno freshly strung,
No linker shall strip what the rabbit has done! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: restoring a deleted FFI function that breaks HTTP/HTTPS/HTTP2 compilation.
Description check ✅ Passed The description provides comprehensive context including severity, root cause, fix details, and verification results, but the test plan checklist is not completed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/restore-js-node-system-error-value

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-runtime/src/error.rs`:
- Line 375: The variable assignment at line 375 in the error handling code
directly bitcasts the f64 value using to_bits() without properly decoding the
NaN-boxed pointer, which will produce an invalid object pointer before
js_object_set_field_by_name mutates it. Replace the direct bitcast with a call
to js_nanbox_get_pointer to properly decode the NaN-boxed value first, matching
the existing pattern used elsewhere in the codebase (such as at lines 648, 1090,
and 1111).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2084dda8-bbfb-48d3-915b-34a26e75d57f

📥 Commits

Reviewing files that changed from the base of the PR and between c970fc5 and f181ae3.

📒 Files selected for processing (1)
  • crates/perry-runtime/src/error.rs

errno: f64,
) -> f64 {
let err_val = js_error_value_with_code(msg_ptr, msg_len, code_ptr, code_len, 0);
let obj = err_val.to_bits() as *mut crate::object::ObjectHeader;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify direct NaN-box bitcasts to raw pointers in this file.
rg -nP 'to_bits\(\)\s+as\s+\*(mut|const)\s+' crates/perry-runtime/src/error.rs
rg -n 'js_nanbox_get_pointer\(' crates/perry-runtime/src/error.rs

Repository: PerryTS/perry

Length of output: 383


🏁 Script executed:

# Find js_nanbox_get_pointer definition
rg -n 'fn\s+js_nanbox_get_pointer' crates/perry-runtime/

Repository: PerryTS/perry

Length of output: 265


🏁 Script executed:

# Check context around line 375 in error.rs
sed -n '370,380p' crates/perry-runtime/src/error.rs

Repository: PerryTS/perry

Length of output: 642


Decode the NaN-boxed pointer before object mutation.

Line 375 directly bitcasts f64 to *mut ObjectHeader using to_bits(), bypassing NaN-box decoding. This produces an invalid object pointer before js_object_set_field_by_name(...) mutates it. Use js_nanbox_get_pointer instead, which properly decodes the value—the file already uses this pattern at lines 648, 1090, and 1111.

Suggested fix
-    let obj = err_val.to_bits() as *mut crate::object::ObjectHeader;
+    let obj = crate::value::js_nanbox_get_pointer(err_val) as *mut crate::object::ObjectHeader;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let obj = err_val.to_bits() as *mut crate::object::ObjectHeader;
let obj = crate::value::js_nanbox_get_pointer(err_val) as *mut crate::object::ObjectHeader;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/error.rs` at line 375, The variable assignment at
line 375 in the error handling code directly bitcasts the f64 value using
to_bits() without properly decoding the NaN-boxed pointer, which will produce an
invalid object pointer before js_object_set_field_by_name mutates it. Replace
the direct bitcast with a call to js_nanbox_get_pointer to properly decode the
NaN-boxed value first, matching the existing pattern used elsewhere in the
codebase (such as at lines 648, 1090, and 1111).

Source: Coding guidelines

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Superseded by #5124, which restores js_node_system_error_value on main (relink shipped in v0.5.1167). Same root cause (#5112 dropped the symbol perry-ffi calls). Closing this duplicate.

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.

1 participant