From df07069c38e07f3eee2f2d21972def4bff5106b6 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 6 Jan 2026 09:16:15 -0600 Subject: [PATCH] return null in bad retrieval, throw on bad insert --- source/quickjs.c | 46 +++++++++++++++++++++++----------- tests/suite.cm | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index 6f48575c..0b895136 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -7572,10 +7572,10 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, signed_idx = JS_VALUE_GET_INT(prop); switch(p->class_id) { case JS_CLASS_ARRAY: - /* arrays require non-negative numeric index */ + /* arrays require non-negative numeric index - return null for invalid */ if (signed_idx < 0) { JS_FreeValue(ctx, prop); - return JS_ThrowTypeError(ctx, "array index must be non-negative"); + return JS_NULL; } idx = (uint32_t)signed_idx; if (unlikely(idx >= p->u.array.count)) goto slow_path; @@ -7585,34 +7585,41 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, } } else { slow_path: - /* Type checking for array vs object indexing */ + /* Type checking for array vs object indexing - return null for invalid retrieval */ if (JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(this_obj); if (p->class_id == JS_CLASS_ARRAY) { - /* Arrays require numeric index */ + /* Arrays require numeric index - return null for non-numeric */ if (prop_tag != JS_TAG_INT && !JS_TAG_IS_FLOAT64(prop_tag)) { JS_FreeValue(ctx, prop); - return JS_ThrowTypeError(ctx, "array index must be a number"); + return JS_NULL; } - /* Check for negative index */ + /* Return null for negative index */ if (prop_tag == JS_TAG_INT) { if (JS_VALUE_GET_INT(prop) < 0) { JS_FreeValue(ctx, prop); - return JS_ThrowTypeError(ctx, "array index must be non-negative"); + return JS_NULL; } } else { double d = JS_VALUE_GET_FLOAT64(prop); if (d < 0) { JS_FreeValue(ctx, prop); - return JS_ThrowTypeError(ctx, "array index must be non-negative"); + return JS_NULL; } } } else { - /* Objects require text or object (symbol) key - NOT numbers */ - if (prop_tag != JS_TAG_STRING && prop_tag != JS_TAG_STRING_ROPE && - prop_tag != JS_TAG_SYMBOL && prop_tag != JS_TAG_OBJECT) { + /* Objects require text or non-array object (symbol) key - return null for invalid */ + if (prop_tag == JS_TAG_OBJECT) { + /* Check if it's an array - arrays not allowed as object keys */ + JSObject *key_obj = JS_VALUE_GET_OBJ(prop); + if (key_obj->class_id == JS_CLASS_ARRAY) { + JS_FreeValue(ctx, prop); + return JS_NULL; + } + } else if (prop_tag != JS_TAG_STRING && prop_tag != JS_TAG_STRING_ROPE && + prop_tag != JS_TAG_SYMBOL) { JS_FreeValue(ctx, prop); - return JS_ThrowTypeError(ctx, "object key must be text or object"); + return JS_NULL; } } } @@ -8388,9 +8395,18 @@ static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, } } } else { - /* Objects require text or object (symbol) key - NOT numbers */ - if (prop_tag != JS_TAG_STRING && prop_tag != JS_TAG_STRING_ROPE && - prop_tag != JS_TAG_SYMBOL && prop_tag != JS_TAG_OBJECT) { + /* Objects require text or non-array object (symbol) key - NOT numbers */ + if (prop_tag == JS_TAG_OBJECT) { + /* Check if it's an array - arrays not allowed as object keys */ + JSObject *key_obj = JS_VALUE_GET_OBJ(prop); + if (key_obj->class_id == JS_CLASS_ARRAY) { + JS_FreeValue(ctx, prop); + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "object key must be text or object, not array"); + return -1; + } + } else if (prop_tag != JS_TAG_STRING && prop_tag != JS_TAG_STRING_ROPE && + prop_tag != JS_TAG_SYMBOL) { JS_FreeValue(ctx, prop); JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "object key must be text or object"); diff --git a/tests/suite.cm b/tests/suite.cm index 3633621b..af3fe2f9 100644 --- a/tests/suite.cm +++ b/tests/suite.cm @@ -1831,4 +1831,69 @@ return { if (!caught) throw "object should not be able to use null as key" }, + // ============================================================================ + // RETRIEVAL WITH INVALID KEY RETURNS NULL (not throw) + // ============================================================================ + + test_array_get_string_key_returns_null: function() { + var a = [1, 2, 3] + var result = a["x"] + if (result != null) throw "array get with string key should return null" + }, + + test_array_get_negative_index_returns_null: function() { + var a = [1, 2, 3] + var result = a[-1] + if (result != null) throw "array get with negative index should return null" + }, + + test_array_get_object_key_returns_null: function() { + var a = [1, 2, 3] + var k = {} + var result = a[k] + if (result != null) throw "array get with object key should return null" + }, + + test_array_get_array_key_returns_null: function() { + var a = [1, 2, 3] + var result = a[[1, 2]] + if (result != null) throw "array get with array key should return null" + }, + + test_array_get_boolean_key_returns_null: function() { + var a = [1, 2, 3] + var result = a[true] + if (result != null) throw "array get with boolean key should return null" + }, + + test_array_get_null_key_returns_null: function() { + var a = [1, 2, 3] + var result = a[null] + if (result != null) throw "array get with null key should return null" + }, + + test_obj_get_number_key_returns_null: function() { + var o = {a: 1} + var result = o[5] + if (result != null) throw "object get with number key should return null" + }, + + test_obj_get_array_key_returns_null: function() { + var o = {a: 1} + var result = o[[1, 2]] + if (result != null) throw "object get with array key should return null" + }, + + test_obj_get_boolean_key_returns_null: function() { + var o = {a: 1} + var result = o[true] + if (result != null) throw "object get with boolean key should return null" + }, + + test_obj_get_null_key_returns_null: function() { + var o = {a: 1} + var result = o[null] + if (result != null) throw "object get with null key should return null" + }, + }