Skip to content

Commit 5f91729

Browse files
authored
Improve computed property lookup error messages (#1179)
QuickJS reported "cannot read property of null" for computed property lookups (`a[k]` where `a === null`), which is markedly less helpful than "cannot read property 'foo' of null".
1 parent 76b3b12 commit 5f91729

File tree

2 files changed

+83
-17
lines changed

2 files changed

+83
-17
lines changed

quickjs.c

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,12 @@ typedef struct JSMapState {
530530
resize is needed */
531531
} JSMapState;
532532

533+
enum
534+
{
535+
JS_TO_STRING_IS_PROPERTY_KEY = 1 << 0,
536+
JS_TO_STRING_NO_SIDE_EFFECTS = 1 << 1,
537+
};
538+
533539
enum {
534540
JS_ATOM_TYPE_STRING = 1,
535541
JS_ATOM_TYPE_GLOBAL_SYMBOL,
@@ -1199,6 +1205,8 @@ static int JS_ToBoolFree(JSContext *ctx, JSValue val);
11991205
static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val);
12001206
static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val);
12011207
static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val);
1208+
static JSValue JS_ToPropertyKeyInternal(JSContext *ctx, JSValueConst val,
1209+
int flags);
12021210
static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len);
12031211
static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern,
12041212
JSValueConst flags);
@@ -8364,7 +8372,8 @@ static JSAtom js_symbol_to_atom(JSContext *ctx, JSValueConst val)
83648372
}
83658373

83668374
/* return JS_ATOM_NULL in case of exception */
8367-
JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val)
8375+
static JSAtom JS_ValueToAtomInternal(JSContext *ctx, JSValueConst val,
8376+
int flags)
83688377
{
83698378
JSAtom atom;
83708379
uint32_t tag;
@@ -8378,7 +8387,7 @@ JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val)
83788387
atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p));
83798388
} else {
83808389
JSValue str;
8381-
str = JS_ToPropertyKey(ctx, val);
8390+
str = JS_ToPropertyKeyInternal(ctx, val, flags);
83828391
if (JS_IsException(str))
83838392
return JS_ATOM_NULL;
83848393
if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) {
@@ -8390,6 +8399,11 @@ JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val)
83908399
return atom;
83918400
}
83928401

8402+
JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val)
8403+
{
8404+
return JS_ValueToAtomInternal(ctx, val, /*flags*/0);
8405+
}
8406+
83938407
static bool js_get_fast_array_element(JSContext *ctx, JSObject *p,
83948408
uint32_t idx, JSValue *pval)
83958409
{
@@ -8466,15 +8480,21 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj,
84668480
if (js_get_fast_array_element(ctx, p, idx, &val))
84678481
return val;
84688482
}
8469-
} else {
8470-
switch(tag) {
8471-
case JS_TAG_NULL:
8472-
JS_FreeValue(ctx, prop);
8473-
return JS_ThrowTypeError(ctx, "cannot read property of null");
8474-
case JS_TAG_UNDEFINED:
8475-
JS_FreeValue(ctx, prop);
8476-
return JS_ThrowTypeError(ctx, "cannot read property of undefined");
8483+
} else if (unlikely(tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)) {
8484+
// per spec: not allowed to call ToPropertyKey before ToObject
8485+
// so we must ensure to not invoke JS anything that's observable
8486+
// from JS code
8487+
atom = JS_ValueToAtomInternal(ctx, prop, JS_TO_STRING_NO_SIDE_EFFECTS);
8488+
JS_FreeValue(ctx, prop);
8489+
if (unlikely(atom == JS_ATOM_NULL))
8490+
return JS_EXCEPTION;
8491+
if (tag == JS_TAG_NULL) {
8492+
JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", atom);
8493+
} else {
8494+
JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", atom);
84778495
}
8496+
JS_FreeAtom(ctx, atom);
8497+
return JS_EXCEPTION;
84788498
}
84798499
atom = JS_ValueToAtom(ctx, prop);
84808500
JS_FreeValue(ctx, prop);
@@ -12887,8 +12907,8 @@ static JSValue js_dtoa2(JSContext *ctx,
1288712907
return res;
1288812908
}
1288912909

12890-
JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val,
12891-
bool is_ToPropertyKey)
12910+
static JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val,
12911+
int flags)
1289212912
{
1289312913
uint32_t tag;
1289412914
char buf[32];
@@ -12911,20 +12931,22 @@ JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val,
1291112931
case JS_TAG_EXCEPTION:
1291212932
return JS_EXCEPTION;
1291312933
case JS_TAG_OBJECT:
12914-
{
12934+
if (flags & JS_TO_STRING_NO_SIDE_EFFECTS) {
12935+
return js_new_string8(ctx, "{}");
12936+
} else {
1291512937
JSValue val1, ret;
1291612938
val1 = JS_ToPrimitive(ctx, val, HINT_STRING);
1291712939
if (JS_IsException(val1))
1291812940
return val1;
12919-
ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey);
12941+
ret = JS_ToStringInternal(ctx, val1, flags);
1292012942
JS_FreeValue(ctx, val1);
1292112943
return ret;
1292212944
}
1292312945
break;
1292412946
case JS_TAG_FUNCTION_BYTECODE:
1292512947
return js_new_string8(ctx, "[function bytecode]");
1292612948
case JS_TAG_SYMBOL:
12927-
if (is_ToPropertyKey) {
12949+
if (flags & JS_TO_STRING_IS_PROPERTY_KEY) {
1292812950
return js_dup(val);
1292912951
} else {
1293012952
return JS_ThrowTypeError(ctx, "cannot convert symbol to string");
@@ -12944,7 +12966,7 @@ JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val,
1294412966

1294512967
JSValue JS_ToString(JSContext *ctx, JSValueConst val)
1294612968
{
12947-
return JS_ToStringInternal(ctx, val, false);
12969+
return JS_ToStringInternal(ctx, val, /*flags*/0);
1294812970
}
1294912971

1295012972
static JSValue JS_ToStringFree(JSContext *ctx, JSValue val)
@@ -12962,9 +12984,15 @@ static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val)
1296212984
return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL);
1296312985
}
1296412986

12987+
static JSValue JS_ToPropertyKeyInternal(JSContext *ctx, JSValueConst val,
12988+
int flags)
12989+
{
12990+
return JS_ToStringInternal(ctx, val, flags | JS_TO_STRING_IS_PROPERTY_KEY);
12991+
}
12992+
1296512993
JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val)
1296612994
{
12967-
return JS_ToStringInternal(ctx, val, true);
12995+
return JS_ToPropertyKeyInternal(ctx, val, /*flags*/0);
1296812996
}
1296912997

1297012998
static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val)

tests/null_or_undefined.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {assert} from "./assert.js"
2+
3+
let ex
4+
try {
5+
null.x
6+
} catch (e) {
7+
ex = e
8+
}
9+
assert(ex instanceof TypeError)
10+
assert(ex.message, "cannot read property 'x' of null")
11+
ex = undefined
12+
13+
try {
14+
null["x"]
15+
} catch (e) {
16+
ex = e
17+
}
18+
assert(ex instanceof TypeError)
19+
assert(ex.message, "cannot read property 'x' of null")
20+
ex = undefined
21+
22+
try {
23+
undefined.x
24+
} catch (e) {
25+
ex = e
26+
}
27+
assert(ex instanceof TypeError)
28+
assert(ex.message, "cannot read property 'x' of undefined")
29+
ex = undefined
30+
31+
try {
32+
undefined["x"]
33+
} catch (e) {
34+
ex = e
35+
}
36+
assert(ex instanceof TypeError)
37+
assert(ex.message, "cannot read property 'x' of undefined")
38+
ex = undefined

0 commit comments

Comments
 (0)