diff --git a/Makefile b/Makefile index e4a7a819..03e898c5 100755 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ static: # Bootstrap: build cell from scratch using meson (only needed once) # Also installs core scripts to ~/.cell/core bootstrap: - meson setup build_bootstrap -Dbuildtype=debugoptimized + meson setup build_bootstrap -Dbuildtype=debugoptimized -Db_sanitize=address meson compile -C build_bootstrap cp build_bootstrap/cell . cp build_bootstrap/libcell_runtime.dylib . diff --git a/source/quickjs.c b/source/quickjs.c index 76e81160..dcd2f8d5 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -5048,10 +5048,11 @@ static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) } } - /* Free intrinsic array (JS_TAG_ARRAY) */ static void free_array(JSRuntime *rt, JSArray *arr) { + assert(arr->header.gc_obj_type == JS_GC_OBJ_TYPE_ARRAY); + uint32_t i; for (i = 0; i < arr->len; i++) { JS_FreeValueRT(rt, arr->values[i]); @@ -5063,25 +5064,34 @@ static void free_array(JSRuntime *rt, JSArray *arr) static int js_intrinsic_array_ensure_capacity(JSContext *ctx, JSArray *arr, uint32_t min_cap) { - if (min_cap <= arr->cap) - return 0; + 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; - } + uint32_t old_cap = arr->cap; + uint32_t new_cap = old_cap ? old_cap : JS_ARRAY_INITIAL_SIZE; - JSValue *new_values = js_realloc(ctx, arr->values, sizeof(JSValue) * new_cap); - if (!new_values) - return -1; + while (new_cap < min_cap) { + if (new_cap > UINT32_MAX / 2) { new_cap = min_cap; break; } + new_cap *= 2; + } - arr->values = new_values; - arr->cap = new_cap; - return 0; + JSValue *new_values = js_realloc(ctx, arr->values, sizeof(JSValue) * new_cap); + if (!new_values) return -1; + + for (uint32_t i = old_cap; i < new_cap; i++) new_values[i] = JS_NULL; + + arr->values = new_values; + arr->cap = new_cap; + return 0; +} + +static int js_intrinsic_array_push(JSContext *ctx, JSArray *arr, JSValue val) +{ + if (js_intrinsic_array_ensure_capacity(ctx, arr, arr->len + 1) < 0) { + JS_FreeValue(ctx, val); + return -1; + } + arr->values[arr->len++] = val; + return 0; } static int js_intrinsic_array_set(JSContext *ctx, JSArray *arr, uint32_t idx, JSValue val) @@ -5109,23 +5119,6 @@ static int js_intrinsic_array_set(JSContext *ctx, JSArray *arr, uint32_t idx, JS 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) -{ - if (arr->len >= arr->cap) { - uint32_t new_cap = arr->cap ? arr->cap * 2 : JS_ARRAY_INITIAL_SIZE; - JSValue *new_values = js_realloc(ctx, arr->values, sizeof(JSValue) * new_cap); - if (!new_values) { - JS_FreeValue(ctx, val); - return -1; - } - arr->values = new_values; - arr->cap = new_cap; - } - arr->values[arr->len++] = val; - return 0; -} - static void js_c_function_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); @@ -5219,6 +5212,8 @@ static void free_object(JSRuntime *rt, JSObject *p) JSShape *sh; JSShapeProperty *pr; + assert(p->header.gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT); + p->free_mark = 1; /* used to tell the object is invalid when freeing cycles */ /* free all the fields */ @@ -7580,7 +7575,8 @@ static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop, JSValueConst func, int def_flags) { (void)def_flags; - if (JS_SetPropertyInternal(ctx, ctx->global_obj, prop, func) < 0) + /* JS_SetPropertyInternal consumes the value, so we must dup it */ + if (JS_SetPropertyInternal(ctx, ctx->global_obj, prop, JS_DupValue(ctx, func)) < 0) return -1; return 0; } @@ -8102,6 +8098,7 @@ static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) case JS_TAG_NULL: ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val)); break; + case JS_TAG_ARRAY: case JS_TAG_OBJECT: JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert object to number"); @@ -8558,6 +8555,7 @@ static JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToP return JS_AtomToString(ctx, JS_ATOM_null); case JS_TAG_EXCEPTION: return JS_EXCEPTION; + case JS_TAG_ARRAY: case JS_TAG_OBJECT: return JS_AtomToString(ctx, JS_ATOM_true); case JS_TAG_FUNCTION_BYTECODE: @@ -9245,6 +9243,9 @@ static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) case JS_GC_OBJ_TYPE_JS_CONTEXT: printf("[js_context]"); break; + case JS_GC_OBJ_TYPE_ARRAY: + printf("[array]"); + break; default: printf("[unknown %d]", p->gc_obj_type); break; @@ -9585,6 +9586,12 @@ static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, } } break; + case JS_TAG_ARRAY: + if (tag1 != tag2) + res = FALSE; + else + res = JS_VALUE_GET_PTR(op1) == JS_VALUE_GET_PTR(op2); + break; case JS_TAG_OBJECT: if (tag1 != tag2) res = FALSE; @@ -11844,9 +11851,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } ret = js_method_set_properties(ctx, sp[-1], atom, 0, obj); if (ret >= 0) { + /* JS_SetProperty consumes value, so don't free sp[-1] on success */ ret = JS_SetProperty(ctx, obj, atom, value); + } else { + JS_FreeValue(ctx, sp[-1]); } - JS_FreeValue(ctx, sp[-1]); if (is_computed) { JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, sp[-2]); diff --git a/tests/suite.cm b/tests/suite.cm index 8e9e2930..0138c9da 100644 --- a/tests/suite.cm +++ b/tests/suite.cm @@ -3462,7 +3462,9 @@ return { test_gc_cycle_array_self: function() { var arr = [] - push(arr, arr) + for (var i = 0; i < 10; i++) { + push(arr, arr) + } if (arr[0] != arr) throw "array self cycle failed" },