Documentation Index
Fetch the complete documentation index at: https://mintlify.com/quickjs-ng/quickjs/llms.txt
Use this file to discover all available pages before exploring further.
Reference Counting
QuickJS uses reference counting for memory management of JavaScript values. Understanding and correctly using these functions is critical to prevent memory leaks and use-after-free bugs.
Overview
JSValue represents a JavaScript value which can be a primitive type or an object. Values with reference counts (objects, strings, etc.) must be explicitly managed:
- Increment the reference count when you want to keep a value:
JS_DupValue()
- Decrement the reference count when done with a value:
JS_FreeValue()
Primitive values like numbers, booleans, null, and undefined don’t have reference counts and don’t need to be freed, but calling JS_FreeValue() on them is safe and does nothing.
Reference Counting Rules
The QuickJS API follows these conventions:
Function Parameters
JSValue parameter: Function takes ownership; caller must NOT call JS_FreeValue()
JSValueConst parameter: Function does NOT take ownership; caller MUST call JS_FreeValue() later
Return Values
- Function returning
JSValue: Transfers ownership to caller; caller MUST call JS_FreeValue()
- Function returning
JSValueConst: Does NOT transfer ownership; caller must NOT call JS_FreeValue()
Core Functions
JS_FreeValue
Decrements the reference count of a value and frees it if the count reaches zero.
JS_EXTERN void JS_FreeValue(JSContext *ctx, JSValue v);
Parameters:
ctx - JavaScript context
v - Value to free
Example:
JSValue str = JS_NewString(ctx, "hello");
// ... use str ...
JS_FreeValue(ctx, str);
Note: It is safe to call this on values without reference counts (numbers, booleans, etc.). It is NOT safe to call this on uninitialized values.
JS_FreeValueRT
Decrements the reference count using the runtime instead of a context.
JS_EXTERN void JS_FreeValueRT(JSRuntime *rt, JSValue v);
Parameters:
rt - JavaScript runtime
v - Value to free
Use Case: Used in finalizers and other situations where you have the runtime but not a context.
Example:
static void js_point_finalizer(JSRuntime *rt, JSValue val)
{
JSPointData *s = JS_GetOpaque(val, js_point_class_id);
js_free_rt(rt, s);
}
JS_DupValue
Increments the reference count of a value.
JS_EXTERN JSValue JS_DupValue(JSContext *ctx, JSValueConst v);
Parameters:
ctx - JavaScript context
v - Value to duplicate
Returns: The same value with incremented reference count
Example:
JSValue stored_value = JS_DupValue(ctx, input_value);
// Now you own a reference and must free it later
JS_FreeValue(ctx, stored_value);
JS_DupValueRT
Increments the reference count using the runtime.
JS_EXTERN JSValue JS_DupValueRT(JSRuntime *rt, JSValueConst v);
Parameters:
rt - JavaScript runtime
v - Value to duplicate
Returns: The same value with incremented reference count
Common Patterns
Creating and Freeing Values
JSValue val = JS_NewString(ctx, "test");
if (JS_IsException(val))
return JS_EXCEPTION;
// ... use val ...
JS_FreeValue(ctx, val);
Passing Values to Functions
When a function takes JSValue (not JSValueConst), it takes ownership:
JSValue val = JS_NewInt32(ctx, 42);
JS_SetPropertyStr(ctx, obj, "count", val);
// Do NOT free val - JS_SetPropertyStr took ownership
When a function takes JSValueConst, it does NOT take ownership:
JSValue val = JS_NewInt32(ctx, 42);
int result = JS_ToInt32(ctx, &n, val);
// MUST free val - JS_ToInt32 didn't take ownership
JS_FreeValue(ctx, val);
Returning Values from C Functions
C functions that return JSValue must return a newly allocated value:
static JSValue js_point_get_xy(JSContext *ctx, JSValue this_val, int magic)
{
JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id);
if (!s)
return JS_EXCEPTION;
if (magic == 0)
return JS_NewInt32(ctx, s->x); // Returns new value
else
return JS_NewInt32(ctx, s->y); // Returns new value
}
Storing Values
When storing a value in a data structure, duplicate it:
typedef struct {
JSValue callback;
} MyData;
// Storing the value
MyData *data = malloc(sizeof(MyData));
data->callback = JS_DupValue(ctx, callback_arg);
// Later, when cleaning up
JS_FreeValue(ctx, data->callback);
free(data);
Error Handling
When an error occurs, free any values you’ve created:
JSValue obj = JS_NewObject(ctx);
if (JS_IsException(obj))
return JS_EXCEPTION;
JSValue val = JS_NewString(ctx, "test");
if (JS_IsException(val)) {
JS_FreeValue(ctx, obj); // Clean up obj before returning
return JS_EXCEPTION;
}
if (JS_SetPropertyStr(ctx, obj, "name", val) < 0) {
// val was freed by JS_SetPropertyStr (it takes ownership)
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
return obj;
Using goto for Cleanup
A common pattern for complex initialization:
static JSValue js_point_ctor(JSContext *ctx, JSValue new_target,
int argc, JSValue *argv)
{
JSPointData *s;
JSValue obj = JS_UNDEFINED;
JSValue proto;
s = js_mallocz(ctx, sizeof(*s));
if (!s)
return JS_EXCEPTION;
if (JS_ToInt32(ctx, &s->x, argv[0]))
goto fail;
if (JS_ToInt32(ctx, &s->y, argv[1]))
goto fail;
proto = JS_GetPropertyStr(ctx, new_target, "prototype");
if (JS_IsException(proto))
goto fail;
obj = JS_NewObjectProtoClass(ctx, proto, js_point_class_id);
JS_FreeValue(ctx, proto);
if (JS_IsException(obj))
goto fail;
JS_SetOpaque(obj, s);
return obj;
fail:
js_free(ctx, s);
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
Reference Count Checks
JS_VALUE_HAS_REF_COUNT
Checks if a value has a reference count.
#define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST)
Example:
if (JS_VALUE_HAS_REF_COUNT(val)) {
// Value has a reference count
}
Debugging Reference Counting Issues
QuickJS provides dump flags to help debug memory issues:
JS_SetDumpFlags(rt, JS_DUMP_LEAKS); // Dump leaked objects
JS_SetDumpFlags(rt, JS_DUMP_FREE); // Dump every object free
JS_SetDumpFlags(rt, JS_DUMP_GC_FREE); // Dump objects freed by GC
Enable these before JS_FreeRuntime() to see what wasn’t properly freed.
Common Mistakes
Double Free
// WRONG - double free
JSValue val = JS_NewString(ctx, "test");
JS_SetPropertyStr(ctx, obj, "name", val);
JS_FreeValue(ctx, val); // ERROR: JS_SetPropertyStr took ownership
Memory Leak
// WRONG - memory leak
JSValue val = JS_GetPropertyStr(ctx, obj, "name");
// ... use val ...
// ERROR: Forgot to call JS_FreeValue(ctx, val)
Using After Free
// WRONG - use after free
JSValue val = JS_NewString(ctx, "test");
JS_FreeValue(ctx, val);
JS_SetPropertyStr(ctx, obj, "name", val); // ERROR: val was already freed
Not Duplicating Stored Values
// WRONG - storing without duplicating
struct MyData {
JSValue callback;
};
MyData *data = malloc(sizeof(MyData));
data->callback = callback_arg; // ERROR: Should use JS_DupValue
// Later when callback_arg is freed, data->callback becomes invalid
Best Practices
- Always match creation with destruction: Every
JS_New* should have a corresponding JS_FreeValue
- Check function signatures:
JSValue parameters take ownership, JSValueConst don’t
- Duplicate when storing: Use
JS_DupValue() when storing a value in a data structure
- Use goto for cleanup: In complex functions, use goto to ensure proper cleanup on error
- Free in reverse order: When freeing multiple values, free them in reverse order of creation
- Check for exceptions: Always check return values for
JS_EXCEPTION before using them
- Use the build-time checker: Compile with
-DJS_CHECK_JSVALUE during development to catch type mismatches