From 280c50a2cd70dda5725cb92c4b04017871735d6c Mon Sep 17 00:00:00 2001 From: buke Date: Fri, 22 May 2026 17:22:22 +0800 Subject: [PATCH 1/4] refactor(value): align native type checks with quickjs-ng - switch promise, typed array, and array buffer detection to native quickjs-ng APIs - add native collection, proxy, and immutable ArrayBuffer helpers with regression tests - cover value.go changes with focused tests and coverage validation --- context.go | 8 +++ value.go | 150 ++++++++++++++++++++++++++++++++++++-------------- value_test.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+), 41 deletions(-) diff --git a/context.go b/context.go index 635657a..ad401bb 100644 --- a/context.go +++ b/context.go @@ -650,6 +650,14 @@ func (ctx *Context) NewObject() *Value { return &Value{ctx: ctx, ref: C.JS_NewObject(ctx.ref)} } +// NewProxy returns a new Proxy value backed by the given target and handler. +func (ctx *Context) NewProxy(target, handler *Value) *Value { + if ctx == nil || !ctx.hasValidRef() || !target.belongsTo(ctx) || !handler.belongsTo(ctx) { + return nil + } + return &Value{ctx: ctx, ref: C.JS_NewProxy(ctx.ref, target.ref, handler.ref)} +} + // Object returns a new object value. // Deprecated: Use NewObject() instead. func (ctx *Context) Object() *Value { diff --git a/value.go b/value.go index 09a1bdf..8efb243 100644 --- a/value.go +++ b/value.go @@ -713,6 +713,22 @@ func (v *Value) Freeze() bool { return ret == 1 } +// ProxyTarget returns the Proxy target value. +func (v *Value) ProxyTarget() *Value { + if !v.isAlive() { + return nil + } + return &Value{ctx: v.ctx, ref: C.JS_GetProxyTarget(v.ctx.ref, v.ref)} +} + +// ProxyHandler returns the Proxy handler value. +func (v *Value) ProxyHandler() *Value { + if !v.isAlive() { + return nil + } + return &Value{ctx: v.ctx, ref: C.JS_GetProxyHandler(v.ctx.ref, v.ref)} +} + // GlobalInstanceof checks if the value is an instance of the given global constructor func (v *Value) GlobalInstanceof(name string) bool { ctor := v.ctx.Globals().Get(name) @@ -761,16 +777,13 @@ func (v *Value) ToUint8Array() ([]uint8, error) { return nil, errors.New("value is not a Uint8Array or Uint8ClampedArray") } - buffer, byteOffset, byteLength, _ := v.getTypedArrayInfo() - defer buffer.Free() - - totalSize := uint(byteOffset + byteLength) - bytes, err := buffer.ToByteArray(totalSize) - if err != nil { - return nil, err + var cSize C.size_t + outBuf := C.JS_GetUint8Array(v.ctx.ref, &cSize, v.ref) + if outBuf == nil { + return nil, errors.New("failed to get Uint8Array data") } - return bytes[byteOffset : byteOffset+byteLength], nil + return C.GoBytes(unsafe.Pointer(outBuf), C.int(cSize)), nil } // ToInt16Array converts the value to int16 slice if it's an Int16Array. @@ -976,6 +989,14 @@ func (v *Value) IsObject() bool { return v != nil && bool(C.JS_IsObject(v func (v *Value) IsArray() bool { return v != nil && bool(C.JS_IsArray(v.ref)) } func (v *Value) IsDate() bool { return v != nil && bool(C.JS_IsDate(v.ref)) } func (v *Value) IsError() bool { return v != nil && bool(C.JS_IsError(v.ref)) } +func (v *Value) IsRegExp() bool { return v != nil && bool(C.JS_IsRegExp(v.ref)) } +func (v *Value) IsMap() bool { return v != nil && bool(C.JS_IsMap(v.ref)) } +func (v *Value) IsSet() bool { return v != nil && bool(C.JS_IsSet(v.ref)) } +func (v *Value) IsWeakRef() bool { return v != nil && bool(C.JS_IsWeakRef(v.ref)) } +func (v *Value) IsWeakSet() bool { return v != nil && bool(C.JS_IsWeakSet(v.ref)) } +func (v *Value) IsWeakMap() bool { return v != nil && bool(C.JS_IsWeakMap(v.ref)) } +func (v *Value) IsDataView() bool { return v != nil && bool(C.JS_IsDataView(v.ref)) } +func (v *Value) IsProxy() bool { return v != nil && bool(C.JS_IsProxy(v.ref)) } func (v *Value) IsFunction() bool { return v != nil && bool(C.JS_IsFunction(v.ctx.ref, v.ref)) } func (v *Value) IsAsyncFunction() bool { return v != nil && bool(C.JS_IsAsyncFunction(v.ref)) } func (v *Value) IsConstructor() bool { return v != nil && bool(C.JS_IsConstructor(v.ctx.ref, v.ref)) } @@ -985,15 +1006,7 @@ func (v *Value) IsConstructor() bool { return v != nil && bool(C.JS_IsConstruc // ============================================================================= func (v *Value) IsPromise() bool { - if v == nil { - return false - } - state := C.JS_PromiseState(v.ctx.ref, v.ref) - pending := C.int(C.JS_PROMISE_PENDING) - fulfilled := C.int(C.JS_PROMISE_FULFILLED) - rejected := C.int(C.JS_PROMISE_REJECTED) - - return C.int(state) == pending || C.int(state) == fulfilled || C.int(state) == rejected + return v != nil && bool(C.JS_IsPromise(v.ref)) } // Promise state enumeration matching QuickJS @@ -1133,41 +1146,96 @@ func (v *Value) IsInstanceOfConstructor(constructor *Value) bool { } // TypedArray detection methods -func (v *Value) IsTypedArray() bool { +func (v *Value) typedArrayType() (int, bool) { if v == nil { - return false + return -1, false } - typedArrayTypes := []string{ - "Int8Array", "Uint8Array", "Uint8ClampedArray", - "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", - "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array", + typeID := int(C.JS_GetTypedArrayType(v.ref)) + if typeID < 0 { + return typeID, false } + return typeID, true +} - for _, typeName := range typedArrayTypes { - if v.GlobalInstanceof(typeName) { - return true - } - } - return false +func (v *Value) IsTypedArray() bool { + _, ok := v.typedArrayType() + return ok +} + +func (v *Value) IsInt8Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_INT8) +} + +func (v *Value) IsUint8Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_UINT8) } -func (v *Value) IsInt8Array() bool { return v != nil && v.GlobalInstanceof("Int8Array") } -func (v *Value) IsUint8Array() bool { return v != nil && v.GlobalInstanceof("Uint8Array") } func (v *Value) IsUint8ClampedArray() bool { - return v != nil && v.GlobalInstanceof("Uint8ClampedArray") + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_UINT8C) +} + +func (v *Value) IsInt16Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_INT16) +} + +func (v *Value) IsUint16Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_UINT16) +} + +func (v *Value) IsInt32Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_INT32) +} + +func (v *Value) IsUint32Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_UINT32) +} + +func (v *Value) IsFloat32Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_FLOAT32) +} + +func (v *Value) IsFloat64Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_FLOAT64) +} + +func (v *Value) IsBigInt64Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_BIG_INT64) +} + +func (v *Value) IsBigUint64Array() bool { + typeID, ok := v.typedArrayType() + return ok && typeID == int(C.JS_TYPED_ARRAY_BIG_UINT64) } -func (v *Value) IsInt16Array() bool { return v != nil && v.GlobalInstanceof("Int16Array") } -func (v *Value) IsUint16Array() bool { return v != nil && v.GlobalInstanceof("Uint16Array") } -func (v *Value) IsInt32Array() bool { return v != nil && v.GlobalInstanceof("Int32Array") } -func (v *Value) IsUint32Array() bool { return v != nil && v.GlobalInstanceof("Uint32Array") } -func (v *Value) IsFloat32Array() bool { return v != nil && v.GlobalInstanceof("Float32Array") } -func (v *Value) IsFloat64Array() bool { return v != nil && v.GlobalInstanceof("Float64Array") } -func (v *Value) IsBigInt64Array() bool { return v != nil && v.GlobalInstanceof("BigInt64Array") } -func (v *Value) IsBigUint64Array() bool { return v != nil && v.GlobalInstanceof("BigUint64Array") } // IsByteArray returns true if the value is array buffer func (v *Value) IsByteArray() bool { - return v != nil && v.IsObject() && (v.GlobalInstanceof("ArrayBuffer") || v.ToString() == "[object ArrayBuffer]") + return v != nil && bool(C.JS_IsArrayBuffer(v.ref)) +} + +// IsImmutableArrayBuffer returns true if the value is an immutable ArrayBuffer. +func (v *Value) IsImmutableArrayBuffer() bool { + if v == nil { + return false + } + return C.JS_IsImmutableArrayBuffer(v.ref) > 0 +} + +// SetImmutableArrayBuffer updates the immutability flag on an ArrayBuffer value. +func (v *Value) SetImmutableArrayBuffer(immutable bool) bool { + if v == nil { + return false + } + return C.JS_SetImmutableArrayBuffer(v.ref, C.bool(immutable)) == 0 } // resolveClassIDFromInheritance attempts to resolve classID by checking if this constructor diff --git a/value_test.go b/value_test.go index c9fac24..4b36b77 100644 --- a/value_test.go +++ b/value_test.go @@ -534,6 +534,7 @@ func TestValueArrayBuffer(t *testing.T) { defer arrayBuffer.Free() require.True(t, arrayBuffer.IsByteArray()) + require.False(t, arrayBuffer.IsImmutableArrayBuffer()) require.Equal(t, int64(len(data)), arrayBuffer.ByteLen()) // Test ToByteArray with various sizes @@ -554,6 +555,11 @@ func TestValueArrayBuffer(t *testing.T) { require.False(t, arr.IsException()) // Check for exceptions instead of error require.Equal(t, int64(5), arr.Len()) + require.True(t, arrayBuffer.SetImmutableArrayBuffer(true)) + require.True(t, arrayBuffer.IsImmutableArrayBuffer()) + require.True(t, arrayBuffer.SetImmutableArrayBuffer(false)) + require.False(t, arrayBuffer.IsImmutableArrayBuffer()) + // Test error cases with non-ArrayBuffer types - Updated to use New* methods errorTests := []struct { name string @@ -569,6 +575,8 @@ func TestValueArrayBuffer(t *testing.T) { t.Run(tt.name+"Error", func(t *testing.T) { val := tt.createVal() defer val.Free() + require.False(t, val.IsImmutableArrayBuffer()) + require.False(t, val.SetImmutableArrayBuffer(true)) _, err := val.ToByteArray(1) require.Error(t, err) }) @@ -698,6 +706,11 @@ func TestValueTypedArrays(t *testing.T) { require.NoError(t, err) require.Equal(t, []uint8{0, 128, 255}, result) }}, + {"EmptyUint8Array", "new Uint8Array([])", func(v *Value) { + result, err := v.ToUint8Array() + require.NoError(t, err) + require.Empty(t, result) + }}, {"Uint16Array", "new Uint16Array([0, 32768, 65535])", func(v *Value) { result, err := v.ToUint16Array() require.NoError(t, err) @@ -737,6 +750,125 @@ func TestValueTypedArrays(t *testing.T) { tt.testFn(val) }) } + + t.Run("Uint8ArrayWrongType", func(t *testing.T) { + notUint8 := ctx.Eval(`new Int8Array([1, 2, 3])`) + defer notUint8.Free() + require.False(t, notUint8.IsException()) + + _, err := notUint8.ToUint8Array() + require.Error(t, err) + }) +} + +func TestValueNativeObjectPredicates(t *testing.T) { + useStableOwnerHooksForLegacySubtests(t) + + rt := NewRuntime() + defer rt.Close() + ctx := rt.NewContext() + defer ctx.Close() + + tests := []struct { + name string + jsCode string + checkFunc func(*Value) bool + }{ + {"RegExp", `/abc/`, func(v *Value) bool { return v.IsRegExp() }}, + {"Map", `new Map([[1, 2]])`, func(v *Value) bool { return v.IsMap() }}, + {"Set", `new Set([1, 2])`, func(v *Value) bool { return v.IsSet() }}, + {"WeakRef", `new WeakRef({ value: 1 })`, func(v *Value) bool { return v.IsWeakRef() }}, + {"WeakSet", `new WeakSet([{}])`, func(v *Value) bool { return v.IsWeakSet() }}, + {"WeakMap", `new WeakMap([[{}, 1]])`, func(v *Value) bool { return v.IsWeakMap() }}, + {"DataView", `new DataView(new ArrayBuffer(8))`, func(v *Value) bool { return v.IsDataView() }}, + {"Proxy", `new Proxy({}, {})`, func(v *Value) bool { return v.IsProxy() }}, + {"ArrayBuffer", `new ArrayBuffer(8)`, func(v *Value) bool { return v.IsByteArray() }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + val := ctx.Eval(tt.jsCode) + defer val.Free() + require.False(t, val.IsException()) + require.True(t, tt.checkFunc(val)) + }) + } + + negativeChecks := []struct { + name string + checkFunc func(*Value) bool + }{ + {"RegExp", func(v *Value) bool { return v.IsRegExp() }}, + {"Map", func(v *Value) bool { return v.IsMap() }}, + {"Set", func(v *Value) bool { return v.IsSet() }}, + {"WeakRef", func(v *Value) bool { return v.IsWeakRef() }}, + {"WeakSet", func(v *Value) bool { return v.IsWeakSet() }}, + {"WeakMap", func(v *Value) bool { return v.IsWeakMap() }}, + {"DataView", func(v *Value) bool { return v.IsDataView() }}, + {"Proxy", func(v *Value) bool { return v.IsProxy() }}, + {"ArrayBuffer", func(v *Value) bool { return v.IsByteArray() }}, + } + + plainObject := ctx.NewObject() + defer plainObject.Free() + + for _, tt := range negativeChecks { + t.Run(tt.name+"Negative", func(t *testing.T) { + require.False(t, tt.checkFunc(plainObject)) + }) + } +} + +func TestValueProxyHelpers(t *testing.T) { + useStableOwnerHooksForLegacySubtests(t) + + rt := NewRuntime() + defer rt.Close() + ctx := rt.NewContext() + defer ctx.Close() + + target := ctx.NewObject() + defer target.Free() + target.Set("value", ctx.NewInt32(42)) + + handler := ctx.NewObject() + defer handler.Free() + handler.Set("tag", ctx.NewString("handler")) + + proxy := ctx.NewProxy(target, handler) + defer proxy.Free() + require.NotNil(t, proxy) + require.False(t, proxy.IsException()) + require.True(t, proxy.IsProxy()) + + proxyTarget := proxy.ProxyTarget() + defer proxyTarget.Free() + require.NotNil(t, proxyTarget) + require.False(t, proxyTarget.IsException()) + require.True(t, proxyTarget.SameValue(target)) + + proxyHandler := proxy.ProxyHandler() + defer proxyHandler.Free() + require.NotNil(t, proxyHandler) + require.False(t, proxyHandler.IsException()) + require.True(t, proxyHandler.SameValue(handler)) + + plainObject := ctx.NewObject() + defer plainObject.Free() + + notProxyTarget := plainObject.ProxyTarget() + defer notProxyTarget.Free() + require.True(t, notProxyTarget.IsException()) + require.ErrorContains(t, ctx.Exception(), "not a proxy") + + notProxyHandler := plainObject.ProxyHandler() + defer notProxyHandler.Free() + require.True(t, notProxyHandler.IsException()) + require.ErrorContains(t, ctx.Exception(), "not a proxy") + + var nilValue *Value + require.Nil(t, nilValue.ProxyTarget()) + require.Nil(t, nilValue.ProxyHandler()) } // TestValueProperties tests property operations @@ -1046,6 +1178,17 @@ func TestValueSpecialTypes(t *testing.T) { var nilValue *Value require.False(t, nilValue.IsPromise(), "nil value should not be a promise") require.False(t, nilValue.IsTypedArray(), "nil value should not be a typed array") + require.False(t, nilValue.IsByteArray(), "nil value should not be an array buffer") + require.False(t, nilValue.IsImmutableArrayBuffer(), "nil value should not be an immutable array buffer") + require.False(t, nilValue.SetImmutableArrayBuffer(true), "nil value should not update immutable array buffer state") + require.False(t, nilValue.IsRegExp(), "nil value should not be a regexp") + require.False(t, nilValue.IsMap(), "nil value should not be a map") + require.False(t, nilValue.IsSet(), "nil value should not be a set") + require.False(t, nilValue.IsWeakRef(), "nil value should not be a weak ref") + require.False(t, nilValue.IsWeakSet(), "nil value should not be a weak set") + require.False(t, nilValue.IsWeakMap(), "nil value should not be a weak map") + require.False(t, nilValue.IsDataView(), "nil value should not be a data view") + require.False(t, nilValue.IsProxy(), "nil value should not be a proxy") require.False(t, nilValue.IsAsyncFunction(), "nil value should not be an async function") } From 882a9cccb332f577288d293a4b78c01b502c4c62 Mon Sep 17 00:00:00 2001 From: buke Date: Fri, 22 May 2026 18:02:23 +0800 Subject: [PATCH 2/4] test(value): cover native branch guards - add detached Uint8Array coverage for the native JS_GetUint8Array failure path - add nil-context proxy coverage for the Context.NewProxy guard branch --- value_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/value_test.go b/value_test.go index 4b36b77..72696cd 100644 --- a/value_test.go +++ b/value_test.go @@ -759,6 +759,23 @@ func TestValueTypedArrays(t *testing.T) { _, err := notUint8.ToUint8Array() require.Error(t, err) }) + + t.Run("Uint8ArrayDetachedBuffer", func(t *testing.T) { + detachedUint8 := ctx.Eval(` + (() => { + const buffer = new ArrayBuffer(4); + const view = new Uint8Array(buffer); + buffer.transfer(); + return view; + })() + `) + defer detachedUint8.Free() + require.False(t, detachedUint8.IsException()) + + _, err := detachedUint8.ToUint8Array() + require.Error(t, err) + require.ErrorContains(t, ctx.Exception(), "ArrayBuffer") + }) } func TestValueNativeObjectPredicates(t *testing.T) { @@ -841,6 +858,9 @@ func TestValueProxyHelpers(t *testing.T) { require.False(t, proxy.IsException()) require.True(t, proxy.IsProxy()) + var nilCtx *Context + require.Nil(t, nilCtx.NewProxy(target, handler)) + proxyTarget := proxy.ProxyTarget() defer proxyTarget.Free() require.NotNil(t, proxyTarget) From c34b17e452fcc17a25d15f4eed37bd491217a2f9 Mon Sep 17 00:00:00 2001 From: buke Date: Fri, 22 May 2026 18:43:45 +0800 Subject: [PATCH 3/4] test(value): cover detached buffer error paths - add detached ArrayBuffer coverage for the native ToByteArray failure branch - add detached TypedArray coverage for native conversion error branches --- value_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/value_test.go b/value_test.go index 72696cd..b787b46 100644 --- a/value_test.go +++ b/value_test.go @@ -549,6 +549,22 @@ func TestValueArrayBuffer(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "exceeds the maximum length") + t.Run("DetachedArrayBuffer", func(t *testing.T) { + detachedBuffer := ctx.Eval(` + (() => { + const buffer = new ArrayBuffer(4); + buffer.transfer(); + return buffer; + })() + `) + defer detachedBuffer.Free() + require.False(t, detachedBuffer.IsException()) + + _, err := detachedBuffer.ToByteArray(0) + require.Error(t, err) + require.ErrorContains(t, ctx.Exception(), "ArrayBuffer") + }) + // Test array length - FIXED: removed error handling arr := ctx.Eval(`[1, 2, 3, 4, 5]`) defer arr.Free() @@ -776,6 +792,34 @@ func TestValueTypedArrays(t *testing.T) { require.Error(t, err) require.ErrorContains(t, ctx.Exception(), "ArrayBuffer") }) + + detachedConversionTests := []struct { + name string + jsCode string + convertFunc func(*Value) (interface{}, error) + }{ + {"Int8ArrayDetachedBuffer", `(() => { const view = new Int8Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToInt8Array() }}, + {"Int16ArrayDetachedBuffer", `(() => { const view = new Int16Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToInt16Array() }}, + {"Uint16ArrayDetachedBuffer", `(() => { const view = new Uint16Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToUint16Array() }}, + {"Int32ArrayDetachedBuffer", `(() => { const view = new Int32Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToInt32Array() }}, + {"Uint32ArrayDetachedBuffer", `(() => { const view = new Uint32Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToUint32Array() }}, + {"Float32ArrayDetachedBuffer", `(() => { const view = new Float32Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToFloat32Array() }}, + {"Float64ArrayDetachedBuffer", `(() => { const view = new Float64Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToFloat64Array() }}, + {"BigInt64ArrayDetachedBuffer", `(() => { const view = new BigInt64Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToBigInt64Array() }}, + {"BigUint64ArrayDetachedBuffer", `(() => { const view = new BigUint64Array(1); view.buffer.transfer(); return view; })()`, func(v *Value) (interface{}, error) { return v.ToBigUint64Array() }}, + } + + for _, tt := range detachedConversionTests { + t.Run(tt.name, func(t *testing.T) { + val := ctx.Eval(tt.jsCode) + defer val.Free() + require.False(t, val.IsException()) + + _, err := tt.convertFunc(val) + require.Error(t, err) + require.ErrorContains(t, ctx.Exception(), "ArrayBuffer") + }) + } } func TestValueNativeObjectPredicates(t *testing.T) { From 44fa3caddb70a8fc5cf3d6955dc0622918bd6f7d Mon Sep 17 00:00:00 2001 From: buke Date: Fri, 22 May 2026 19:01:52 +0800 Subject: [PATCH 4/4] test(marshal): cover detached buffer decode errors - replace fake typed-array error coverage with detached real buffer/view cases - cover marshal ArrayBuffer and TypedArray decode failure branches after native checks --- marshal_test.go | 110 +++++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 67 deletions(-) diff --git a/marshal_test.go b/marshal_test.go index 2dd51d3..fbf3739 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -684,70 +684,53 @@ func TestTypedArrayErrors(t *testing.T) { ctx := rt.NewContext() defer ctx.Close() - // Helper function to create fake TypedArray objects - createFakeTypedArray := func(typeName string) *Value { - jsCode := fmt.Sprintf(` - var corrupted = Object.create(%s.prototype); - Object.defineProperty(corrupted, 'constructor', { - value: %s, - writable: true, - enumerable: false, - configurable: true - }); - corrupted; - `, typeName, typeName) - val := ctx.Eval(jsCode) - return val - } - // Test ToXXXArray error branches in unmarshalSlice errorTests := []struct { - name string - target interface{} - typeName string + name string + target interface{} + jsCode string + expectedErr string }{ - {"FakeInt8Array", &[]int8{}, "Int8Array"}, - {"FakeUint8Array", &[]uint8{}, "Uint8Array"}, - {"FakeInt16Array", &[]int16{}, "Int16Array"}, - {"FakeUint16Array", &[]uint16{}, "Uint16Array"}, - {"FakeInt32Array", &[]int32{}, "Int32Array"}, - {"FakeUint32Array", &[]uint32{}, "Uint32Array"}, - {"FakeFloat32Array", &[]float32{}, "Float32Array"}, - {"FakeFloat64Array", &[]float64{}, "Float64Array"}, - {"FakeBigInt64Array", &[]int64{}, "BigInt64Array"}, - {"FakeBigUint64Array", &[]uint64{}, "BigUint64Array"}, + {"DetachedInt8Array", &[]int8{}, `(() => { const view = new Int8Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, + {"DetachedUint8Array", &[]uint8{}, `(() => { const view = new Uint8Array(1); view.buffer.transfer(); return view; })()`, "Uint8Array"}, + {"DetachedInt16Array", &[]int16{}, `(() => { const view = new Int16Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, + {"DetachedUint16Array", &[]uint16{}, `(() => { const view = new Uint16Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, + {"DetachedInt32Array", &[]int32{}, `(() => { const view = new Int32Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, + {"DetachedUint32Array", &[]uint32{}, `(() => { const view = new Uint32Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, + {"DetachedFloat32Array", &[]float32{}, `(() => { const view = new Float32Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, + {"DetachedFloat64Array", &[]float64{}, `(() => { const view = new Float64Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, + {"DetachedBigInt64Array", &[]int64{}, `(() => { const view = new BigInt64Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, + {"DetachedBigUint64Array", &[]uint64{}, `(() => { const view = new BigUint64Array(1); view.buffer.transfer(); return view; })()`, "ArrayBuffer"}, } for _, tt := range errorTests { t.Run(tt.name, func(t *testing.T) { - fakeTypedArray := createFakeTypedArray(tt.typeName) - defer fakeTypedArray.Free() + jsVal := ctx.Eval(tt.jsCode) + defer jsVal.Free() + require.False(t, jsVal.IsException()) - err := ctx.Unmarshal(fakeTypedArray, tt.target) - if err != nil { - t.Logf("✓ Covered ToXXXArray error branch for %s: %v", tt.name, err) - } + err := ctx.Unmarshal(jsVal, tt.target) + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedErr) }) } // Test specific error cases for byte arrays t.Run("ByteArrayErrors", func(t *testing.T) { - fakeArrayBuffer := ctx.Eval(` - var fakeBuffer = { - constructor: ArrayBuffer, - byteLength: 10 - }; - Object.setPrototypeOf(fakeBuffer, ArrayBuffer.prototype); - fakeBuffer; + detachedArrayBuffer := ctx.Eval(` + (() => { + const buffer = new ArrayBuffer(8); + buffer.transfer(); + return buffer; + })() `) - defer fakeArrayBuffer.Free() - require.False(t, fakeArrayBuffer.IsException()) + defer detachedArrayBuffer.Free() + require.False(t, detachedArrayBuffer.IsException()) var result []byte - err := ctx.Unmarshal(fakeArrayBuffer, &result) - if err != nil { - t.Logf("✓ Covered ToByteArray error branch: %v", err) - } + err := ctx.Unmarshal(detachedArrayBuffer, &result) + require.Error(t, err) + require.Contains(t, err.Error(), "ArrayBuffer") }) // Test fallback to regular array @@ -1243,27 +1226,20 @@ func TestErrorCases(t *testing.T) { // Test ToByteArray error in unmarshalInterface t.Run("ToByteArrayErrorInInterface", func(t *testing.T) { - fakeArrayBuffer := ctx.Eval(` - var fakeArrayBuffer = { - constructor: ArrayBuffer, - byteLength: 10, - toString: function() { return "[object ArrayBuffer]"; } - }; - Object.setPrototypeOf(fakeArrayBuffer, ArrayBuffer.prototype); - Object.defineProperty(fakeArrayBuffer, Symbol.toStringTag, { - value: "ArrayBuffer", - configurable: true - }); - fakeArrayBuffer; - `) - defer fakeArrayBuffer.Free() - require.False(t, fakeArrayBuffer.IsException()) + detachedArrayBuffer := ctx.Eval(` + (() => { + const buffer = new ArrayBuffer(8); + buffer.transfer(); + return buffer; + })() + `) + defer detachedArrayBuffer.Free() + require.False(t, detachedArrayBuffer.IsException()) var result interface{} - err := ctx.Unmarshal(fakeArrayBuffer, &result) - if err != nil { - t.Logf("✓ Covered ToByteArray error in unmarshalInterface: %v", err) - } + err := ctx.Unmarshal(detachedArrayBuffer, &result) + require.Error(t, err) + require.Contains(t, err.Error(), "ArrayBuffer") }) }