Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 163 additions & 12 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ struct JSContext {
const char *input, size_t input_len,
const char *filename, int line, int flags, int scope_idx);
void *user_opaque;
size_t mem_limit; /* 0 = unlimited */
size_t mem_used; /* tracked allocations */
};

typedef union JSFloat64Union {
Expand Down Expand Up @@ -1552,72 +1554,184 @@ void *js_mallocz_rt(JSRuntime *rt, size_t size)
/* Throw out of memory in case of error */
void *js_calloc(JSContext *ctx, size_t count, size_t size)
{
void *ptr;
ptr = js_calloc_rt(ctx->rt, count, size);
size_t req_size;
if (unlikely(__builtin_mul_overflow(count, size, &req_size))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't use __builtin_mul_overflow, not universally supported.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right, I only put it there as a draft.
Instead of using __builtin_mul_overflow, we can #include <stdckdint.h>.
For portability, I'll add a compile-time fallback with a pure C implementation as backup.

JS_ThrowOutOfMemory(ctx);
return NULL; // overflow
}

// upfront check against budget
if (unlikely(ctx->mem_limit && req_size > (ctx->mem_limit - ctx->mem_used))) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

void *ptr = js_calloc_rt(ctx->rt, count, size);
if (unlikely(!ptr)) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

size_t actual = js_malloc_usable_size_rt(ctx->rt, ptr);

if (unlikely(ctx->mem_limit && ctx->mem_used + actual > ctx->mem_limit)) {
js_free_rt(ctx->rt, ptr);
JS_ThrowOutOfMemory(ctx);
return NULL;
}
Comment on lines +1575 to +1581
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is arguably excessive. js_malloc_usable_size_rt (wrapper around malloc_usable_size on linux) is not necessarily very cheap. Probably not worth it to avoid going a few bytes over the limit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I should only check once before the pre-allocation. Fragmentation and the actual physical size shouldn't be included in the limits. I will make the change


ctx->mem_used += actual;
return ptr;
}


/* Throw out of memory in case of error */
void *js_malloc(JSContext *ctx, size_t size)
{
void *ptr;
ptr = js_malloc_rt(ctx->rt, size);
if (unlikely(ctx->mem_limit && size > (ctx->mem_limit - ctx->mem_used))) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

void *ptr = js_malloc_rt(ctx->rt, size);
if (unlikely(!ptr)) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

size_t actual = js_malloc_usable_size_rt(ctx->rt, ptr);

if (unlikely(ctx->mem_limit && ctx->mem_used + actual > ctx->mem_limit)) {
js_free_rt(ctx->rt, ptr);
JS_ThrowOutOfMemory(ctx);
return NULL;
}

ctx->mem_used += actual;
return ptr;
}


/* Throw out of memory in case of error */
void *js_mallocz(JSContext *ctx, size_t size)
{
void *ptr;
ptr = js_mallocz_rt(ctx->rt, size);
// upfront check
if (unlikely(ctx->mem_limit && size > (ctx->mem_limit - ctx->mem_used))) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

void *ptr = js_mallocz_rt(ctx->rt, size);
if (unlikely(!ptr)) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

size_t actual = js_malloc_usable_size_rt(ctx->rt, ptr);

if (unlikely(ctx->mem_limit && ctx->mem_used + actual > ctx->mem_limit)) {
js_free_rt(ctx->rt, ptr);
JS_ThrowOutOfMemory(ctx);
return NULL;
}

ctx->mem_used += actual;
return ptr;
}


void js_free(JSContext *ctx, void *ptr)
{
if (unlikely(!ptr))
return;
size_t actual = js_malloc_usable_size(ctx, ptr);
ctx->mem_used -= actual;
js_free_rt(ctx->rt, ptr);
}

/* Throw out of memory in case of error */
void *js_realloc(JSContext *ctx, void *ptr, size_t size)
{
void *ret;
ret = js_realloc_rt(ctx->rt, ptr, size);
size_t old_size = 0;
if (ptr)
old_size = js_malloc_usable_size_rt(ctx->rt, ptr);

if (unlikely(ctx->mem_limit && size > 0 &&
size > (ctx->mem_limit - (ctx->mem_used - old_size)))) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

void *ret = js_realloc_rt(ctx->rt, ptr, size);
if (unlikely(!ret && size != 0)) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

if (likely(ret)) {
size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret);
ctx->mem_used += new_size - old_size;

if (unlikely(ctx->mem_limit && ctx->mem_used > ctx->mem_limit)) {
js_free_rt(ctx->rt, ret);
JS_ThrowOutOfMemory(ctx);
return NULL;
}
} else {
ctx->mem_used -= old_size; // realloc(...,0) frees memory
}

return ret;
}



