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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions