From 33f5208bab02e81d64e2c319aca1685c2ff1d201 Mon Sep 17 00:00:00 2001 From: buke Date: Fri, 22 May 2026 14:51:36 +0800 Subject: [PATCH 1/2] feat(context): add no-description Symbol API - add explicit constructors for local and global symbols without descriptions - keep the existing string-based Symbol constructors unchanged - cover no-description Symbol semantics and nil/closed guards in context tests --- context.go | 25 ++++++++++++++++++------- context_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/context.go b/context.go index bb13adb..e9005ca 100644 --- a/context.go +++ b/context.go @@ -388,24 +388,35 @@ func (ctx *Context) NewDate(epochMS float64) *Value { return &Value{ctx: ctx, ref: C.JS_NewDate(ctx.ref, C.double(epochMS))} } -// NewSymbol returns a JavaScript local symbol. -func (ctx *Context) NewSymbol(description string) *Value { +func (ctx *Context) newSymbol(description *C.char, isGlobal bool) *Value { if !ctx.hasValidRef() { return nil } + return &Value{ctx: ctx, ref: C.JS_NewSymbol(ctx.ref, description, C.bool(isGlobal))} +} + +// NewSymbol returns a JavaScript local symbol. +func (ctx *Context) NewSymbol(description string) *Value { desc := C.CString(description) defer C.free(unsafe.Pointer(desc)) - return &Value{ctx: ctx, ref: C.JS_NewSymbol(ctx.ref, desc, C.bool(false))} + return ctx.newSymbol(desc, false) +} + +// NewSymbolWithoutDescription returns a JavaScript local symbol created with Symbol(). +func (ctx *Context) NewSymbolWithoutDescription() *Value { + return ctx.newSymbol(nil, false) } // NewGlobalSymbol returns a JavaScript global symbol. func (ctx *Context) NewGlobalSymbol(description string) *Value { - if !ctx.hasValidRef() { - return nil - } desc := C.CString(description) defer C.free(unsafe.Pointer(desc)) - return &Value{ctx: ctx, ref: C.JS_NewSymbol(ctx.ref, desc, C.bool(true))} + return ctx.newSymbol(desc, true) +} + +// NewGlobalSymbolWithoutDescription returns a JavaScript global symbol created with Symbol.for("undefined"). +func (ctx *Context) NewGlobalSymbolWithoutDescription() *Value { + return ctx.newSymbol(nil, true) } // String returns a string value with given string. diff --git a/context_test.go b/context_test.go index 7e529d2..b46028c 100644 --- a/context_test.go +++ b/context_test.go @@ -357,14 +357,24 @@ func TestContextLibcStage4Helpers(t *testing.T) { require.NotNil(t, localSym) require.True(t, localSym.IsSymbol()) + localSymNoDesc := ctx.NewSymbolWithoutDescription() + require.NotNil(t, localSymNoDesc) + require.True(t, localSymNoDesc.IsSymbol()) + globalSym := ctx.NewGlobalSymbol("global-key") require.NotNil(t, globalSym) require.True(t, globalSym.IsSymbol()) + globalSymNoDesc := ctx.NewGlobalSymbolWithoutDescription() + require.NotNil(t, globalSymNoDesc) + require.True(t, globalSymNoDesc.IsSymbol()) + globals := ctx.Globals() require.NotNil(t, globals) globals.Set("_localSym", localSym) + globals.Set("_localSymNoDesc", localSymNoDesc) globals.Set("_globalSym", globalSym) + globals.Set("_globalSymNoDesc", globalSymNoDesc) localKey := ctx.Eval(`Symbol.keyFor(globalThis._localSym)`) require.NotNil(t, localKey) @@ -377,6 +387,18 @@ func TestContextLibcStage4Helpers(t *testing.T) { require.False(t, globalKey.IsException()) require.Equal(t, "global-key", globalKey.ToString()) + localNoDescSemantics := ctx.Eval(`globalThis._localSymNoDesc.description === undefined && Symbol.keyFor(globalThis._localSymNoDesc) === undefined`) + require.NotNil(t, localNoDescSemantics) + defer localNoDescSemantics.Free() + require.False(t, localNoDescSemantics.IsException()) + require.True(t, localNoDescSemantics.ToBool()) + + globalNoDescSemantics := ctx.Eval(`globalThis._globalSymNoDesc.description === "undefined" && Symbol.keyFor(globalThis._globalSymNoDesc) === "undefined"`) + require.NotNil(t, globalNoDescSemantics) + defer globalNoDescSemantics.Free() + require.False(t, globalNoDescSemantics.IsException()) + require.True(t, globalNoDescSemantics.ToBool()) + moduleFunc := ctx.Eval(`export const v = 1`, EvalFlagModule(true), EvalFlagCompileOnly(true)) require.NotNil(t, moduleFunc) defer moduleFunc.Free() @@ -411,7 +433,9 @@ func TestContextLibcStage4HelpersNilAndClosedGuards(t *testing.T) { var nilCtx *Context require.Nil(t, nilCtx.NewDate(0)) require.Nil(t, nilCtx.NewSymbol("x")) + require.Nil(t, nilCtx.NewSymbolWithoutDescription()) require.Nil(t, nilCtx.NewGlobalSymbol("x")) + require.Nil(t, nilCtx.NewGlobalSymbolWithoutDescription()) require.False(t, nilCtx.SetImportMeta(nil, false, false)) require.False(t, nilCtx.BootstrapBJSON()) require.Equal(t, -1, nilCtx.LoopOnce()) @@ -430,7 +454,9 @@ func TestContextLibcStage4HelpersNilAndClosedGuards(t *testing.T) { ctx.Close() require.Nil(t, ctx.NewDate(0)) require.Nil(t, ctx.NewSymbol("x")) + require.Nil(t, ctx.NewSymbolWithoutDescription()) require.Nil(t, ctx.NewGlobalSymbol("x")) + require.Nil(t, ctx.NewGlobalSymbolWithoutDescription()) require.False(t, ctx.SetImportMeta(moduleFunc, false, false)) require.False(t, ctx.BootstrapBJSON()) require.Equal(t, -1, ctx.LoopOnce()) From 3188aea590b4f02b1127073356213e8c598773c1 Mon Sep 17 00:00:00 2001 From: buke Date: Fri, 22 May 2026 15:10:54 +0800 Subject: [PATCH 2/2] fix(context): avoid Symbol CString allocation on invalid context - move hasValidRef checks before C.CString in Symbol constructors - keep the shared helper guard in place as a defensive fallback - preserve existing behavior while removing unnecessary C allocations --- context.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/context.go b/context.go index e9005ca..3acb6d0 100644 --- a/context.go +++ b/context.go @@ -397,6 +397,9 @@ func (ctx *Context) newSymbol(description *C.char, isGlobal bool) *Value { // NewSymbol returns a JavaScript local symbol. func (ctx *Context) NewSymbol(description string) *Value { + if !ctx.hasValidRef() { + return nil + } desc := C.CString(description) defer C.free(unsafe.Pointer(desc)) return ctx.newSymbol(desc, false) @@ -409,6 +412,9 @@ func (ctx *Context) NewSymbolWithoutDescription() *Value { // NewGlobalSymbol returns a JavaScript global symbol. func (ctx *Context) NewGlobalSymbol(description string) *Value { + if !ctx.hasValidRef() { + return nil + } desc := C.CString(description) defer C.free(unsafe.Pointer(desc)) return ctx.newSymbol(desc, true)