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
8 changes: 8 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Comment thread
buke marked this conversation as resolved.
}

// Object returns a new object value.
// Deprecated: Use NewObject() instead.
func (ctx *Context) Object() *Value {
Expand Down
110 changes: 43 additions & 67 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
})
}

Expand Down
150 changes: 109 additions & 41 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Comment thread
buke marked this conversation as resolved.
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.
Expand Down Expand Up @@ -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)) }
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Comment thread
buke marked this conversation as resolved.
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
Comment thread
buke marked this conversation as resolved.
}

// 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
Expand Down
Loading
Loading