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
(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_value → ecma_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.
Array.prototype.flatwithdepth = Infinityrecurses intoecma_builtin_array_flatten_into_arrayfor 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 viaecma_ref_object_inline. JerryScript's internal reference count is a bounded integer; when it saturatesJERRY_FATAL_REF_COUNT_LIMIT,jerry_fatalis called and the process aborts.Trigger (minimal):
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_arrayJerryScript revision
git hash b706935 on master branch
Build platform
Ubuntu 24.04.4 LTS (Linux 6.8.0-106-generic x86_64)
Build steps
Note: ASAN does not intercept this crash — JerryScript calls
abort()directly viajerry_port_fatalbefore ASAN's signal handler can produce a report. The backtrace below was obtained with GDB.Test case
Output
(No ASAN report; process exits with signal 6.)
Backtrace
Obtained via
gdb -ex "handle SIGABRT stop" -ex run -ex bt: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 (viaecma_copy_value→ecma_ref_object_inline) on every descent.Expected behavior
ecma_builtin_array_flatten_into_arrayshould 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 ancestorsource) and either skip the element or throw aRangeError. As a lighter alternative, bounding the recursion depth against a hard limit (e.g. the currentdepthparameter capped at a sane maximum) would also prevent the abort.