quickjs-go icon indicating copy to clipboard operation
quickjs-go copied to clipboard

Encountered a segmentation fault (SIGSEGV) when trying to push a function into array.

Open foreverzmy opened this issue 9 months ago • 1 comments

Description

I encountered a segmentation fault (SIGSEGV) when trying to use quickjs-go to push a function into array.

Environment

  • Go Version: 1.24.0
  • quickjs-go Version: v0.4.15
  • Operating System: macOS 15.3.1

Error Message

SIGSEGV: segmentation violation
PC=0x1042ca938 m=0 sigcode=2 addr=0x8
signal arrived during cgo execution

Reproduction Steps

  1. Create a new Go file with the following code:
package main

import (
	"fmt"

	"github.com/buke/quickjs-go"
)

const WorkerListenerName = "__WORKED_LISTENERS__"

// Load worker script
const script = `
		addEventListener('fetch', event => {
			event.respondWith(
				new Response('Hello World!', {
					status: 200,
					headers: { 'Content-Type': 'text/plain' }
				})
			);
			return 12345;
		});
	`

func main() {
	// Create a new runtime
	rt := quickjs.NewRuntime(
		quickjs.WithExecuteTimeout(30),
		quickjs.WithMemoryLimit(128*1024),
		quickjs.WithGCThreshold(256*1024),
		quickjs.WithMaxStackSize(65534),
		quickjs.WithCanBlock(true),
	)
	defer rt.Close()

	// Create a new context
	ctx := rt.NewContext()
	defer ctx.Close()

	addEventListenerFn := ctx.Function(addEventListener)

	ctx.Globals().Set("addEventListener", addEventListenerFn)

	ret, err := ctx.Eval(script)
	if err != nil {
		println(err.Error())
	}
	defer ret.Free()
	defer addEventListenerFn.Free()
	fmt.Printf("=========\n")
	listeners := ctx.Globals().Get(WorkerListenerName)
	fmt.Println(listeners.IsObject())
	fmt.Printf("===It's Not OK===%+v\n", listeners.JSONStringify())
	fmt.Println(ret.String())

	// listeners := ctx.Globals().Get(WorkerListenerName)
	// // ss, _ := listeners.Get("fetch").ToArray().Get(0)
	// fmt.Printf("===%+v\n", listeners.JSONStringify())
}

