Skip to content
Merged
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
19 changes: 18 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build clean
.PHONY: build build-debug clean

build:
@echo "Configuring and building qjs..."
Expand All @@ -16,6 +16,23 @@ build:

wasm-opt -O3 qjs.wasm -o qjs.wasm

build-debug:
@echo "Configuring and building qjs with runtime address debug..."
cd qjswasm/quickjs && \
rm -rf build && \
cmake -B build \
-DQJS_BUILD_LIBC=ON \
-DQJS_BUILD_CLI_WITH_MIMALLOC=OFF \
-DQJS_DEBUG_RUNTIME_ADDRESS=ON \
-DCMAKE_TOOLCHAIN_FILE=/opt/wasi-sdk/share/cmake/wasi-sdk.cmake \
-DCMAKE_PROJECT_INCLUDE=../qjswasm.cmake
@echo "Building qjs target..."
make -C qjswasm/quickjs/build qjswasm -j$(nproc)
@echo "Copying build/qjswasm to top-level as qjs.wasm..."
cp qjswasm/quickjs/build/qjswasm qjs.wasm

wasm-opt -O3 qjs.wasm -o qjs.wasm

clean:
@echo "Cleaning build directory..."
cd quickjs && rm -rf build
Expand Down
8 changes: 4 additions & 4 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,16 @@ func (o *EvalOption) Free() {
}
}

