Skip to content

Array.fromAsync: use internal call instead of .call lookup (#1795) - #1832

Open
Chieler wants to merge 1 commit into
quickjs-ng:masterfrom
Chieler:fix-1795-fromasync-call
Open

Chieler wants to merge 1 commit into
quickjs-ng:masterfrom
Chieler:fix-1795-fromasync-call

Conversation

@Chieler

@Chieler Chieler commented Oct 17, 2026

Copy link
Copy Markdown

Title

Array.fromAsync: use internal call instead of .call lookup

Description

Fixes #1795

Problem

Array.fromAsync in builtin-array-fromasync.js used method-call syntax on user-provided functions:

  • mapFn.call(thisArg, value, i) in both array-like and iterable paths
  • method.call(arrayLike) when invoking [Symbol.iterator] or [Symbol.asyncIterator]
    This performed an observable property lookup for .call on mapFn and method. Under the ECMAScript specification (Array.fromAsync):
  • mapFn must be invoked via Call(mapper, thisArg, « value, 𝔽(k) ») (steps 8.d.ix.1 and 9.g.iv.1).
  • The iterator method must be invoked via GetIteratorFromMethod step 1: Call(method, obj).
    Neither step reads a .call property. If user code or a library provides a function with a custom or non-callable .call property, or if Function.prototype.call is modified/deleted, Array.fromAsync either executes the hijacked .call method or throws a TypeError: not a function.

Fix

  1. In quickjs.c, passed JS_NewCFunction(ctx, js_call_function, "call", 2) to js_bytecode_autoinit when instantiating qjsc_builtin_array_fromasync. This provides the same monkey patch-proof js_call_function helper already used by Iterator.zip built-ins.
  2. In builtin-array-fromasync.js:
    • Accepted call in the outer IIFE wrapper parameters.
    • Replaced mapFn.call(thisArg, value, i) with call(thisArg, mapFn, value, i).
    • Replaced method.call(arrayLike) with call(arrayLike, method).
  3. Regenerated builtin-array-fromasync.h with make codegen (qjsc -C -ss).

Verification

Added tests/bug1795.js covering:

  1. . mapFn with overridden .call on an iterable source ([1, 2]) returns [10, 20].
  2. mapFn with overridden .call on an array-like source ({ length: 2, 0: 1, 1: 2 }) returns [10, 20].
  3. Synchronous iterator method ([Symbol.iterator]) with overridden .call returns [1, 2].
  4. Asynchronous iterator method ([Symbol.asyncIterator]) with overridden .call returns [1,2].
  5. Non-callable .call (null, number) succeeds instead of throwing TypeError: not a function.
  6. Verified thisArg and (value, index) parameters are forwarded correctly to mapFn in both iterable and array-like paths.
  7. Verified this receiver for iterator methods is the source arrayLike object.

Test results:

  • Without patch: tests/bug1795.js fails with assertion failed: got |10|, expected |hijacked|.
  • With patch: tests/bug1795.js passes cleanly.
  • make test (run-test262 -c tests.conf) passes with 0 errors: Result: 0/121 errors, 9 excluded.
  • make ctest passes with zero compiler warnings/errors (-Wall -Wextra -Werror -pedantic).

…g#1795)

builtin-array-fromasync.js looked up .call on the mapping function and
iterator method (e.g. mapFn.call(thisArg, value, i) and method.call(arrayLike)).
This violated the TC39 specification (sec-array.fromasync steps 8.d.ix.1 and 9.g.iv.1,
and GetIteratorFromMethod step 1), which invoke the mapper and iterator
method directly via Call(F, V, argumentsList) without performing a property
lookup on .call.

If .call was overridden, deleted, or set to a non-callable value on the mapper
function or iterator method, or if Function.prototype.call was modified,
Array.fromAsync would execute the hijacked .call or throw a TypeError.

Fix this by passing the internal monkey patch-proof js_call_function ("call")
into builtin-array-fromasync.js and calling call(thisArg, func, ...args).
Regenerate builtin-array-fromasync.h via `make codegen`.

Fixes quickjs-ng#1795

This branch has not been deployed

No deployments
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.

Array.fromAsync looks up call on the mapper and the iterator method

1 participant