Skip to content

node:async_hooks: validate createHook options and callbacks #3089

@andrewtdiz

Description

@andrewtdiz

Summary

async_hooks.createHook(options) should match Node's validation for the top-level options value and hook callback members. Perry currently reads callback fields permissively and creates a hook even when options is undefined/null or when init, before, after, destroy, or promiseResolve are present but non-callable.

Node behavior

Probe run with npx --yes node@25.9.0:

const async_hooks = require("node:async_hooks");
for (const value of [undefined, null]) {
  try { async_hooks.createHook(value); }
  catch (err) { console.log("options", String(value), err.name, err.code || "no-code", err.message); }
}
for (const value of [null, 0, true, "x", {}, [], Symbol("s")]) {
  try { async_hooks.createHook({ init: value }); }
  catch (err) { console.log("init", String(value), err.name, err.code || "no-code", err.message); }
}
console.log(typeof async_hooks.createHook({ init() {} }).enable);

Observed behavior:

  • createHook(undefined) and createHook(null) throw TypeError before creating a hook.
  • Present non-callable hook members throw TypeError [ERR_ASYNC_CALLBACK] with messages like hook.init must be a function.
  • A missing/undefined member is allowed, and primitive top-level objects like 0 are accepted because destructuring simply produces no callback fields.

Perry implementation evidence

  • crates/perry-codegen/src/lower_call/native_table/async_decimal.rs routes async_hooks.createHook to js_async_hooks_create_hook with a raw NA_F64 options value.
  • crates/perry-runtime/src/async_hooks.rs::object_field() returns undefined when the options value is not an object pointer.
  • callbacks_from_options() reads init, before, after, destroy, and promiseResolve, then assigns each with closure_from_value(...) without checking that a present value is callable.
  • js_async_hooks_create_hook() always pushes a HookRecord with those callbacks and returns an AsyncHookHandle.

Expected fix

  • Preserve Node's allowed cases: missing/undefined individual callback fields and non-nullish primitive top-level options should not register callbacks.
  • Reject nullish top-level options like Node does.
  • Reject present non-callable callback fields with TypeError [ERR_ASYNC_CALLBACK] naming the invalid hook member.
  • Add parity coverage for nullish options and invalid init/before/after/destroy/promiseResolve members.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions