Skip to content

fix(napi): receive raw pointer out parameters#57

Merged
wemeetagain merged 6 commits into
mainfrom
fix/napi-out-parameters-v2
Jul 24, 2026
Merged

fix(napi): receive raw pointer out parameters#57
wemeetagain merged 6 commits into
mainfrom
fix/napi-out-parameters-v2

Conversation

@GrapeBaBa

@GrapeBaBa GrapeBaBa commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Motivation

Node-API returns several values through pointer-sized C out parameters. The previous bindings passed addresses of higher-level Zig struct or wrapper storage to some of these APIs, so Node-API wrote raw pointer or handle bits into the wrong fields instead of a correctly typed raw slot. This could corrupt returned Node version data and TypedArray or DataView backing ArrayBuffer wrappers. Receiving the exact C values first, then copying or wrapping them only after a successful call, preserves both the C ABI and the Zig wrapper invariants. Unsupported TypedArray tags must likewise become a normal conversion error rather than an enum-conversion trap.

Summary

  • receive the Node version pointer through its own raw out slot before copying the version structure
  • receive TypedArray and DataView backing ArrayBuffer handles as raw napi_value values
  • construct Value wrappers only after successful Node-API calls
  • report unsupported Node-API TypedArray element types as UnsupportedTypedarrayType instead of trapping during enum conversion

@GrapeBaBa
GrapeBaBa marked this pull request as ready for review July 20, 2026 12:33
Comment thread examples/js_dsl/mod.test.ts Outdated
Comment on lines +293 to +295
it("rejects unsupported TypedArray element types", () => {
const Float16ArrayCtor = Reflect.get(globalThis, "Float16Array");
if (typeof Float16ArrayCtor !== "function") return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If that condition does not match, it will show the tests pass not skipped. We can use something like this to properly show the test skipped.

it.skipIf(typeof Float16Array !== "function")

Comment thread src/Value.zig
/// https://nodejs.org/api/n-api.html#napi_get_typedarray_info
pub fn getTypedarrayInfo(self: Value) NapiError!TypedarrayInfo {
var info: TypedarrayInfo = undefined;
pub fn getTypedarrayInfo(self: Value) TypedarrayInfoError!TypedarrayInfo {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a note for reference. The error set change on a pub function, will be breaking for usage when user completely exhausting error union.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is what I thought a while and finally add a new error

nazarhussain
nazarhussain previously approved these changes Jul 22, 2026
nazarhussain
nazarhussain previously approved these changes Jul 23, 2026
## Motivation

Node-API permits ArrayBuffer, Buffer, TypedArray, and DataView data out
parameters to be `NULL` or an arbitrary pointer when the reported length
is zero. This commonly occurs for empty and detached values. The
previous binding received those results into non-null `[*]u8` storage,
so a valid Node-API result could create a zero-length Zig slice whose
pointer violates the slice type invariant. Normalizing this case at the
binding boundary keeps the public slice API safe, while the TypedArray
zero-length branch avoids applying element-alignment casts to an empty
byte-slice sentinel.

## Summary

- represent Node-API `void*` data out parameters as nullable Zig
pointers
- normalize successful zero-length results to safe empty slices without
changing public return types
- skip TypedArray alignment casts when the element length is zero
- cover shared null-plus-zero normalization in Zig and exercise empty
and detached values through the example API
@GrapeBaBa

Copy link
Copy Markdown
Contributor Author

why this need another approve?

@wemeetagain

Copy link
Copy Markdown
Member

why this need another approve?

Looks like it was bc Nazar was both the last one who pushed the branch AND the only reviewer.

@wemeetagain
wemeetagain merged commit fde4a9a into main Jul 24, 2026
5 checks passed
@wemeetagain
wemeetagain deleted the fix/napi-out-parameters-v2 branch July 24, 2026 15:35
nazarhussain added a commit that referenced this pull request Jul 27, 2026
Resolves getTypedarrayInfo conflict: main (#57) fixed the same
unknown-typedarray-type bug via std.enums.fromInt, so adopt that
mechanism and drop this branch's now-redundant non-exhaustive
TypedarrayType / optional elementSize. Status and ValueType stay
non-exhaustive — main's typeof still raw-casts into ValueType.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nazarhussain added a commit that referenced this pull request Jul 27, 2026
Fixes JS-triggerable memory-safety bugs found in a security audit of
`src/js` and the N-API layer.

## Fixes

- **BigInt word OOB read** (`getValueBigintWords`): napi reports the
*required* word count, which can exceed the caller's buffer; slicing by
it read out of bounds. Now returns `error.Overflow`.
- **`BigInt.toI128` overflow**: out-of-range magnitudes and the valid
`-2^127` both tripped `@intCast`. Now range-checked.
- **Unknown napi enum values** (`Status`, `ValueType`): exhaustive enums
from napi out-params were invalid-enum UB (e.g. newer statuses). Made
non-exhaustive; unknown values map to errors. (The `TypedarrayType`
case, e.g. `Float16Array`, is now handled by main #57 via
`std.enums.fromInt`, so this branch defers to that.)
- **Constructor without `new`**: wrapped native state onto `globalThis`.
Now throws `TypeError`.
- **Error-path memory bugs** (not reachable from the test harness):
double-free in `materializeClassInstance`, use-after-free in
`registerClass` list linking, `catch unreachable` in module
registration.
- **Bonus**: implemented `expectType`/`expectTypedArrayOfType`, which
every `js.Value.as*()` method called but were never defined (compile
error on use, hidden by lazy analysis).

Impact depends on the consumer's optimize mode — zapi ships as source,
so the build mode is chosen downstream. lodestar-z builds `ReleaseSafe`,
where these surface as safety-check panics → process abort (DoS). They
would be UB only under `ReleaseFast`/`ReleaseSmall` (safety checks
elided), which lodestar-z doesn't use.

## Tests

- `zig build test:napi` / `test:zapi`, `pnpm test:js` all green
- New regression tests: i128 boundaries, out-of-range BigInts, no-`new`
calls, value narrowing
- `examples/targets` has one pre-existing, unrelated failure (musl
detection on macOS)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

3 participants