func getRuntimeOption(registry *ProxyRegistry, options ...*Option) (option *Option, err error) {
if len(options) == 0 || options[0] == nil {
option = &Option{}
func getRuntimeOption(registry *ProxyRegistry, options ...Option) (option Option, err error) {
if len(options) == 0 {
option = Option{}
} else {
option = options[0]
}

if option.CWD == "" {
if option.CWD, err = os.Getwd(); err != nil {
return nil, fmt.Errorf("cannot get current working directory: %w", err)
return Option{}, fmt.Errorf("cannot get current working directory: %w", err)
}
}

Expand Down
6 changes: 1 addition & 5 deletions options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ func TestEvalOptions(t *testing.T) {

t.Run("explicit_cwd_provided", func(t *testing.T) {
tempDir := t.TempDir()
options := &qjs.Option{
CWD: tempDir,
}

runtime, err := qjs.New(options)
runtime, err := qjs.New(qjs.Option{CWD: tempDir})
require.NoError(t, err)
runtime.Close()
})
Expand Down
2 changes: 0 additions & 2 deletions proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ func createFuncProxyWithRegistry(registry *ProxyRegistry) JsFunctionProxy {
) (rs uint64) {
goFunc, this := getProxyFuncParams(registry, module.Memory(), thisVal, argc, argv)

// Ensure panic recovery for safe execution
defer func() {
if r := recover(); r != nil {
rs = handlePanicRecovery(this, r)
Expand Down Expand Up @@ -182,7 +181,6 @@ func getProxyFuncParams(
isAsync := args[2] // The third argument is the async flag

goFunc, goContext := retrieveGoResources(registry, functionHandle, jsContextHandle)

promise := extractPromiseIfAsync(goContext, args, isAsync)
fnArgs := extractFunctionArguments(goContext, args)
this := createThisContext(goContext, thisRef, fnArgs, promise, isAsync)
Expand Down
Binary file modified qjs.wasm
Binary file not shown.
59 changes: 32 additions & 27 deletions qjswasm/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,37 @@
#include <limits.h>
#include <float.h>

#ifdef QJS_DEBUG_RUNTIME_ADDRESS
/**
* Allocates a random-sized block of memory to randomize address space layout.
* This helps in debugging by ensuring runtime objects are allocated at different
* addresses on each run, making pointer-related bugs more apparent.
*/
void randomize_address_space(void)
{
static unsigned int call_counter = 0;
int stack_variable;

// Generate entropy from multiple sources for better randomization
unsigned int entropy = (unsigned int)((uintptr_t)&stack_variable ^ // Stack address (varies per call)
(uintptr_t)time(NULL) ^ // Current time
(uintptr_t)clock() ^ // Clock ticks (higher resolution)
(++call_counter) // Incremental counter
);

// Allocate 1-1024 bytes to perturb the address space
size_t allocation_size = (entropy % 1024) + 1;
volatile void *random_allocation = malloc(allocation_size);

// Note: Intentionally not freeing to affect subsequent allocations
// Touch the allocated memory to ensure it's not optimized away
if (random_allocation)
{
*((volatile char *)random_allocation) = 0;
}
}
#endif

JSValue JS_NewNull() { return JS_NULL; }
JSValue JS_NewUndefined() { return JS_UNDEFINED; }
JSValue JS_NewUninitialized() { return JS_UNINITIALIZED; }
Expand Down Expand Up @@ -606,34 +637,8 @@ JSValue QJS_Call(JSContext *ctx, JSValue func, JSValue this, int argc, uint64_t
return JS_Call(ctx, func, this, argc, js_argv);
}

// Create a new QJS_PROXY_VALUE instance directly in C for better performance
JSValue QJS_NewProxyValue(JSContext *ctx, int64_t proxyId)
void QJS_Panic()
{
// Get the QJS_PROXY_VALUE constructor from global object
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue ctor = JS_GetPropertyStr(ctx, global_obj, "QJS_PROXY_VALUE");
JS_FreeValue(ctx, global_obj);

if (JS_IsException(ctor) || JS_IsUndefined(ctor)) {
JS_FreeValue(ctx, ctor);
return JS_ThrowReferenceError(ctx, "QJS_PROXY_VALUE is not defined");
}

// Create argument for the constructor (proxyId)
JSValue arg = JS_NewInt64(ctx, proxyId);
JSValue args[1] = { arg };

// Call the constructor with 'new'
JSValue result = JS_CallConstructor(ctx, ctor, 1, args);

// Clean up
JS_FreeValue(ctx, ctor);
JS_FreeValue(ctx, arg);

return result;
}

void QJS_Panic() {
// Handle panic situation
fprintf(stderr, "QJS Panic: Unrecoverable error occurred\n");
abort();
Expand Down
126 changes: 126 additions & 0 deletions qjswasm/proxy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#include "qjs.h"

// toString method for QJS_PROXY_VALUE class
static JSValue qjs_proxy_value_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
JSValue proxy_id = JS_GetPropertyStr(ctx, this_val, "proxyId");
if (JS_IsException(proxy_id))
return proxy_id;

const char *proxy_id_str = JS_ToCString(ctx, proxy_id);
JS_FreeValue(ctx, proxy_id);

if (!proxy_id_str)
return JS_EXCEPTION;

char buffer[256];
snprintf(buffer, sizeof(buffer), "[object QJS_PROXY_VALUE(proxyId: %s)]", proxy_id_str);
JS_FreeCString(ctx, proxy_id_str);

return JS_NewString(ctx, buffer);
}

// Constructor function for QJS_PROXY_VALUE class
static JSValue qjs_proxy_value_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
JSValue obj;
JSValue proto;

if (JS_IsUndefined(new_target))
{
// Called as function, not constructor
return JS_ThrowTypeError(ctx, "QJS_PROXY_VALUE must be called with new");
}

// Get prototype from new_target
proto = JS_GetPropertyStr(ctx, new_target, "prototype");
if (JS_IsException(proto))
return proto;

// Create object with proper prototype
obj = JS_NewObjectProto(ctx, proto);
JS_FreeValue(ctx, proto);

if (JS_IsException(obj))
return obj;

// Set the proxyId property
if (argc > 0)
{
if (JS_SetPropertyStr(ctx, obj, "proxyId", JS_DupValue(ctx, argv[0])) < 0)
{
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
}
else
{
if (JS_SetPropertyStr(ctx, obj, "proxyId", JS_UNDEFINED) < 0)
{
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
}

return obj;
}

// Initialize QJS_PROXY_VALUE class and add it to global object
int init_qjs_proxy_value_class(JSContext *ctx)
{
JSValue global_obj = JS_GetGlobalObject(ctx);

// Create prototype object with toString method
JSValue proto = JS_NewObject(ctx);
JSValue toString_func = JS_NewCFunction(ctx, qjs_proxy_value_toString, "toString", 0);
if (JS_SetPropertyStr(ctx, proto, "toString", toString_func) < 0)
{
JS_FreeValue(ctx, proto);
JS_FreeValue(ctx, global_obj);
return -1;
}

// Create the constructor function
JSValue ctor = JS_NewCFunction2(ctx, qjs_proxy_value_constructor, "QJS_PROXY_VALUE", 1, JS_CFUNC_constructor, 0);

// Set proto.constructor and ctor.prototype using QuickJS helper
JS_SetConstructor(ctx, ctor, proto);

// Add the constructor to the global object
if (JS_SetPropertyStr(ctx, global_obj, "QJS_PROXY_VALUE", ctor) < 0)
{
JS_FreeValue(ctx, global_obj);
return -1;
}

JS_FreeValue(ctx, global_obj);
return 0;
}

// Create a new QJS_PROXY_VALUE instance directly in C for better performance
JSValue QJS_NewProxyValue(JSContext *ctx, int64_t proxyId)
{
// Get the QJS_PROXY_VALUE constructor from global object
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue ctor = JS_GetPropertyStr(ctx, global_obj, "QJS_PROXY_VALUE");
JS_FreeValue(ctx, global_obj);

if (JS_IsException(ctor) || JS_IsUndefined(ctor))
{
JS_FreeValue(ctx, ctor);
return JS_ThrowReferenceError(ctx, "QJS_PROXY_VALUE is not defined");
}

// Create argument for the constructor (proxyId)
JSValue arg = JS_NewInt64(ctx, proxyId);
JSValue args[1] = {arg};

// Call the constructor with 'new'
JSValue result = JS_CallConstructor(ctx, ctor, 1, args);

// Clean up
JS_FreeValue(ctx, ctor);
JS_FreeValue(ctx, arg);

return result;
}
Loading
Loading