/* store extra allocated size in *pslack if successful */
void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack)
{
void *ret;
ret = js_realloc_rt(ctx->rt, ptr, size);
size_t old_size = 0;
if (ptr)
old_size = js_malloc_usable_size_rt(ctx->rt, ptr);

// upfront check
if (unlikely(ctx->mem_limit && size > 0)) {
size_t remaining = ctx->mem_limit - (ctx->mem_used - old_size);
if (unlikely(size > remaining)) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}
}

void *ret = js_realloc_rt(ctx->rt, ptr, size);
if (unlikely(!ret && size != 0)) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}
if (pslack) {

if (likely(ret)) {
size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret);
*pslack = (new_size > size) ? new_size - size : 0;
ctx->mem_used += new_size - old_size;

if (unlikely(ctx->mem_limit && ctx->mem_used > ctx->mem_limit)) {
js_free_rt(ctx->rt, ret);
JS_ThrowOutOfMemory(ctx);
return NULL;
}

if (pslack)
*pslack = (new_size > size) ? new_size - size : 0;
} else {
// realloc with size=0 frees memory
ctx->mem_used -= old_size;
if (pslack)
*pslack = 0;
}

return ret;
}



size_t js_malloc_usable_size(JSContext *ctx, const void *ptr)
{
return js_malloc_usable_size_rt(ctx->rt, ptr);
Expand Down Expand Up @@ -2056,11 +2170,31 @@ static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char
static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char)
{
JSString *p;

// upfront budget check: string object header + chars
size_t approx_size = sizeof(JSString) +
(size_t)max_len * (is_wide_char ? 2 : 1);

if (unlikely(ctx->mem_limit && approx_size > (ctx->mem_limit - ctx->mem_used))) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}
Comment on lines +2174 to +2181
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is better handled by breaking out js_alloc_string_rt into two separate functions, one that calls js_malloc_rt and then calls the second function to initialize the string. Call said second function from here with memory allocated with js_malloc.


p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char);
if (unlikely(!p)) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}

size_t actual = js_malloc_usable_size_rt(ctx->rt, p);

if (unlikely(ctx->mem_limit && ctx->mem_used + actual > ctx->mem_limit)) {
js_free_rt(ctx->rt, p);
JS_ThrowOutOfMemory(ctx);
return NULL;
}

ctx->mem_used += actual;
return p;
}

Expand Down Expand Up @@ -2311,6 +2445,8 @@ JSContext *JS_NewContextRaw(JSRuntime *rt)
ctx->error_back_trace = JS_UNDEFINED;
ctx->error_prepare_stack = JS_UNDEFINED;
ctx->error_stack_trace_limit = js_int32(10);
ctx->mem_limit = 0;
ctx->mem_used = 0;
init_list_head(&ctx->loaded_modules);

JS_AddIntrinsicBasicObjects(ctx);
Expand Down Expand Up @@ -2379,6 +2515,21 @@ JSValue JS_GetFunctionProto(JSContext *ctx)
return js_dup(ctx->function_proto);
}

void JS_SetContextMemoryLimit(JSContext *ctx, size_t limit)
{
ctx->mem_limit = limit;
}

void JS_ResetContextMemory(JSContext *ctx)
{
ctx->mem_used = 0;
}

size_t JS_GetContextMemoryUsage(JSContext *ctx)
{
return ctx->mem_used;
}

typedef enum JSFreeModuleEnum {
JS_FREE_MODULE_ALL,
JS_FREE_MODULE_NOT_RESOLVED,
Expand Down
4 changes: 3 additions & 1 deletion quickjs.h
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,9 @@ JS_EXTERN JSRuntime *JS_GetRuntime(JSContext *ctx);
JS_EXTERN void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj);
JS_EXTERN JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id);
JS_EXTERN JSValue JS_GetFunctionProto(JSContext *ctx);

JS_EXTERN void JS_SetContextMemoryLimit(JSContext *ctx, size_t limit);
JS_EXTERN void JS_ResetContextMemory(JSContext *ctx);
JS_EXTERN size_t JS_GetContextMemoryUsage(JSContext *ctx); /* optional helper */
/* the following functions are used to select the intrinsic object to
save memory */
JS_EXTERN JSContext *JS_NewContextRaw(JSRuntime *rt);
Expand Down