Skip to content

Dynamic calls ignore AsyncResource.bind static override and bind the constructor instead #10045

Description

@proggeramlug

Independent reproduction

No application bundle, credentials, network, subprocess, or extraction work is
required. Node 26.5.1 passes this program:

const hooks = process.getBuiltinModule('async_hooks');
const calls = [];
const callback = hooks.AsyncResource.bind(value => {
  calls.push(value);
  return value + 1;
});
console.log('created', typeof callback);
try {
  const value = callback(41);
  if (value !== 42 || calls.length !== 1 || calls[0] !== 41)
    throw new Error('wrong callback dispatch');
  console.log('PASS: builtin AsyncResource.bind');
} catch (error) {
  console.error('FAIL:', String(error));
  process.exitCode = 1;
}

Native Perry prints:

created function
FAIL: TypeError: Class constructor AsyncResource cannot be invoked without 'new'

Reproduced on macOS arm64 with Perry 0.5.1531: a frozen compiler and all nine
coherent runtime/provider archives from 7dc2a2700, based on current main
1a9c0de6c. The branch changes only the independent child-output fix #10042;
the dispatch code involved here is unchanged from main. Compilation used
--platform bun --enable-wasm-runtime --no-auto-optimize, LLVM Oz and the
explicit shadow-stack/full-outline profile. Compiler bound 120 s, execution
bound 10 s; this is an immediate failure, not a timeout.

Located dispatch error

object/native_module/callable_exports.rs::nm_attach_async_hooks already
attaches AsyncResource's own static bind implementation. However,
object/native_call_method.rs's early closure-property dispatch unconditionally
excludes method names apply | call | bind | toString to preserve dedicated
Function.prototype fast paths. The later common_methods::dispatch_common
bind arm calls js_function_bind on any callable receiver.

Thus this call resolves as Function.prototype.bind on the AsyncResource
constructor, rather than the constructor's own AsyncResource.bind method.
Calling the returned function then attempts an ordinary constructor invocation.

The runtime should honor an actual own method override without routing normal
inherited Function.prototype calls through generic thunks that lose
arguments-object semantics. Do not fix this by removing all bind functionality,
making constructors callable, or bypassing async-context propagation.

Required regression coverage

  • The program above through process.getBuiltinModule, plus import/require and
    aliased constructor forms.
  • AsyncResource.bind actually calls the supplied callback, forwards arguments,
    preserves its result and captured async context, and handles its optional
    receiver/type arguments according to Node.
  • An ordinary function with an own bind override dispatches that override;
    a non-callable own override throws rather than silently using the prototype.
  • Existing Function.prototype.bind/call/apply behavior (including arguments
    objects and bound functions) stays intact. Check O0/Os/Oz.
  • Audit the analogous own AsyncLocalStorage.bind and snapshot exposure,
    without assuming they share every failure mode.

Application evidence / scope

The reduced bug was found in a headless CLI signal dispatcher:
getBuiltinModule('async_hooks').AsyncResource.bind(listener) creates a
listener which later throws during emission. LLDB stops at
nm_dispatch_async_hooks -> js_throw, through bound-function dispatch.
Its detached message producer subsequently rejects without closing its output
queue, leaving the consumer waiting. The application is not needed to fix or
validate this issue, and this report does not claim it is the only remaining
application blocker.

Related: #9980 concerns native constructor prototype metadata. This report
has a separate static-method call reproducer and a located dispatch bypass.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions