]> git.kaiwu.me - quickjs.git/commitdiff
avoid side effects in JS_PrintValue() which may lead to crashes in print() and js_std...
authorFabrice Bellard <fabrice@bellard.org>
Mon, 25 Aug 2025 14:48:36 +0000 (16:48 +0200)
committerFabrice Bellard <fabrice@bellard.org>
Mon, 25 Aug 2025 14:48:36 +0000 (16:48 +0200)
quickjs.c

index 9be149de8a016de1b2bfb19e5aaba454a044e4b7..b3fd19ffbea89930271100448595714a0ea4b2fb 100644 (file)
--- a/quickjs.c
+++ b/quickjs.c
@@ -6877,20 +6877,30 @@ static int find_line_num(JSContext *ctx, JSFunctionBytecode *b,
     return 0;
 }
 
-/* in order to avoid executing arbitrary code during the stack trace
-   generation, we only look at simple 'name' properties containing a
-   string. */
-static const char *get_func_name(JSContext *ctx, JSValueConst func)
+/* return a string property without executing arbitrary JS code (used
+   when dumping the stack trace or in debug print). */
+static const char *get_prop_string(JSContext *ctx, JSValueConst obj, JSAtom prop)
 {
+    JSObject *p;
     JSProperty *pr;
     JSShapeProperty *prs;
     JSValueConst val;
 
-    if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)
-        return NULL;
-    prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name);
-    if (!prs)
+    if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
         return NULL;
+    p = JS_VALUE_GET_OBJ(obj);
+    prs = find_own_property(&pr, p, prop);
+    if (!prs) {
+        /* we look at one level in the prototype to handle the 'name'
+           field of the Error objects */
+        p = p->shape->proto;
+        if (!p)
+            return NULL;
+        prs = find_own_property(&pr, p, prop);
+        if (!prs)
+            return NULL;
+    }
+    
     if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL)
         return NULL;
     val = pr->u.value;
@@ -6943,7 +6953,7 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj,
             backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL;
             continue;
         }
-        func_name_str = get_func_name(ctx, sf->cur_func);
+        func_name_str = get_prop_string(ctx, sf->cur_func, JS_ATOM_name);
         if (!func_name_str || func_name_str[0] == '\0')
             str1 = "<anonymous>";
         else
@@ -13154,24 +13164,17 @@ static void js_print_string(JSPrintValueState *s, JSValueConst val)
     }
 }
 
-static void js_print_raw_string2(JSPrintValueState *s, JSValueConst val, BOOL remove_last_lf)
+static void js_print_raw_string(JSPrintValueState *s, JSValueConst val)
 {
     const char *cstr;
     size_t len;
     cstr = JS_ToCStringLen(s->ctx, &len, val);
     if (cstr) {
-        if (remove_last_lf && len > 0 && cstr[len - 1] == '\n')
-            len--;
         s->write_func(s->write_opaque, cstr, len);
         JS_FreeCString(s->ctx, cstr);
     }
 }
 