func addEventListener(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value {
	if len(args) < 2 {
		return ctx.Undefined()
	}

	// Get event type
	eventType := args[0].String()

	// Get callback function
	callback := args[1]
	if !callback.IsFunction() {
		return ctx.Undefined()
	}

	listeners := ctx.Globals().Get(WorkerListenerName)
	if !listeners.IsObject() {
		listeners = ctx.Object()
		ctx.Globals().Set(WorkerListenerName, listeners)
	}

	if listeners.Get(string(eventType)).IsUndefined() {
		listeners.Set(string(eventType), ctx.Array().ToValue())
	}

	listener := ctx.Object()

	listener.Set("callback", callback)

	listeners.Get(eventType).ToArray().Push(listener)
	fmt.Printf("===It's OK==%+v\n", listeners.JSONStringify())

	ls := ctx.Globals().Get(WorkerListenerName)
	fmt.Printf("===It's OK==%+v\n", ls.JSONStringify())

	return ctx.Undefined()
}
  1. Run the code

Expected Behavior

The code should successfully register the event listener and store it in the global object without crashing.

Actual Behavior

The program crashes with a segmentation fault during execution.

Additional Information

  • The crash occurs when trying to handle JavaScript event listeners and store them in a global object
  • The error suggests memory access violation during CGO execution
  • The code attempts to implement a simple event listener system similar to web browsers

Possible Related Issues

Question

Is there a proper way to handle JavaScript event listeners and store them in the global context using quickjs-go? The current implementation leads to a segmentation fault.

foreverzmy avatar Feb 27 '25 03:02 foreverzmy

And It's work in C:

#include <stdio.h>
#include <string.h>
#include <stdlib.h> 
#include "./quickjs/quickjs.h"

char WorkerListenerName[] = "__WORKED_LISTENERS__";

static JSValue js_add_event_listener(JSContext *ctx, JSValueConst this_val,
                                   int argc, JSValueConst *argv) {
    if (argc != 2)
        return JS_UNDEFINED;
    
    const char* event_type = JS_ToCString(ctx, argv[0]);
    if (!event_type)
        return JS_UNDEFINED;
    
    JSValue callback = JS_DupValue(ctx, argv[1]);

    JSValue global_obj = JS_GetGlobalObject(ctx);

	JSValue listeners = JS_NewObject(ctx);


    JSValue listener = JS_NewObject(ctx);

    JS_SetPropertyStr(ctx, listener, "callback", callback);

    JSValue arr = JS_NewArray(ctx);

    JSValue pushFunc = JS_GetPropertyStr(ctx, arr, "push");

    JS_Call(ctx, pushFunc, arr, 1, &listener);

    JS_SetPropertyStr(ctx, listeners, event_type, arr);
    
    JS_SetPropertyStr(ctx, global_obj, WorkerListenerName, listeners);

    JSValue ref = JS_JSONStringify(ctx, listeners, JS_NULL, JS_NULL);

    const char *jsonStr = JS_ToCString(ctx, ref);
    printf("===: %s\n", jsonStr);

    JS_FreeCString(ctx, event_type);
    return JS_UNDEFINED;
}


int main(int argc, char **argv) {
    JSRuntime *rt = JS_NewRuntime();
    JSContext *ctx = JS_NewContext(rt);
    
    JSValue global_obj = JS_GetGlobalObject(ctx);
    
    JSValue add_event_listener_func = JS_NewCFunction(ctx, js_add_event_listener,
                                                    "addEventListener", 2);
    JS_SetPropertyStr(ctx, global_obj, "addEventListener", add_event_listener_func);
    
    const char *user_code = 
        "addEventListener('fetch', event => {\n"
        "   // event.respondWith(\n"
        "   //   new Response('Hello World!', {\n"
        "   //       status: 200,\n"
        "   //       headers: { 'Content-Type': 'text/plain' }\n"
        "   //   })\n"
        "   //);\n"
        "    return 12345;\n"
        "});";
    
    JSValue val = JS_Eval(ctx, user_code, strlen(user_code), "<input>", JS_EVAL_TYPE_GLOBAL);
    if (JS_IsException(val)) {
        JSValue exc = JS_GetException(ctx);
        const char *str = JS_ToCString(ctx, exc);
        fprintf(stderr, "Error: %s\n", str);
        JS_FreeCString(ctx, str);
        JS_FreeValue(ctx, exc);
    }


    JSValue listeners = JS_GetPropertyStr(ctx, global_obj, WorkerListenerName);

    JSValue ref = JS_JSONStringify(ctx, listeners, JS_NULL, JS_NULL);
    const char *jsonStr = JS_ToCString(ctx, ref);
    printf("===: %s\n", jsonStr);


    JSValue arr = JS_GetPropertyStr(ctx, listeners, "fetch");
    JSValue listener = JS_GetPropertyUint32(ctx, arr, 0);
    JSValue callback = JS_GetPropertyStr(ctx, listener, "callback");

    JS_BOOL isFn = JS_IsFunction(ctx, callback);
    printf("===: %d\n", isFn);

    JSValue ret = JS_Call(ctx, callback, listener, 0, NULL);
    JSValue ref2 = JS_JSONStringify(ctx, ret, JS_NULL, JS_NULL);
    const char *jsonStr2 = JS_ToCString(ctx, ref2);
    printf("===: %s\n", jsonStr2);

    JS_FreeValue(ctx, val);
    
    JS_FreeValue(ctx, global_obj);
    JS_FreeContext(ctx);
    // JS_FreeRuntime(rt);
    
    return 0;
}

foreverzmy avatar Feb 27 '25 09:02 foreverzmy

Have you tried increasing you memory limit? We are experiencing similar crash behaviour when the memory limit is too low. In you C version you have no limit set.

webmaster128 avatar May 07 '25 14:05 webmaster128

See also https://github.com/buke/quickjs-go/issues/396

webmaster128 avatar May 08 '25 16:05 webmaster128

please try not to using the collection api (such as context.Array() / value.ToArray() ), using context.Eval with js code to instead it .

and the collection api will be remove in next tag

buke avatar May 27 '25 15:05 buke

I'm going to close this issue. If there are any problems, please feel free to reopen it.

buke avatar Jun 06 '25 16:06 buke