JS_SetPropertyInternal2 loses the value when a lazy property on the prototype runs out of memory
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
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, thenJS_FreeRuntime: Assertion 'list_empty(&rt->gc_obj_list)' failed. - With the quickjs.c part of #1783: the
typeofisfunctionnow, 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
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de quickjs-ng/quickjs
-
Array.prototype.find frees an element twice when interrupted between iterationsPosiblemente ocupada @josefguenther la tomó hace 22 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 73/100
quickjs-ng/quickjs#1792 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
Object.prototype.propertyIsEnumerable converts `this` before the property keyPosiblemente ocupada @SomSamantray la tomó hace 18 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
quickjs-ng/quickjs#1772 ·
Los mantenedores suelen responder en 2 días
-
Deserialization corrupts data or returns `undefined` when a RegExp precedes a shared object referencePosiblemente ocupada @Sree24-ui la tomó hace 22 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
quickjs-ng/quickjs#1771 · 2 comentarios ·
Los mantenedores suelen responder en 2 días
-
More native loops never consult the interrupt handler (TypedArray, String, JSON, change-array-by-copy, and some harder cases)Posiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
quickjs-ng/quickjs#1856 ·
Los mantenedores suelen responder en 2 días
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
quickjs-ng/quickjs#1853 ·
Los mantenedores suelen responder en 2 días
Todos los issues de quickjs-ng/quickjs
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
InukaAsith/TwinDesk#84 ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
CanadaHonk/porffor#426 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
FujiNetWIFI/fujinet-firmware#2221 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 77/100
raysan5/raylib.com#132 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100