-static void js_print_raw_string(JSPrintValueState *s, JSValueConst val)
-{
-    js_print_raw_string2(s, val, FALSE);
-}
-
 static BOOL is_ascii_ident(const JSString *p)
 {
     int i, c;
@@ -13257,6 +13260,104 @@ static void js_print_more_items(JSPrintValueState *s, int *pcomma_state,
     js_printf(s, "... %u more item%s", n, n > 1 ? "s" : "");
 }
 
+/* similar to js_regexp_toString() but without side effect */
+static void js_print_regexp(JSPrintValueState *s, JSObject *p1)
+{
+    JSRegExp *re = &p1->u.regexp;
+    JSString *p;
+    int i, n, c, c2, bra, flags;
+    static const char regexp_flags[] = { 'g', 'i', 'm', 's', 'u', 'y', 'd', 'v' };
+
+    p = re->pattern;
+    js_putc(s, '/');
+    if (p->len == 0) {
+        js_puts(s, "(?:)");
+    } else {
+        bra = 0;
+        for (i = 0, n = p->len; i < n;) {
+            c2 = -1;
+            switch (c = string_get(p, i++)) {
+            case '\\':
+                if (i < n)
+                    c2 = string_get(p, i++);
+                break;
+            case ']':
+                bra = 0;
+                break;
+            case '[':
+                if (!bra) {
+                    if (i < n && string_get(p, i) == ']')
+                        c2 = string_get(p, i++);
+                    bra = 1;
+                }
+                break;
+            case '\n':
+                c = '\\';
+                c2 = 'n';
+                break;
+            case '\r':
+                c = '\\';
+                c2 = 'r';
+                break;
+            case '/':
+                if (!bra) {
+                    c = '\\';
+                    c2 = '/';
+                }
+                break;
+            }
+            js_putc(s, c);
+            if (c2 >= 0)
+                js_putc(s, c2);
+        }
+    }
+    js_putc(s, '/');
+
+    flags = lre_get_flags(re->bytecode->u.str8);
+    for(i = 0; i < countof(regexp_flags); i++) {
+        if ((flags >> i) & 1) {
+            js_putc(s, regexp_flags[i]);
+        }
+    }
+}
+
+/* similar to js_error_toString() but without side effect */
+static void js_print_error(JSPrintValueState *s, JSObject *p)
+{
+    const char *str;
+    size_t len;
+
+    str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_name);
+    if (!str) {
+        js_puts(s, "Error");
+    } else {
+        js_puts(s, str);
+        JS_FreeCString(s->ctx, str);
+    }
+    
+    str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_message);
+    if (str && str[0] != '\0') {
+        js_puts(s, ": ");
+        js_puts(s, str);
+    }
+    JS_FreeCString(s->ctx, str);
+
+    /* dump the stack if present */
+    str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_stack);
+    if (str) {
+        js_putc(s, '\n');
+        
+        /* XXX: should remove the last '\n' in stack as
+           v8. SpiderMonkey does not do it */
+        len = strlen(str);
+        if (len > 0 && str[len - 1] == '\n')
+            len--;
+        s->write_func(s->write_opaque, str, len);
+        
+        JS_FreeCString(s->ctx, str);
+    }
+}
+
 static void js_print_object(JSPrintValueState *s, JSObject *p)
 {
     JSRuntime *rt = s->rt;
@@ -13352,7 +13453,7 @@ static void js_print_object(JSPrintValueState *s, JSObject *p)
         if (!s->options.raw_dump && s->ctx) {
             const char *func_name_str;
             js_putc(s, ' ');
-            func_name_str = get_func_name(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p));
+            func_name_str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_name);
             if (!func_name_str || func_name_str[0] == '\0')
                 js_puts(s, "(anonymous)");
             else
@@ -13386,35 +13487,19 @@ static void js_print_object(JSPrintValueState *s, JSObject *p)
         }
         if (i < ms->record_count)
             js_print_more_items(s, &comma_state, ms->record_count - i);
-    } else if (p->class_id == JS_CLASS_REGEXP && s->ctx && !s->options.raw_dump) {
-        JSValue str = js_regexp_toString(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL);
-        if (JS_IsException(str))
-            goto default_obj;
-        js_print_raw_string(s, str);
-        JS_FreeValueRT(s->rt, str);
+    } else if (p->class_id == JS_CLASS_REGEXP && s->ctx) {
+        js_print_regexp(s, p);
         comma_state = 2;
-    } else if (p->class_id == JS_CLASS_DATE && s->ctx && !s->options.raw_dump) {
+    } else if (p->class_id == JS_CLASS_DATE && s->ctx) {
+        /* get_date_string() has no side effect */
         JSValue str = get_date_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL, 0x23); /* toISOString() */
         if (JS_IsException(str))
             goto default_obj;
         js_print_raw_string(s, str);
         JS_FreeValueRT(s->rt, str);
         comma_state = 2;
-    } else if (p->class_id == JS_CLASS_ERROR && s->ctx && !s->options.raw_dump) {
-        JSValue str = js_error_toString(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL);
-        if (JS_IsException(str))
-            goto default_obj;
-        js_print_raw_string(s, str);
-        JS_FreeValueRT(s->rt, str);
-        /* dump the stack if present */
-        str = JS_GetProperty(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_stack);
-        if (JS_IsString(str)) {
-            js_putc(s, '\n');
-            /* XXX: should remove the last '\n' in stack as
-               v8. SpiderMonkey does not do it */
-            js_print_raw_string2(s, str, TRUE);
-        }
-        JS_FreeValueRT(s->rt, str);
+    } else if (p->class_id == JS_CLASS_ERROR && s->ctx) {
+        js_print_error(s, p);
         comma_state = 2;
     } else {
         default_obj: