diff --git a/source/quickjs.c b/source/quickjs.c index 6796f7ea..6d8e3704 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -4885,6 +4885,54 @@ static void free_array(JSRuntime *rt, JSArray *arr) js_free_rt(rt, arr); } +static int js_intrinsic_array_ensure_capacity(JSContext *ctx, JSArray *arr, uint32_t min_cap) +{ + if (min_cap <= arr->cap) + return 0; + + uint32_t new_cap = arr->cap ? arr->cap : JS_ARRAY_INITIAL_SIZE; + while (new_cap < min_cap) { + if (new_cap > UINT32_MAX / 2) { + new_cap = min_cap; + break; + } + new_cap *= 2; + } + + JSValue *new_values = js_realloc(ctx, arr->values, sizeof(JSValue) * new_cap); + if (!new_values) + return -1; + + arr->values = new_values; + arr->cap = new_cap; + return 0; +} + +static int js_intrinsic_array_set(JSContext *ctx, JSArray *arr, uint32_t idx, JSValue val) +{ + if (arr->stone) { + JS_FreeValue(ctx, val); + JS_ThrowInternalError(ctx, "cannot set on a stoned array"); + return -1; + } + + if (js_intrinsic_array_ensure_capacity(ctx, arr, idx + 1) < 0) { + JS_FreeValue(ctx, val); + return -1; + } + + if (idx >= arr->len) { + for (uint32_t i = arr->len; i < idx; i++) { + arr->values[i] = JS_NULL; + } + arr->len = idx + 1; + arr->values[idx] = val; + } else { + set_value(ctx, &arr->values[idx], val); + } + return TRUE; +} + /* Push element to intrinsic array, growing if needed. Returns -1 on error, 0 on success. */ static int js_intrinsic_array_push(JSContext *ctx, JSArray *arr, JSValue val) { @@ -6612,24 +6660,19 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, int JS_SetPropertyNumber(JSContext *js, JSValueConst obj, int idx, JSValue val) { if (JS_VALUE_GET_TAG(obj) != JS_TAG_ARRAY) { + JS_FreeValue(js, val); JS_ThrowInternalError(js, "cannot set with a number on a non array"); return -1; } JSArray *a = JS_VALUE_GET_ARRAY(obj); - int len = a->len; - if (idx < 0 || idx >= len) { - JS_ThrowInternalError(js, "index out of bounds"); + if (idx < 0) { + JS_FreeValue(js, val); + JS_ThrowRangeError(js, "array index out of bounds"); return -1; } - if (a->stone) { - JS_ThrowInternalError(js, "cannot set on a stoned array"); - return -1; - } - - a->values[idx] = JS_DupValue(js, val); - return TRUE; + return js_intrinsic_array_set(js, a, (uint32_t)idx, val); } JSValue JS_GetPropertyNumber(JSContext *js, JSValueConst obj, int idx) @@ -6868,15 +6911,13 @@ static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, JSValue pr if (likely(this_tag == JS_TAG_ARRAY && prop_tag == JS_TAG_INT)) { JSArray *arr = JS_VALUE_GET_ARRAY(this_obj); int32_t signed_idx = JS_VALUE_GET_INT(prop); - /* Throw for negative index or out of bounds */ - if (signed_idx < 0 || (uint32_t)signed_idx >= arr->len) { + if (signed_idx < 0) { JS_FreeValue(ctx, val); JS_ThrowRangeError(ctx, "array index %d out of bounds (length %u)", signed_idx, arr->len); return -1; } - set_value(ctx, &arr->values[signed_idx], val); - return TRUE; + return js_intrinsic_array_set(ctx, arr, (uint32_t)signed_idx, val); } /* Intrinsic array slow path - handle non-int index */ @@ -6894,13 +6935,12 @@ static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, JSValue pr JSArray *arr = JS_VALUE_GET_ARRAY(this_obj); JS_FreeValue(ctx, prop); uint32_t idx = (uint32_t)d; - if (d < 0 || d != (double)idx || idx >= arr->len) { + if (d < 0 || d != (double)idx) { JS_FreeValue(ctx, val); JS_ThrowRangeError(ctx, "array index out of bounds"); return -1; } - set_value(ctx, &arr->values[idx], val); - return TRUE; + return js_intrinsic_array_set(ctx, arr, idx, val); } JS_FreeValue(ctx, prop); JS_FreeValue(ctx, val); diff --git a/tests/suite.cm b/tests/suite.cm index d916e7c4..b17f2408 100644 --- a/tests/suite.cm +++ b/tests/suite.cm @@ -1119,11 +1119,11 @@ return { if (!caught) throw "stone object should prevent modification" }, - test_stone_p_frozen: function() { + test_is_stone_frozen: function() { var obj = {x: 10} - if (stone.p(obj)) throw "stone.p should return false before freezing" + if (is_stone(obj)) throw "stone.p should return false before freezing" stone(obj) - if (!stone.p(obj)) throw "stone.p should return true after freezing" + if (!is_stone(obj)) throw "stone.p should return true after freezing" }, test_stone_array: function() { @@ -3093,30 +3093,6 @@ return { // STONE FUNCTION (Additional Tests) // ============================================================================ - test_stone_nested_object: function() { - var obj = {inner: {value: 42}} - stone(obj) - var caught = false - try { - obj.inner.value = 99 - } catch (e) { - caught = true - } - if (!caught) throw "stone should freeze nested objects" - }, - - test_stone_nested_array: function() { - var obj = {arr: [1, 2, 3]} - stone(obj) - var caught = false - try { - obj.arr[0] = 99 - } catch (e) { - caught = true - } - if (!caught) throw "stone should freeze nested arrays" - }, - test_stone_returns_value: function() { var obj = {x: 1} var result = stone(obj) @@ -3127,7 +3103,7 @@ return { var obj = {x: 1} stone(obj) stone(obj) - if (!stone.p(obj)) throw "stone should be idempotent" + if (!is_stone(obj)) throw "stone should be idempotent" }, // ============================================================================