Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

JS_SetPropertyInternal2 loses the value when a lazy property on the prototype runs out of memory

Abierto Apto para principiantes
#1,852 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 2 días

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
78/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
c

Línea de trabajo

Lee JS_SetPropertyInternal2 en quickjs.c alrededor de la línea 10729 y compara la ruta de error de autoinit del prototipo con la ruta fail de la propiedad propia alrededor de la línea 10613. Reproduce el problema con repro.c y sus instrucciones de compilación. Después, verifica que los tres casos terminen sin la aserción del runtime y conserven las propiedades lazy tras los fallos de asignación.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Seen at 7a23d8e. In JS_SetPropertyInternal2, when an autoinit property found on the prototype chain cannot be made because memory runs out (quickjs.c:10729), the function returns -1 without freeing val. The own-property case above it (quickjs.c:10613) goes to fail, which frees it. The value written is lost, and JS_FreeRuntime fails the assertion list_empty(&rt->gc_obj_list).

#1783 fixes the related problem in JS_AutoInitProperty (the property turned into undefined for good) but not this one.

Reproduce

repro.c below, linked with the library, asserts on. ./repro 2 writes an object to o.stringify, where o = Object.create(JSON), under a memory limit:

  • On master: case 2, limit +0: failed, then typeof JSON.stringify is undefined, then JS_FreeRuntime: Assertion 'list_empty(&rt->gc_obj_list)' failed.
  • With the quickjs.c part of #1783: the typeof is function now, and the assertion still fails.
repro.c
/* Running out of memory on the first access to a lazily created property.
   Build: cc -O1 -g repro.c libquickjs.a -lm -lpthread
   Run: ./repro CASE [full|but] */
#include <stdio.h>
#include <string.h>
#include "quickjs.h"

static size_t used(JSRuntime *rt)
{
    JSMemoryUsage u;
    JS_ComputeMemoryUsage(rt, &u);
    return u.malloc_size;
}

static const char *type_of(JSContext *ctx, const char *expr, char *buf, size_t n)
{
    char code[128];
    JSValue v;
    const char *s;

    snprintf(code, sizeof code, "typeof %s", expr);
    v = JS_Eval(ctx, code, strlen(code), "<repro>", JS_EVAL_TYPE_GLOBAL);
    s = JS_ToCString(ctx, v);
    snprintf(buf, n, "%s", s ? s : "?");
    JS_FreeCString(ctx, s);
    JS_FreeValue(ctx, v);
    return buf;
}

/* bellard/quickjs takes small blocks from arenas that are counted
   as a whole, so a limit alone does not make them fail. fill() takes every
   free slot (one block of each size in turn, until no size can be had), and
   run() gives back the last `keep` blocks before the access. */
static void *blocks[1 << 20];
static size_t sizes[1 << 20];
static int nblocks;

static void fill(JSRuntime *rt)
{
    int any = 1;
    size_t size;

    nblocks = 0;
    while (any && nblocks < (1 << 20) - 64) {
        any = 0;
        for (size = 8; size <= 496; size += 8) {
            void *p = js_malloc_rt(rt, size);
            if (p) {
                sizes[nblocks] = size;
                blocks[nblocks++] = p;
                any = 1;
            }
        }
    }
}

/* give back every block but those of one size */
static void unfill_but(JSRuntime *rt, size_t size)
{
    int i, j = 0;

    for (i = 0; i < nblocks; i++) {
        if (sizes[i] == size) {
            sizes[j] = sizes[i];
            blocks[j++] = blocks[i];
        } else {
            js_free_rt(rt, blocks[i]);
        }
    }
    nblocks = j;
}

static void unfill(JSRuntime *rt, int keep_from)
{
    while (nblocks > keep_from)
        js_free_rt(rt, blocks[--nblocks]);
}

/* case 0: read JSON.parse. case 1: read the global decodeURIComponent.
   case 2: write an object to o.stringify, o = Object.create(JSON). */
static void run(int which, size_t extra, int full)
{
    JSRuntime *rt = JS_NewRuntime();
    JSContext *ctx = JS_NewContext(rt);
    JSValue global = JS_GetGlobalObject(ctx);
    JSValue json = JS_GetPropertyStr(ctx, global, "JSON");
    JSValue o = JS_NewObjectProto(ctx, json);
    JSValue obj = JS_NewObject(ctx);
    JSValue r;
    char buf[32];
    int failed;

    JS_SetMemoryLimit(rt, used(rt) + (full ? 0 : extra));
    if (full == 2) {
        fill(rt);
        unfill_but(rt, extra);
    } else if (full) {
        fill(rt);
        unfill(rt, nblocks - (int)extra);
    }
    if (which == 0) {
        r = JS_GetPropertyStr(ctx, json, "parse");
        failed = JS_IsException(r);
        JS_FreeValue(ctx, r);
    } else if (which == 1) {
        r = JS_GetPropertyStr(ctx, global, "decodeURIComponent");
        failed = JS_IsException(r);
        JS_FreeValue(ctx, r);
    } else {
        failed = JS_SetPropertyStr(ctx, o, "stringify", obj) < 0;
        obj = JS_UNDEFINED;
    }
    JS_FreeValue(ctx, JS_GetException(ctx));
    if (full)
        unfill(rt, 0);
    JS_SetMemoryLimit(rt, -1);
    if (failed)
        printf("case %d, %s%zu: failed, then typeof %s is %s\n", which, full == 2 ? "full heap but for blocks of size " : full ? "full heap, blocks given back " : "limit +", extra,
               which == 1 ? "decodeURIComponent" : which == 0 ? "JSON.parse" : "JSON.stringify",
               type_of(ctx, which == 1 ? "decodeURIComponent" : which == 0 ? "JSON.parse" : "JSON.stringify", buf, sizeof buf));
    fflush(stdout);
    JS_FreeValue(ctx, obj);
    JS_FreeValue(ctx, o);
    JS_FreeValue(ctx, json);
    JS_FreeValue(ctx, global);
    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);   /* asserts that no object is left */
}

int main(int argc, char **argv)
{
    int which = argc > 1 ? argv[1][0] - '0' : 0;
    /* a second argument: "full" gives back 0 to 300 blocks of a full heap,
       "but" leaves the blocks of one size full, for each size */
    int full = argc > 2 ? (strcmp(argv[2], "but") ? 1 : 2) : 0;
    size_t extra;

    if (full == 2) {
        for (extra = 8; extra <= 496; extra += 8)
            run(which, extra, full);
    } else {
        for (extra = 0; extra <= (full ? 300 : 2048); extra += full ? 1 : 8)
            run(which, extra, full);
    }
    printf("case %d: done\n", which);
    return 0;
}
A fix
             } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
                 /* Instantiate property and retry (potentially useless) */
                 if (JS_AutoInitProperty(ctx, p1, prop, pr, prs))
-                    return -1;
+                    goto fail;
                 goto retry2;

With this and the quickjs.c part of #1783, ./repro 0, ./repro 1 and ./repro 2 each fail 27 times and run to the end, with typeof ... is function after every failure and no assertion.

Lenguaje dominante
C
Estrellas
3.9k
Forks
389
Merge medio
2 d 9 h
PR fusionados (30 d)
19

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de quickjs-ng/quickjs

Todos los issues de quickjs-ng/quickjs

Issues similares

Más issues de C

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.