Skip to content

Heap reference count exhaustion #5287

@davidlie

Description

@davidlie

Array.prototype.flat with depth = Infinity recurses into ecma_builtin_array_flatten_into_array for each nested array element. When the source array is circular (array[1] === array), the function recurses without bound. Each recursive call increments the reference count of the shared array object via ecma_ref_object_inline. JerryScript's internal reference count is a bounded integer; when it saturates JERRY_FATAL_REF_COUNT_LIMIT, jerry_fatal is called and the process aborts.

Trigger (minimal):

let array = new Array(1);
array.splice(1, 0, array);  // array[1] === array (circular)
array.flat(Infinity);

Crash type: abort (SIGABRT / signal 6, JERRY_FATAL_REF_COUNT_LIMIT)
Component: jerry-core/ecma/built-in-objects/ecma-builtin-array-prototype.c, ecma_builtin_array_flatten_into_array

JerryScript revision

git hash b706935 on master branch

Build platform

Ubuntu 24.04.4 LTS (Linux 6.8.0-106-generic x86_64)

Build steps
tools/build.py --builddir build-asan --build-type Debug \
  --compile-flag=-fsanitize=address \
  --compile-flag=-fno-omit-frame-pointer \
  --compile-flag=-g \
  --linker-flag=-fsanitize=address

Note: ASAN does not intercept this crash — JerryScript calls abort() directly via jerry_port_fatal before ASAN's signal handler can produce a report. The backtrace below was obtained with GDB.

Test case
echo "let a=new Array(1); a.splice(1,0,a); a.flat(Infinity);" \
  | build-asan/bin/jerry -
Output
Aborted (core dumped)

(No ASAN report; process exits with signal 6.)

Backtrace

Obtained via gdb -ex "handle SIGABRT stop" -ex run -ex bt:

#0  __pthread_kill_implementation              pthread_kill.c:44
#1  __pthread_kill_internal                    pthread_kill.c:78
#2  __GI___pthread_kill
#3  __GI_raise (sig=6)                         raise.c:26
#4  __GI_abort ()                              abort.c:79
#5  jerry_port_fatal (code=JERRY_FATAL_REF_COUNT_LIMIT)
        jerry-port/common/jerry-port-process.c:41
#6  jerry_fatal (code=JERRY_FATAL_REF_COUNT_LIMIT)
        jerry-core/jrt/jrt-fatals.c:63
#7  ecma_ref_object_inline
        (object_p=0x5555557f63f8 <jerry_global_heap+760>)
        ecma/base/ecma-gc.c:141           ← ref count limit hit here
#8  ecma_copy_value (value=763)
        ecma/base/ecma-helpers-value.c:894
#9  ecma_fast_copy_value (value=763)
        ecma/base/ecma-helpers-value.c:921
#10 ecma_op_object_find_own
        (base_value=763, object_p=0x5555557f63f8, property_name_p=0x35)
        ecma/operations/ecma-objects.c:619
#11 ecma_op_object_find                        ecma-objects.c:752
#12 ecma_op_object_find_by_index
        (object_p=0x5555557f63f8, index=1)
        ecma/operations/ecma-objects.c:718
#13 ecma_builtin_array_flatten_into_array
        (target=1075, source=0x5555557f63f8, source_len=2,
         start=0, depth=inf, mapped_value=72, thisArg=72)
        ecma/builtin-objects/ecma-builtin-array-prototype.c:2567
#14 ecma_builtin_array_flatten_into_array (...)
        ecma-builtin-array-prototype.c:2621   ┐ recurses on
#15 ecma_builtin_array_flatten_into_array (...)  │ array[1]===array
        ecma-builtin-array-prototype.c:2621   ┘
    ... [truncated — frames #14 onwards repeat at the same call site]

Frame #13 (line 2567) is the initial call that reads array[index=1] and discovers it is an array, then calls itself recursively at line 2621 with the same object. The ref-count increments at frame #7 (via ecma_copy_valueecma_ref_object_inline) on every descent.

Expected behavior

ecma_builtin_array_flatten_into_array should detect cycles in the source array (e.g. by tracking visited objects in a set, or by checking whether the element to flatten is identical to any ancestor source) and either skip the element or throw a RangeError. As a lighter alternative, bounding the recursion depth against a hard limit (e.g. the current depth parameter capped at a sane maximum) would also prevent the abort.

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