From 4cd9609011b44d54802e9d14f3b105c50ca754e8 Mon Sep 17 00:00:00 2001 From: Pham Tan Minh Tien Date: Fri, 25 Sep 2026 18:47:35 +0700 Subject: [PATCH 1/2] feat(dynamic): expand dynamic tier with Proxy traps and unconstrained invocations --- docs/typescript-parity-report.md | 2 +- internal/backend/llvm/dynamic.go | 61 ++++- internal/backend/llvm/emit.go | 4 +- internal/backend/llvm/instructions_calls.go | 9 + internal/backend/llvm/numbers.go | 5 + internal/backend/llvm/objects.go | 18 +- internal/backend/llvm/objects_intrinsics.go | 81 +++++- internal/compiler/compiler.go | 2 +- .../corpus/dynamic/any_method_call/main.ts | 6 + .../corpus/dynamic/any_method_call/math.js | 15 ++ .../dynamic/any_method_call/run.expected | 2 + .../corpus/dynamic/proxy_traps/main.ts | 57 ++++ .../corpus/dynamic/proxy_traps/run.expected | 11 + internal/ir/verify.go | 4 +- internal/lowering/builtins_reflect.go | 39 ++- internal/lowering/calls.go | 22 +- internal/lowering/compatibility_analyze.go | 5 +- internal/lowering/compatibility_enforce.go | 4 +- internal/lowering/expressions_new.go | 3 + internal/lowering/expressions_new_proxy.go | 41 +++ internal/lowering/expressions_unary.go | 54 +++- internal/lowering/lowering.go | 14 +- internal/lowering/statements.go | 2 +- internal/runtime/native/dynamic/runtime.c | 244 ++++++++++++++++-- internal/runtime/native/dynamic/values.c | 59 ++++- internal/runtime/native/objects/runtime.c | 221 +++++++++++++++- internal/runtime/native/values/runtime.c | 20 +- .../runtime/native/values/scriptgo_value.h | 27 ++ internal/typescriptgo/syntax_expr.go | 10 + 29 files changed, 948 insertions(+), 94 deletions(-) create mode 100644 internal/compiler/testdata/corpus/dynamic/any_method_call/main.ts create mode 100644 internal/compiler/testdata/corpus/dynamic/any_method_call/math.js create mode 100644 internal/compiler/testdata/corpus/dynamic/any_method_call/run.expected create mode 100644 internal/compiler/testdata/corpus/dynamic/proxy_traps/main.ts create mode 100644 internal/compiler/testdata/corpus/dynamic/proxy_traps/run.expected create mode 100644 internal/lowering/expressions_new_proxy.go diff --git a/docs/typescript-parity-report.md b/docs/typescript-parity-report.md index 9c50f96..4a78451 100644 --- a/docs/typescript-parity-report.md +++ b/docs/typescript-parity-report.md @@ -340,7 +340,7 @@ Below is the detailed audit of all TypeScript/ECMAScript Abstract Syntax Tree (A | **Dynamic `import('./mod')`** | ❌ Unsupported | Currently supports closed static module graphs only (AOT static linking). | | **`eval()` & `new Function()`** | ❌ Unavailable in Native | Native machine binaries cannot interpret arbitrary JS strings at runtime (requires `--dynamic`). | | **`Reflect` Namespace** | ✅ Full | All 19 standard ECMAScript & metadata APIs (`get`, `set`, `has`, `deleteProperty`, `ownKeys`, `defineProperty`, `getOwnPropertyDescriptor`, `getPrototypeOf`, `setPrototypeOf`, `isExtensible`, `preventExtensions`, `apply`, `construct`, `getMetadata`, `getOwnMetadata`, `hasMetadata`, `hasOwnMetadata`, `defineMetadata`, `metadata`) supported in Static Tier. | -| **`Proxy` Objects** | ⏳ Roadmap (Dynamic) | Dynamic field interception traps (get/set traps on arbitrary dynamic objects) slated for `--dynamic` tier. | +| **`Proxy` Objects** | ✅ Supported (--dynamic) | Basic Proxy interception traps (`get`, `set`, `has`, `apply`, `deleteProperty`, `ownKeys`) supported in the Dynamic tier (QuickJS-ng). Dynamic `any` method calls, prototype traversal, and unconstrained dynamic invocations with arbitrary argument arity are supported. | | **Prototype Chain Manipulation** | ❌ Rejected | `Object.setPrototypeOf`, `__proto__`, `Object.defineProperty` (runtime dynamic getters/setters) are disabled to preserve static struct layouts. | --- diff --git a/internal/backend/llvm/dynamic.go b/internal/backend/llvm/dynamic.go index 4490a72..be2ddad 100644 --- a/internal/backend/llvm/dynamic.go +++ b/internal/backend/llvm/dynamic.go @@ -20,7 +20,7 @@ func moduleHasDynamic(module ir.Module) bool { func hasDynamicInstruction(list []ir.Instruction) bool { for _, instruction := range list { - if instruction.Op == ir.OpDynamicCall || instruction.Op == ir.OpDynamicFunctionCall || hasDynamicInstruction(instruction.Then) || hasDynamicInstruction(instruction.Else) || hasDynamicInstruction(instruction.Body) || hasDynamicInstruction(instruction.Catch) || hasDynamicInstruction(instruction.Finally) { + if instruction.Op == ir.OpDynamicCall || instruction.Op == ir.OpDynamicFunctionCall || (instruction.Op == ir.OpCall && strings.HasPrefix(instruction.Callee, "__dynamic.")) || hasDynamicInstruction(instruction.Then) || hasDynamicInstruction(instruction.Else) || hasDynamicInstruction(instruction.Body) || hasDynamicInstruction(instruction.Catch) || hasDynamicInstruction(instruction.Finally) { return true } } @@ -101,8 +101,8 @@ func (e *functionEmitter) emitDynamicCall(out *strings.Builder, instruction ir.I } func (e *functionEmitter) emitDynamicFunctionCall(out *strings.Builder, instruction ir.Instruction) error { - if len(instruction.Args) == 0 || len(instruction.Args) > 5 { - return fmt.Errorf("dynamic function call supports one callable and up to four arguments") + if len(instruction.Args) == 0 { + return fmt.Errorf("dynamic function call requires at least a callable") } callable := e.resolveArg(out, instruction.Args[0]) if e.types[instruction.Args[0]] == ir.TypeUnknown { @@ -113,14 +113,25 @@ func (e *functionEmitter) emitDynamicFunctionCall(out *strings.Builder, instruct e.loadCounter++ out.WriteString(fmt.Sprintf(" %%%s = inttoptr i64 %%%s to ptr\n", callable, payload)) } - pointers := []string{"ptr null", "ptr null", "ptr null", "ptr null"} + argCount := len(instruction.Args) - 1 + arrayCount := argCount + if arrayCount == 0 { + arrayCount = 1 + } + argsSlot := fmt.Sprintf("dynamic.function.args.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = alloca [%d x %s]\n", argsSlot, arrayCount, boxedLLVMType) for i, arg := range instruction.Args[1:] { argType := e.types[arg] - pointer, err := e.emitCanonicalValuePointer(out, arg, argType, fmt.Sprintf("dynamic.function.arg.%d", i)) - if err != nil { + boxed := fmt.Sprintf("dynamic.function.arg.boxed.%d", e.loadCounter) + e.loadCounter++ + if err := e.emitBoxValue(out, arg, argType, boxed); err != nil { return err } - pointers[i] = "ptr " + pointer + index := fmt.Sprintf("dynamic.function.arg.index.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = getelementptr inbounds [%d x %s], ptr %%%s, i64 0, i64 %d\n", index, arrayCount, boxedLLVMType, argsSlot, i) + fmt.Fprintf(out, " store %s %%%s, ptr %%%s\n", boxedLLVMType, boxed, index) } thisPointer := e.dynamicUndefinedValuePointer(out) if instruction.This != "" { @@ -135,7 +146,7 @@ func (e *functionEmitter) emitDynamicFunctionCall(out *strings.Builder, instruct out.WriteString(fmt.Sprintf(" %%%s = alloca %s\n", outSlot, boxedLLVMType)) status := fmt.Sprintf("dynamic.function.status.%d", e.runtimeStatus) e.runtimeStatus++ - out.WriteString(fmt.Sprintf(" %%%s = call i32 @scriptgo_dynamic_invoke_function(ptr %%%s, ptr %s, i32 %d, %s, i32 %d, ptr %%%s)\n", status, callable, thisPointer, len(instruction.Args)-1, strings.Join(pointers, ", "), dynamicResultTag(instruction.Type), outSlot)) + out.WriteString(fmt.Sprintf(" %%%s = call i32 @scriptgo_dynamic_invoke_function(ptr %%%s, ptr %s, i32 %d, ptr %%%s, i32 %d, ptr %%%s)\n", status, callable, thisPointer, argCount, argsSlot, dynamicResultTag(instruction.Type), outSlot)) out.WriteString(fmt.Sprintf(" call void @scriptgo_dynamic_abort_if_failed(i32 %%%s)\n", status)) boxed := fmt.Sprintf("dynamic.function.result.%d", e.loadCounter) e.loadCounter++ @@ -143,6 +154,36 @@ func (e *functionEmitter) emitDynamicFunctionCall(out *strings.Builder, instruct return e.emitDynamicResult(out, instruction.Type, boxed, instruction.Result) } +func (e *functionEmitter) emitDynamicIntrinsic(out *strings.Builder, instruction ir.Instruction) error { + switch instruction.Callee { + case "__dynamic.new_proxy": + if len(instruction.Args) != 2 { + return fmt.Errorf("__dynamic.new_proxy expects 2 arguments, got %d", len(instruction.Args)) + } + targetSlot, err := e.emitCanonicalValuePointer(out, instruction.Args[0], e.types[instruction.Args[0]], "proxy.target") + if err != nil { + return err + } + handlerSlot, err := e.emitCanonicalValuePointer(out, instruction.Args[1], e.types[instruction.Args[1]], "proxy.handler") + if err != nil { + return err + } + outSlot := fmt.Sprintf("dynamic.proxy.out.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = alloca %s\n", outSlot, boxedLLVMType) + status := fmt.Sprintf("dynamic.proxy.status.%d", e.runtimeStatus) + e.runtimeStatus++ + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_dynamic_new_proxy(ptr %s, ptr %s, ptr %%%s)\n", status, targetSlot, handlerSlot, outSlot) + fmt.Fprintf(out, " call void @scriptgo_dynamic_abort_if_failed(i32 %%%s)\n", status) + boxedResult := fmt.Sprintf("dynamic.proxy.result.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = load %s, ptr %%%s\n", boxedResult, boxedLLVMType, outSlot) + return e.emitDynamicResult(out, instruction.Type, boxedResult, instruction.Result) + default: + return fmt.Errorf("unknown dynamic intrinsic %s", instruction.Callee) + } +} + func (e *functionEmitter) dynamicUndefinedValuePointer(out *strings.Builder) string { slot := fmt.Sprintf("dynamic.function.undefined.this.%d", e.loadCounter) e.loadCounter++ @@ -187,6 +228,8 @@ func (e *functionEmitter) emitDynamicModuleRegistry(out *strings.Builder) error func dynamicResultTag(typ ir.Type) int { switch typ { + case ir.TypeVoid: + return 0 case ir.TypeBool: return 2 case ir.TypeNumber: @@ -225,6 +268,8 @@ func (e *functionEmitter) emitDynamicResult(out *strings.Builder, typ ir.Type, b e.loadCounter++ fmt.Fprintf(out, " %%%s = extractvalue %s %%%s, 0\n", tag, boxedLLVMType, boxed) switch typ { + case ir.TypeVoid: + return nil case ir.TypeUnknown: fmt.Fprintf(out, " %%%s = load %s, ptr %%%s\n", result, boxedLLVMType, e.dynamicResultSlot(out, boxed)) case ir.TypeNumber: diff --git a/internal/backend/llvm/emit.go b/internal/backend/llvm/emit.go index 9738c0d..9ca46e0 100644 --- a/internal/backend/llvm/emit.go +++ b/internal/backend/llvm/emit.go @@ -177,7 +177,8 @@ func EmitWithOptions(module ir.Module, options Options) (string, error) { out.WriteString("declare i32 @scriptgo_dynamic_register_module(ptr, ptr, ptr, ptr)\n") out.WriteString("declare i32 @scriptgo_dynamic_register_dependency(ptr, ptr, ptr)\n") out.WriteString("declare i32 @scriptgo_dynamic_call_module(ptr, ptr, ptr, i32, i32, i32, ptr)\n") - out.WriteString("declare i32 @scriptgo_dynamic_invoke_function(ptr, ptr, i32, ptr, ptr, ptr, ptr, i32, ptr)\n") + out.WriteString("declare i32 @scriptgo_dynamic_invoke_function(ptr, ptr, i32, ptr, i32, ptr)\n") + out.WriteString("declare i32 @scriptgo_dynamic_new_proxy(ptr, ptr, ptr)\n") out.WriteString("declare void @scriptgo_dynamic_abort_if_failed(i32)\n") } out.WriteString("declare void @scriptgo_debugger_break(ptr, i32)\n\n") @@ -313,6 +314,7 @@ func EmitWithOptions(module ir.Module, options Options) (string, error) { out.WriteString("declare i32 @scriptgo_object_property_bigint_set(ptr, ptr, i64)\n") out.WriteString("declare i32 @scriptgo_object_property_ptr_set(ptr, ptr, ptr)\n") out.WriteString("declare i32 @scriptgo_object_property_unknown_set(ptr, ptr, ptr)\n") + out.WriteString("declare i32 @scriptgo_object_delete_property(ptr, ptr, ptr)\n") out.WriteString("declare i32 @scriptgo_unknown_number_property(ptr, ptr, ptr)\n") out.WriteString("declare i32 @scriptgo_object_type_set(ptr, ptr)\n") out.WriteString("declare i32 @scriptgo_object_type_set_static(ptr, ptr)\n") diff --git a/internal/backend/llvm/instructions_calls.go b/internal/backend/llvm/instructions_calls.go index 407ca6b..8aaf6da 100644 --- a/internal/backend/llvm/instructions_calls.go +++ b/internal/backend/llvm/instructions_calls.go @@ -109,6 +109,15 @@ func (e *functionEmitter) emitPrint(out *strings.Builder, instruction ir.Instruc } func (e *functionEmitter) emitCall(out *strings.Builder, instruction ir.Instruction) error { + if strings.HasPrefix(instruction.Callee, "__dynamic.") { + if err := e.emitDynamicIntrinsic(out, instruction); err != nil { + return err + } + if instruction.Result != "" { + e.types[instruction.Result] = instruction.Type + } + return nil + } if strings.HasPrefix(instruction.Callee, "__console.") { if err := e.emitConsoleIntrinsic(out, instruction); err != nil { return err diff --git a/internal/backend/llvm/numbers.go b/internal/backend/llvm/numbers.go index 786f801..335b3f6 100644 --- a/internal/backend/llvm/numbers.go +++ b/internal/backend/llvm/numbers.go @@ -189,6 +189,11 @@ func (e *functionEmitter) emitNumberIntrinsic(out *strings.Builder, instruction bigIntF64 := instruction.Result + ".f64" fmt.Fprintf(out, " %%%s = sitofp i64 %%%s to double\n", bigIntF64, instruction.Args[0]) fmt.Fprintf(out, " %%%s = fadd double %%%s, 0.0\n", instruction.Result, bigIntF64) + } else if argType == ir.TypeUnknown { + payload := fmt.Sprintf("number.unknown.payload.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = extractvalue { i32, i32, i64, i64 } %%%s, 2\n", payload, instruction.Args[0]) + fmt.Fprintf(out, " %%%s = bitcast i64 %%%s to double\n", instruction.Result, payload) } else { fmt.Fprintf(out, " %%%s = fadd double %%%s, 0.0\n", instruction.Result, instruction.Args[0]) } diff --git a/internal/backend/llvm/objects.go b/internal/backend/llvm/objects.go index 72ef1ba..399e743 100644 --- a/internal/backend/llvm/objects.go +++ b/internal/backend/llvm/objects.go @@ -57,13 +57,27 @@ func (e *functionEmitter) emitInstanceOf(out *strings.Builder, instruction ir.In } else if len(instruction.Args) > 1 { cls := instruction.Args[1] clsVal := cls + clsType := e.types[cls] if slot, ok := e.varSlots[cls]; ok { loaded := fmt.Sprintf("%s.instanceof_cls.%d", cls, e.loadCounter) e.loadCounter++ - out.WriteString(fmt.Sprintf(" %%%s = load ptr, ptr %%%s\n", loaded, slot)) + if clsType == ir.TypeUnknown { + out.WriteString(fmt.Sprintf(" %%%s = load { i32, i32, i64, i64 }, ptr %%%s\n", loaded, slot)) + } else { + out.WriteString(fmt.Sprintf(" %%%s = load ptr, ptr %%%s\n", loaded, slot)) + } clsVal = loaded } - classArg = "%" + clsVal + if clsType == ir.TypeUnknown { + propPayload := fmt.Sprintf("instanceof.prop.payload.%d", e.loadCounter) + propPtr := fmt.Sprintf("instanceof.prop.ptr.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = extractvalue { i32, i32, i64, i64 } %%%s, 2\n", propPayload, clsVal) + fmt.Fprintf(out, " %%%s = inttoptr i64 %%%s to ptr\n", propPtr, propPayload) + classArg = "%" + propPtr + } else { + classArg = "%" + clsVal + } } else { return fmt.Errorf("instanceof requires target class or property") } diff --git a/internal/backend/llvm/objects_intrinsics.go b/internal/backend/llvm/objects_intrinsics.go index f91b565..ba46603 100644 --- a/internal/backend/llvm/objects_intrinsics.go +++ b/internal/backend/llvm/objects_intrinsics.go @@ -173,12 +173,21 @@ func (e *functionEmitter) emitObjectIntrinsic(out *strings.Builder, instruction ptrObj = "%" + ptrName } propertyArg := e.resolveArg(out, instruction.Args[1]) + ptrProperty := "%" + propertyArg + if e.types[instruction.Args[1]] == ir.TypeUnknown { + propPayload := fmt.Sprintf("dynamic.propname.payload.%d", e.loadCounter) + propPtr := fmt.Sprintf("dynamic.propname.ptr.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = extractvalue { i32, i32, i64, i64 } %%%s, 2\n", propPayload, propertyArg) + fmt.Fprintf(out, " %%%s = inttoptr i64 %%%s to ptr\n", propPtr, propPayload) + ptrProperty = "%" + propPtr + } status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus) e.runtimeStatus++ e.types[instruction.Result] = ir.TypeUnknown valueSlot := fmt.Sprintf("%s.dynamic.value.slot", instruction.Result) fmt.Fprintf(out, " %%%s = alloca { i32, i32, i64, i64 }\n", valueSlot) - fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_unknown_get(ptr %s, ptr %%%s, ptr %%%s)\n", status, ptrObj, propertyArg, valueSlot) + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_unknown_get(ptr %s, ptr %s, ptr %%%s)\n", status, ptrObj, ptrProperty, valueSlot) fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status) fmt.Fprintf(out, " %%%s = load { i32, i32, i64, i64 }, ptr %%%s\n", instruction.Result, valueSlot) return nil @@ -195,6 +204,15 @@ func (e *functionEmitter) emitObjectIntrinsic(out *strings.Builder, instruction ptrObj = ptrName } propertyArg := e.resolveArg(out, instruction.Args[1]) + ptrProperty := "%" + propertyArg + if e.types[instruction.Args[1]] == ir.TypeUnknown { + propPayload := fmt.Sprintf("dynamic.propname.payload.%d", e.loadCounter) + propPtr := fmt.Sprintf("dynamic.propname.ptr.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = extractvalue { i32, i32, i64, i64 } %%%s, 2\n", propPayload, propertyArg) + fmt.Fprintf(out, " %%%s = inttoptr i64 %%%s to ptr\n", propPtr, propPayload) + ptrProperty = "%" + propPtr + } status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus) e.runtimeStatus++ e.types[instruction.Result] = instruction.Type @@ -207,9 +225,9 @@ func (e *functionEmitter) emitObjectIntrinsic(out *strings.Builder, instruction if err != nil { return err } - fmt.Fprintf(out, " %%%s = call i32 @scriptgo_unknown_number_property(ptr %s, ptr %%%s, ptr %%%s)\n", status, valuePtr, propertyArg, slot) + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_unknown_number_property(ptr %s, ptr %s, ptr %%%s)\n", status, valuePtr, ptrProperty, slot) } else { - fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_number_get(ptr %%%s, ptr %%%s, ptr %%%s)\n", status, ptrObj, propertyArg, slot) + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_number_get(ptr %%%s, ptr %s, ptr %%%s)\n", status, ptrObj, ptrProperty, slot) } out.WriteString(fmt.Sprintf(" call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status)) out.WriteString(fmt.Sprintf(" %%%s = load double, ptr %%%s\n", instruction.Result, slot)) @@ -241,6 +259,44 @@ func (e *functionEmitter) emitObjectIntrinsic(out *strings.Builder, instruction out.WriteString(fmt.Sprintf(" %%%s = load ptr, ptr %%%s\n", instruction.Result, slot)) } return nil + case "__object.delete_prop": + if len(instruction.Args) != 2 { + return fmt.Errorf("__object.delete_prop requires object and property") + } + objArg := e.resolveArg(out, instruction.Args[0]) + propertyArg := e.resolveArg(out, instruction.Args[1]) + objType := e.types[instruction.Args[0]] + ptrObj := objArg + if objType == ir.TypeUnknown { + payloadName := fmt.Sprintf("dynamic.delete.payload.%d", e.loadCounter) + ptrName := fmt.Sprintf("dynamic.delete.ptr.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = extractvalue { i32, i32, i64, i64 } %%%s, 2\n", payloadName, objArg) + fmt.Fprintf(out, " %%%s = inttoptr i64 %%%s to ptr\n", ptrName, payloadName) + ptrObj = ptrName + } + ptrProperty := propertyArg + if e.types[instruction.Args[1]] == ir.TypeUnknown { + propPayload := fmt.Sprintf("dynamic.delete.prop.payload.%d", e.loadCounter) + propPtr := fmt.Sprintf("dynamic.delete.prop.ptr.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = extractvalue { i32, i32, i64, i64 } %%%s, 2\n", propPayload, propertyArg) + fmt.Fprintf(out, " %%%s = inttoptr i64 %%%s to ptr\n", propPtr, propPayload) + ptrProperty = propPtr + } + outSlot := fmt.Sprintf("delete.prop.out.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = alloca i32\n", outSlot) + status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus) + e.runtimeStatus++ + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_delete_property(ptr %%%s, ptr %%%s, ptr %%%s)\n", status, ptrObj, ptrProperty, outSlot) + fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status) + i32Val := fmt.Sprintf("delete.prop.val.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = load i32, ptr %%%s\n", i32Val, outSlot) + fmt.Fprintf(out, " %%%s = trunc i32 %%%s to i1\n", instruction.Result, i32Val) + e.types[instruction.Result] = ir.TypeBool + return nil case "__object.set_prop": if len(instruction.Args) != 3 { return fmt.Errorf("__object.set_prop requires object, property, and value") @@ -258,21 +314,30 @@ func (e *functionEmitter) emitObjectIntrinsic(out *strings.Builder, instruction fmt.Fprintf(out, " %%%s = inttoptr i64 %%%s to ptr\n", ptrName, payloadName) ptrObj = ptrName } + ptrProperty := propertyArg + if e.types[instruction.Args[1]] == ir.TypeUnknown { + propPayload := fmt.Sprintf("dynamic.set.prop.payload.%d", e.loadCounter) + propPtr := fmt.Sprintf("dynamic.set.prop.ptr.%d", e.loadCounter) + e.loadCounter++ + fmt.Fprintf(out, " %%%s = extractvalue { i32, i32, i64, i64 } %%%s, 2\n", propPayload, propertyArg) + fmt.Fprintf(out, " %%%s = inttoptr i64 %%%s to ptr\n", propPtr, propPayload) + ptrProperty = propPtr + } valueType := e.types[instruction.Args[2]] status := fmt.Sprintf("runtime.status.%d", e.runtimeStatus) e.runtimeStatus++ switch valueType { case ir.TypeString: - fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_string_set(ptr %%%s, ptr %%%s, ptr %%%s)\n", status, ptrObj, propertyArg, valueArg) + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_string_set(ptr %%%s, ptr %%%s, ptr %%%s)\n", status, ptrObj, ptrProperty, valueArg) case ir.TypeNumber: - fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_number_set(ptr %%%s, ptr %%%s, double %%%s)\n", status, ptrObj, propertyArg, valueArg) + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_number_set(ptr %%%s, ptr %%%s, double %%%s)\n", status, ptrObj, ptrProperty, valueArg) case ir.TypeBool: boolValue := fmt.Sprintf("dynamic.set.bool.%d", e.loadCounter) e.loadCounter++ fmt.Fprintf(out, " %%%s = zext i1 %%%s to i32\n", boolValue, valueArg) - fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_bool_set(ptr %%%s, ptr %%%s, i32 %%%s)\n", status, ptrObj, propertyArg, boolValue) + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_bool_set(ptr %%%s, ptr %%%s, i32 %%%s)\n", status, ptrObj, ptrProperty, boolValue) case ir.TypeBigInt: - fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_bigint_set(ptr %%%s, ptr %%%s, i64 %%%s)\n", status, ptrObj, propertyArg, valueArg) + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_bigint_set(ptr %%%s, ptr %%%s, i64 %%%s)\n", status, ptrObj, ptrProperty, valueArg) default: boxed := valueArg if valueType != ir.TypeUnknown { @@ -286,7 +351,7 @@ func (e *functionEmitter) emitObjectIntrinsic(out *strings.Builder, instruction e.loadCounter++ fmt.Fprintf(out, " %%%s = alloca { i32, i32, i64, i64 }\n", boxedSlot) fmt.Fprintf(out, " store { i32, i32, i64, i64 } %%%s, ptr %%%s\n", boxed, boxedSlot) - fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_unknown_set(ptr %%%s, ptr %%%s, ptr %%%s)\n", status, ptrObj, propertyArg, boxedSlot) + fmt.Fprintf(out, " %%%s = call i32 @scriptgo_object_property_unknown_set(ptr %%%s, ptr %%%s, ptr %%%s)\n", status, ptrObj, ptrProperty, boxedSlot) } fmt.Fprintf(out, " call void @scriptgo_runtime_abort_if_failed(i32 %%%s)\n", status) return nil diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index 2d07f5f..3ea6831 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -245,7 +245,7 @@ func BuildWithOptions(entryPath, outputPath string, options BuildOptions) error return fmt.Errorf("write temporary LLVM file: %w", err) } var args []string - dynamicRuntime := strings.Contains(output, "@scriptgo_dynamic_call") + dynamicRuntime := options.Dynamic || strings.Contains(output, "@scriptgo_dynamic_") runtimeSource := runtime.SourceForDynamic(dynamicRuntime) runtimeObj, err := getOrBuildCachedRuntime(ccParts, options, codecConfig, runtimeSource, false) if err != nil { diff --git a/internal/compiler/testdata/corpus/dynamic/any_method_call/main.ts b/internal/compiler/testdata/corpus/dynamic/any_method_call/main.ts new file mode 100644 index 0000000..fd408db --- /dev/null +++ b/internal/compiler/testdata/corpus/dynamic/any_method_call/main.ts @@ -0,0 +1,6 @@ +// @dynamic +import { createCalc } from "./math.js"; + +const calc: any = createCalc(3); +console.log(calc.greet("ScriptGo")); +console.log(calc.scaleAndSum(1, 2, 3, 4, 5, 6)); diff --git a/internal/compiler/testdata/corpus/dynamic/any_method_call/math.js b/internal/compiler/testdata/corpus/dynamic/any_method_call/math.js new file mode 100644 index 0000000..f6cf930 --- /dev/null +++ b/internal/compiler/testdata/corpus/dynamic/any_method_call/math.js @@ -0,0 +1,15 @@ +export class Calculator { + constructor(factor) { + this.factor = factor; + } + scaleAndSum(a, b, c, d, e, f) { + return (a + b + c + d + e + f) * this.factor; + } + greet(name) { + return `Hello, ${name}!`; + } +} + +export function createCalc(factor) { + return new Calculator(factor); +} diff --git a/internal/compiler/testdata/corpus/dynamic/any_method_call/run.expected b/internal/compiler/testdata/corpus/dynamic/any_method_call/run.expected new file mode 100644 index 0000000..a863194 --- /dev/null +++ b/internal/compiler/testdata/corpus/dynamic/any_method_call/run.expected @@ -0,0 +1,2 @@ +Hello, ScriptGo! +63 diff --git a/internal/compiler/testdata/corpus/dynamic/proxy_traps/main.ts b/internal/compiler/testdata/corpus/dynamic/proxy_traps/main.ts new file mode 100644 index 0000000..662213e --- /dev/null +++ b/internal/compiler/testdata/corpus/dynamic/proxy_traps/main.ts @@ -0,0 +1,57 @@ +// @dynamic +const target: any = { + foo: "bar", + count: 10, + extra: "baz", +}; + +const handler: any = { + get(t: any, prop: any, receiver: any) { + if (prop === "magic") return 999; + return t[prop]; + }, + set(t: any, prop: any, val: any) { + t[prop] = val; + return true; + }, + has(t: any, prop: any) { + if (prop === "secret") return true; + return prop in t; + }, + deleteProperty(t: any, prop: any) { + delete t[prop]; + return true; + }, + ownKeys(t: any) { + return ["foo", "count", "extra"]; + }, +}; + +const proxy: any = new Proxy(target, handler); + +console.log(proxy.foo); +console.log(proxy.magic); + +proxy.count = 42; +console.log(proxy.count); + +console.log("secret" in proxy); +console.log("missing" in proxy); + +const keys: any = Object.keys(proxy); +console.log(keys.length); +console.log(keys[0]); +console.log(keys[1]); +console.log(keys[2]); + +delete proxy.foo; +console.log(proxy.foo); + +const fnTarget: any = (a: number, b: number) => a + b; +const fnHandler: any = { + apply(t: any, thisArg: any, args: any) { + return Number(t(args[0], args[1])) * 2; + }, +}; +const fnProxy: any = new Proxy(fnTarget, fnHandler); +console.log(fnProxy(10, 11)); diff --git a/internal/compiler/testdata/corpus/dynamic/proxy_traps/run.expected b/internal/compiler/testdata/corpus/dynamic/proxy_traps/run.expected new file mode 100644 index 0000000..b9d80ca --- /dev/null +++ b/internal/compiler/testdata/corpus/dynamic/proxy_traps/run.expected @@ -0,0 +1,11 @@ +bar +999 +42 +true +false +3 +foo +count +extra +undefined +42 diff --git a/internal/ir/verify.go b/internal/ir/verify.go index 519357f..874889b 100644 --- a/internal/ir/verify.go +++ b/internal/ir/verify.go @@ -130,7 +130,7 @@ func (f Function) verifyInternal(globals map[string]Type) error { if len(instruction.Args) == 0 { return fmt.Errorf("dynamic.function.call requires a callable argument") } - if callableType, ok := known[instruction.Args[0]]; !ok || callableType != TypeDynamicFunction { + if callableType, ok := known[instruction.Args[0]]; !ok || (callableType != TypeDynamicFunction && callableType != TypeUnknown) { return fmt.Errorf("dynamic.function.call requires a dynamic function handle") } if instruction.This != "" { @@ -168,7 +168,7 @@ func (f Function) verifyInternal(globals map[string]Type) error { return fmt.Errorf("%s instruction must define result and type", instruction.Op) } if instruction.Op == OpInstanceOf { - if len(instruction.Args) != 1 || instruction.Type != TypeBool || instruction.Value == "" { + if instruction.Type != TypeBool || (len(instruction.Args) == 1 && instruction.Value == "") || (len(instruction.Args) != 1 && len(instruction.Args) != 2) { return fmt.Errorf("instanceof requires one object operand, target class value, and bool result") } } diff --git a/internal/lowering/builtins_reflect.go b/internal/lowering/builtins_reflect.go index 43235a0..fa67387 100644 --- a/internal/lowering/builtins_reflect.go +++ b/internal/lowering/builtins_reflect.go @@ -478,9 +478,32 @@ func lowerReflectSet(call IntrinsicCall, intrinsic BuiltinIntrinsic) (string, ir break } } + result := call.Result + if result == "" { + result = nextTemp(call.Counter) + } + call.Function.Body = append(call.Function.Body, ir.Instruction{ + Op: ir.OpConst, + Type: ir.TypeBool, + Result: result, + Value: "true", + Span: toIRSpan(call.Path, call.Expression.Span), + }) + return result, ir.TypeBool, nil } } + propVal, _, err := call.LowerExpression(call.Path, propArg, "", call.Function, call.Env, call.Counter, call.Shapes, call.Signatures) + if err != nil { + return "", "", err + } + call.Function.Body = append(call.Function.Body, ir.Instruction{ + Op: ir.OpCall, + Type: ir.TypeVoid, + Callee: "__object.set_prop", + Args: []string{targetVal, propVal, valVal}, + Span: toIRSpan(call.Path, call.Expression.Span), + }) result := call.Result if result == "" { result = nextTemp(call.Counter) @@ -542,15 +565,27 @@ func lowerReflectHas(call IntrinsicCall, intrinsic BuiltinIntrinsic) (string, ir } func lowerReflectDeleteProperty(call IntrinsicCall, intrinsic BuiltinIntrinsic) (string, ir.Type, error) { + if len(call.Expression.Arguments) < 2 { + return "", "", fmt.Errorf("Reflect.deleteProperty requires target and propertyKey arguments") + } + targetVal, _, err := call.LowerExpression(call.Path, call.Expression.Arguments[0], "", call.Function, call.Env, call.Counter, call.Shapes, call.Signatures) + if err != nil { + return "", "", err + } + propVal, _, err := call.LowerExpression(call.Path, call.Expression.Arguments[1], "", call.Function, call.Env, call.Counter, call.Shapes, call.Signatures) + if err != nil { + return "", "", err + } result := call.Result if result == "" { result = nextTemp(call.Counter) } call.Function.Body = append(call.Function.Body, ir.Instruction{ - Op: ir.OpConst, + Op: ir.OpCall, Type: ir.TypeBool, Result: result, - Value: "true", + Callee: "__object.delete_prop", + Args: []string{targetVal, propVal}, Span: toIRSpan(call.Path, call.Expression.Span), }) return result, ir.TypeBool, nil diff --git a/internal/lowering/calls.go b/internal/lowering/calls.go index e6675db..8ca84c2 100644 --- a/internal/lowering/calls.go +++ b/internal/lowering/calls.go @@ -542,7 +542,7 @@ func lowerCallExpression( retType = toIRType(expression.InferredType) } op := ir.OpClosureCall - if propType == ir.TypeDynamicFunction { + if propType == ir.TypeDynamicFunction || (currentDynamicMode && (receiverType == ir.TypeUnknown || propType == ir.TypeUnknown)) { op = ir.OpDynamicFunctionCall } instruction := ir.Instruction{ @@ -883,7 +883,7 @@ func lowerCallExpression( } if !isModuleFunc { closureVal, closureType, err := lowerExpression(path, expression.Left, "", function, env, counter, shapes, signatures) - if err == nil && (closureType == ir.TypeDynamicFunction || closureType == ir.TypeClosure || closureType == "Function" || closureType == "function" || strings.Contains(string(closureType), "=>")) { + if err == nil && (closureType == ir.TypeDynamicFunction || (currentDynamicMode && closureType == ir.TypeUnknown) || closureType == ir.TypeClosure || closureType == "Function" || closureType == "function" || strings.Contains(string(closureType), "=>")) { args := make([]string, 0, len(expression.Arguments)) for _, argument := range expression.Arguments { value, _, err := lowerExpression(path, argument, "", function, env, counter, shapes, signatures) @@ -909,15 +909,13 @@ func lowerCallExpression( } } op := ir.OpClosureCall - if closureType == ir.TypeDynamicFunction { - receiver, receiverType, receiverErr := lowerExpression(path, expression.Left.Left, "", function, env, counter, shapes, signatures) - if receiverErr != nil { - return "", "", receiverErr - } - if receiverType == ir.TypeUnknown { - // Keep the boxed receiver available for the ABI call. - } else if receiver == "" { - return "", "", fmt.Errorf("dynamic method receiver is empty") + if closureType == ir.TypeDynamicFunction || (currentDynamicMode && closureType == ir.TypeUnknown) { + var receiver string + if expression.Left != nil && expression.Left.Left != nil { + rec, _, receiverErr := lowerExpression(path, expression.Left.Left, "", function, env, counter, shapes, signatures) + if receiverErr == nil { + receiver = rec + } } function.Body = append(function.Body, ir.Instruction{ Op: ir.OpDynamicFunctionCall, Type: retType, Result: result, @@ -982,7 +980,7 @@ func lowerCallExpression( calleeIsClosure := false calleeType, hasCalleeType := env[callee] - if hasCalleeType && calleeType == ir.TypeDynamicFunction { + if hasCalleeType && (calleeType == ir.TypeDynamicFunction || (currentDynamicMode && calleeType == ir.TypeUnknown)) { args := make([]string, 0, len(expression.Arguments)+1) args = append(args, callee) for _, argument := range expression.Arguments { diff --git a/internal/lowering/compatibility_analyze.go b/internal/lowering/compatibility_analyze.go index b756382..7ec90e7 100644 --- a/internal/lowering/compatibility_analyze.go +++ b/internal/lowering/compatibility_analyze.go @@ -374,12 +374,15 @@ func (c *compatibilityCollector) expression(path string, expression *typescriptg if callName(expression.Left) == "Function" { c.add(path, expression.Span, expression.Kind, CodeFunctionValue, "dynamic constructor target", "enable --dynamic for new Function", true) classified = true + } else if callName(expression.Left) == "Proxy" { + c.add(path, expression.Span, expression.Kind, CodeFunctionValue, "Proxy constructor", "enable --dynamic for new Proxy", true) + classified = true } else if callName(expression.Left) == "" { c.add(path, expression.Span, expression.Kind, CodeLanguageLowering, "dynamic constructor target", "", false) classified = true } case "unary": - if expression.Operator != "!" && expression.Operator != "-" && expression.Operator != "+" && expression.Operator != "~" && expression.Operator != "++" && expression.Operator != "--" && expression.Operator != "void" { + if expression.Operator != "!" && expression.Operator != "-" && expression.Operator != "+" && expression.Operator != "~" && expression.Operator != "++" && expression.Operator != "--" && expression.Operator != "void" && expression.Operator != "delete" { c.add(path, expression.Span, expression.Kind, CodeLanguageLowering, "unary operator "+expression.Operator, "", false) classified = true } diff --git a/internal/lowering/compatibility_enforce.go b/internal/lowering/compatibility_enforce.go index a03141c..f31b388 100644 --- a/internal/lowering/compatibility_enforce.go +++ b/internal/lowering/compatibility_enforce.go @@ -12,9 +12,7 @@ func EnforceCompatibility(report CompatibilityReport, capabilities Compatibility case TierStatic: continue case TierDynamic: - // JavaScript module bodies execute inside the Dynamic island. Their - // property and call semantics are intentionally not lowered natively. - if capabilities.DynamicRuntime && (decision.DynamicCapability == "local JavaScript module call" || decision.DynamicCapability == "any type" || isJavaScriptFile(decision.FileName)) { + if capabilities.DynamicRuntime && (decision.DynamicCapability == "local JavaScript module call" || decision.DynamicCapability == "any type" || decision.DynamicCapability == "Proxy constructor" || decision.DynamicCapability == "delete operator" || isJavaScriptFile(decision.FileName)) { continue } message := "This source site requires Dynamic execution, but the Dynamic runtime is not available in this build." diff --git a/internal/lowering/expressions_new.go b/internal/lowering/expressions_new.go index 02f599e..e4d17ed 100644 --- a/internal/lowering/expressions_new.go +++ b/internal/lowering/expressions_new.go @@ -14,6 +14,9 @@ func lowerNewExpression(path string, expression *typescriptgo.SyntaxExpression, if res, typ, handled, err := lowerIntlNew(path, expression, rawClassName, result, function, env, counter, shapes, signatures); handled { return res, typ, err } + if className == "Proxy" { + return lowerProxyNew(path, expression, result, function, env, counter, shapes, signatures) + } if className == "RegExp" { ensureRegExpShape(shapes) } diff --git a/internal/lowering/expressions_new_proxy.go b/internal/lowering/expressions_new_proxy.go new file mode 100644 index 0000000..8f49aca --- /dev/null +++ b/internal/lowering/expressions_new_proxy.go @@ -0,0 +1,41 @@ +package lowering + +import ( + "fmt" + + typescriptgo "github.com/microsoft/TypeScript/tsc/scriptgo" + "github.com/pilotworks/scriptgo/internal/ir" +) + +func lowerProxyNew(path string, expression *typescriptgo.SyntaxExpression, result string, function *ir.Function, env map[string]ir.Type, counter *int, shapes map[string]ir.ObjectShape, signatures map[string]ir.Function) (string, ir.Type, error) { + if len(expression.Arguments) != 2 { + return "", "", fmt.Errorf("Proxy constructor requires target and handler arguments") + } + target, _, err := lowerExpression(path, expression.Arguments[0], "", function, env, counter, shapes, signatures) + if err != nil { + return "", "", err + } + handler, _, err := lowerExpression(path, expression.Arguments[1], "", function, env, counter, shapes, signatures) + if err != nil { + return "", "", err + } + if result == "" { + result = nextTemp(counter) + } + retType := ir.TypeUnknown + if expression.InferredType != "" { + retType = toIRType(expression.InferredType) + if retType == "" { + retType = ir.TypeUnknown + } + } + function.Body = append(function.Body, ir.Instruction{ + Op: ir.OpCall, + Type: retType, + Result: result, + Callee: "__dynamic.new_proxy", + Args: []string{target, handler}, + Span: toIRSpan(path, expression.Span), + }) + return result, retType, nil +} diff --git a/internal/lowering/expressions_unary.go b/internal/lowering/expressions_unary.go index 130ad9a..4115854 100644 --- a/internal/lowering/expressions_unary.go +++ b/internal/lowering/expressions_unary.go @@ -13,6 +13,46 @@ func lowerUnaryExpression(path string, expression *typescriptgo.SyntaxExpression return lowerUpdateLValue(path, expression.Left, expression.Operator, false, result, function, env, counter, shapes, signatures, expression.Span) } + if expression.Operator == "delete" { + if expression.Left != nil && (expression.Left.Kind == "property" || expression.Left.Kind == "index") { + objVal, _, err := lowerExpression(path, expression.Left.Left, "", function, env, counter, shapes, signatures) + if err != nil { + return "", "", err + } + var propVal string + if expression.Left.Kind == "property" { + propVal = nextTemp(counter) + function.Body = append(function.Body, ir.Instruction{ + Op: ir.OpConst, Type: ir.TypeString, Result: propVal, Value: expression.Left.Text, + Span: toIRSpan(path, expression.Left.Span), + }) + } else { + pv, _, err := lowerExpression(path, expression.Left.Right, "", function, env, counter, shapes, signatures) + if err != nil { + return "", "", err + } + propVal = pv + } + if result == "" { + result = nextTemp(counter) + } + function.Body = append(function.Body, ir.Instruction{ + Op: ir.OpCall, Type: ir.TypeBool, Result: result, + Callee: "__object.delete_prop", Args: []string{objVal, propVal}, + Span: toIRSpan(path, expression.Span), + }) + return result, ir.TypeBool, nil + } + if result == "" { + result = nextTemp(counter) + } + function.Body = append(function.Body, ir.Instruction{ + Op: ir.OpConst, Type: ir.TypeBool, Result: result, Value: "true", + Span: toIRSpan(path, expression.Span), + }) + return result, ir.TypeBool, nil + } + value, valType, err := lowerExpression(path, expression.Left, "", function, env, counter, shapes, signatures) if err != nil { return "", "", err @@ -615,5 +655,17 @@ func lowerInExpression(path string, expression *typescriptgo.SyntaxExpression, r return result, ir.TypeBool, nil } - return "", "", fmt.Errorf("operator \"in\" requires object or array, got %s", rightType) + leftVal, _, err := lowerExpression(path, expression.Left, "", function, env, counter, shapes, signatures) + if err != nil { + return "", "", err + } + function.Body = append(function.Body, ir.Instruction{ + Op: ir.OpInstanceOf, + Type: ir.TypeBool, + Result: result, + Value: "", + Args: []string{rightVal, leftVal}, + Span: toIRSpan(path, expression.Span), + }) + return result, ir.TypeBool, nil } diff --git a/internal/lowering/lowering.go b/internal/lowering/lowering.go index 583d3a9..84b3d7f 100644 --- a/internal/lowering/lowering.go +++ b/internal/lowering/lowering.go @@ -14,10 +14,11 @@ import ( ) var ( - lowerMu sync.Mutex - topLevelVars = map[string]typescriptgo.SyntaxStatement{} - inProgressVars = map[string]bool{} - dynamicImports = map[string]dynamicImportBinding{} + lowerMu sync.Mutex + topLevelVars = map[string]typescriptgo.SyntaxStatement{} + inProgressVars = map[string]bool{} + dynamicImports = map[string]dynamicImportBinding{} + currentDynamicMode = false ) // Options specifies optional flags for the lowering phase. @@ -41,6 +42,11 @@ func LowerWithOptions(program frontend.Program, options Options) (ir.Module, err defer func() { WarnRuntimeCasts = prevWarn }() + prevDynamic := currentDynamicMode + currentDynamicMode = options.Dynamic + defer func() { + currentDynamicMode = prevDynamic + }() checkedProgram := program if options.Dynamic { checkedProgram = normalizeDynamicAnyProgram(checkedProgram) diff --git a/internal/lowering/statements.go b/internal/lowering/statements.go index 9c96564..a7bbd3e 100644 --- a/internal/lowering/statements.go +++ b/internal/lowering/statements.go @@ -901,7 +901,7 @@ func lowerStatement(path string, statement typescriptgo.SyntaxStatement, functio if err != nil { return err } - if idxType == ir.TypeString { + if idxType == ir.TypeString || idxType == ir.TypeUnknown { val, _, err := lowerExpression(path, statement.Expression, "", function, env, counter, shapes, signatures) if err != nil { return err diff --git a/internal/runtime/native/dynamic/runtime.c b/internal/runtime/native/dynamic/runtime.c index f8aa605..46b7197 100644 --- a/internal/runtime/native/dynamic/runtime.c +++ b/internal/runtime/native/dynamic/runtime.c @@ -60,10 +60,12 @@ static JSContext *scriptgo_dynamic_js_context = NULL; static scriptgo_dynamic_context *scriptgo_dynamic_value_context = NULL; static int scriptgo_dynamic_cleanup_registered = 0; -typedef struct scriptgo_dynamic_function_ref { - JSValue function; +typedef struct scriptgo_dynamic_engine_val { + JSValue val; uint32_t refs; -} scriptgo_dynamic_function_ref; +} scriptgo_dynamic_engine_val; +typedef scriptgo_dynamic_engine_val scriptgo_dynamic_function_ref; +typedef scriptgo_dynamic_engine_val scriptgo_dynamic_object_ref; static scriptgo_dynamic_module *scriptgo_dynamic_find_module(const char *path); static JSValue scriptgo_dynamic_require_module(JSContext *ctx, scriptgo_dynamic_module *module); @@ -74,33 +76,51 @@ static int scriptgo_dynamic_function_call(scriptgo_dynamic_context *context, uin scriptgo_value *out_result, scriptgo_value *out_exception, void *user_data); static void scriptgo_dynamic_function_retain(scriptgo_dynamic_context *context, uint64_t handle) { - scriptgo_dynamic_function_ref *ref = (scriptgo_dynamic_function_ref *)(uintptr_t)handle; + scriptgo_dynamic_engine_val *ref = (scriptgo_dynamic_engine_val *)(uintptr_t)handle; (void)context; if (ref != NULL) ref->refs++; } static void scriptgo_dynamic_function_release(scriptgo_dynamic_context *context, uint64_t handle) { - scriptgo_dynamic_function_ref *ref = (scriptgo_dynamic_function_ref *)(uintptr_t)handle; + scriptgo_dynamic_engine_val *ref = (scriptgo_dynamic_engine_val *)(uintptr_t)handle; (void)context; if (ref == NULL || ref->refs == 0) return; ref->refs--; if (ref->refs == 0) { - JS_FreeValue(scriptgo_dynamic_js_context, ref->function); + JS_FreeValue(scriptgo_dynamic_js_context, ref->val); free(ref); } } int scriptgo_dynamic_adopt_function(JSValue value, scriptgo_value *out) { - scriptgo_dynamic_function_ref *ref; + scriptgo_dynamic_engine_val *ref; if (out == NULL || scriptgo_dynamic_js_context == NULL || scriptgo_dynamic_value_context == NULL) return -1; - ref = (scriptgo_dynamic_function_ref *)calloc(1, sizeof(*ref)); + ref = (scriptgo_dynamic_engine_val *)calloc(1, sizeof(*ref)); if (ref == NULL) return -1; - ref->function = JS_DupValue(scriptgo_dynamic_js_context, value); + ref->val = JS_DupValue(scriptgo_dynamic_js_context, value); ref->refs = 1; if (scriptgo_value_adopt_engine_ref(scriptgo_dynamic_value_context, SCRIPTGO_TAG_FUNCTION, (uint64_t)(uintptr_t)ref, out) != 0) { - JS_FreeValue(scriptgo_dynamic_js_context, ref->function); + JS_FreeValue(scriptgo_dynamic_js_context, ref->val); + free(ref); + return -1; + } + return 0; +} + +int scriptgo_dynamic_adopt_object(JSValue value, scriptgo_value *out) { + scriptgo_dynamic_engine_val *ref; + if (out == NULL || scriptgo_dynamic_js_context == NULL || scriptgo_dynamic_value_context == NULL) + return -1; + ref = (scriptgo_dynamic_engine_val *)calloc(1, sizeof(*ref)); + if (ref == NULL) return -1; + ref->val = JS_DupValue(scriptgo_dynamic_js_context, value); + ref->refs = 1; + uint32_t tag = JS_IsFunction(scriptgo_dynamic_js_context, value) ? SCRIPTGO_TAG_FUNCTION : SCRIPTGO_TAG_OBJECT; + if (scriptgo_value_adopt_engine_ref(scriptgo_dynamic_value_context, tag, + (uint64_t)(uintptr_t)ref, out) != 0) { + JS_FreeValue(scriptgo_dynamic_js_context, ref->val); free(ref); return -1; } @@ -575,6 +595,13 @@ static JSValue js_scriptgo_setImmediate(JSContext *ctx, JSValueConst this_val, return res; } +int scriptgo_dynamic_engine_property_get(uint64_t handle, const char *property, scriptgo_value *out_value); +int scriptgo_dynamic_engine_property_set(uint64_t handle, const char *property, const scriptgo_value *value); +int scriptgo_dynamic_engine_has(uint64_t handle, const char *property, int32_t *out_result); +int scriptgo_dynamic_engine_delete(uint64_t handle, const char *property, int32_t *out_result); +int scriptgo_dynamic_engine_keys(uint64_t handle, void **out_array); +int scriptgo_dynamic_new_proxy(const scriptgo_value *target, const scriptgo_value *handler, scriptgo_value *out); + static int scriptgo_dynamic_ensure_context(void) { JSValue global, require_function; if (scriptgo_dynamic_js_context != NULL) return 0; @@ -589,7 +616,7 @@ static int scriptgo_dynamic_ensure_context(void) { scriptgo_dynamic_load_module, NULL); global = JS_GetGlobalObject(scriptgo_dynamic_js_context); require_function = JS_NewCFunction(scriptgo_dynamic_js_context, scriptgo_dynamic_require_by_path, - "__scriptgo_require_module", 1); + "__scriptgo_require_module", 1); JS_SetPropertyStr(scriptgo_dynamic_js_context, global, "__scriptgo_require_module", require_function); JS_SetPropertyStr(scriptgo_dynamic_js_context, global, "setTimeout", JS_NewCFunction(scriptgo_dynamic_js_context, js_scriptgo_setTimeout, "setTimeout", 2)); @@ -604,6 +631,11 @@ static int scriptgo_dynamic_ensure_context(void) { JS_SetPropertyStr(scriptgo_dynamic_js_context, global, "clearImmediate", JS_NewCFunction(scriptgo_dynamic_js_context, js_scriptgo_clearTimeout, "clearImmediate", 1)); JS_FreeValue(scriptgo_dynamic_js_context, global); + scriptgo_dynamic_hook_prop_get = scriptgo_dynamic_engine_property_get; + scriptgo_dynamic_hook_prop_set = scriptgo_dynamic_engine_property_set; + scriptgo_dynamic_hook_has = scriptgo_dynamic_engine_has; + scriptgo_dynamic_hook_delete = scriptgo_dynamic_engine_delete; + scriptgo_dynamic_hook_keys = scriptgo_dynamic_engine_keys; if (!scriptgo_dynamic_cleanup_registered) { atexit(scriptgo_dynamic_cleanup); scriptgo_dynamic_cleanup_registered = 1; @@ -615,13 +647,13 @@ static int scriptgo_dynamic_function_call(scriptgo_dynamic_context *context, uin const scriptgo_value *this_value, const scriptgo_value *arguments, uint32_t argument_count, const scriptgo_boundary_descriptor *descriptor, scriptgo_value *out_result, scriptgo_value *out_exception, void *user_data) { - scriptgo_dynamic_function_ref *ref = (scriptgo_dynamic_function_ref *)(uintptr_t)handle; + scriptgo_dynamic_engine_val *ref = (scriptgo_dynamic_engine_val *)(uintptr_t)handle; JSValue *argv = NULL; JSValue js_this = JS_UNDEFINED; JSValue result; uint32_t i; (void)context; (void)descriptor; (void)user_data; - if (ref == NULL || out_result == NULL || out_exception == NULL || argument_count > 4) + if (ref == NULL || out_result == NULL || out_exception == NULL) return SCRIPTGO_CALL_FATAL; scriptgo_value_init_undefined(out_result); scriptgo_value_init_undefined(out_exception); @@ -639,7 +671,7 @@ static int scriptgo_dynamic_function_call(scriptgo_dynamic_context *context, uin free(argv); return SCRIPTGO_CALL_FATAL; } - result = JS_Call(scriptgo_dynamic_js_context, ref->function, js_this, argument_count, argv); + result = JS_Call(scriptgo_dynamic_js_context, ref->val, js_this, argument_count, argv); for (i = 0; i < argument_count; i++) JS_FreeValue(scriptgo_dynamic_js_context, argv[i]); free(argv); JS_FreeValue(scriptgo_dynamic_js_context, js_this); @@ -663,7 +695,7 @@ static int scriptgo_dynamic_function_call(scriptgo_dynamic_context *context, uin int scriptgo_dynamic_engine_call_js(uint64_t handle, JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, JSValue *out) { scriptgo_value this_native = {0}; - scriptgo_value arguments[4] = {{0}}; + scriptgo_value *arguments = NULL; scriptgo_value result = {0}; scriptgo_value exception = {0}; scriptgo_value_constraint any = {UINT64_C(0x3ff)}; @@ -673,11 +705,19 @@ int scriptgo_dynamic_engine_call_js(uint64_t handle, JSContext *ctx, JSValueCons SCRIPTGO_VALUE_OWNED | SCRIPTGO_VALUE_ENGINE_REF, handle, 0}; int status; int i; - if (out == NULL || argc < 0 || argc > 4) return -1; - if (scriptgo_dynamic_from_js(ctx, this_val, &this_native) != 0) return -1; + if (out == NULL || argc < 0) return -1; + if (argc > 0) { + arguments = (scriptgo_value *)calloc((size_t)argc, sizeof(*arguments)); + if (arguments == NULL) return -1; + } + if (scriptgo_dynamic_from_js(ctx, this_val, &this_native) != 0) { + if (arguments != NULL) free(arguments); + return -1; + } for (i = 0; i < argc; i++) { if (scriptgo_dynamic_from_js(ctx, argv[i], &arguments[i]) != 0) { while (i > 0) scriptgo_value_release(&arguments[--i]); + free(arguments); scriptgo_value_release(&this_native); return -1; } @@ -686,6 +726,7 @@ int scriptgo_dynamic_engine_call_js(uint64_t handle, JSContext *ctx, JSValueCons descriptor.parameters = argc == 0 ? NULL : calloc((size_t)argc, sizeof(*descriptor.parameters)); if (argc != 0 && descriptor.parameters == NULL) { for (i = 0; i < argc; i++) scriptgo_value_release(&arguments[i]); + free(arguments); scriptgo_value_release(&this_native); return -1; } @@ -696,6 +737,7 @@ int scriptgo_dynamic_engine_call_js(uint64_t handle, JSContext *ctx, JSValueCons &this_native, arguments, (uint32_t)argc, &descriptor, &result, &exception); free((void *)descriptor.parameters); for (i = 0; i < argc; i++) scriptgo_value_release(&arguments[i]); + if (arguments != NULL) free(arguments); scriptgo_value_release(&this_native); if (status != SCRIPTGO_CALL_OK) { scriptgo_value_release(&result); @@ -710,26 +752,176 @@ int scriptgo_dynamic_engine_call_js(uint64_t handle, JSContext *ctx, JSValueCons return 0; } +int scriptgo_dynamic_engine_property_get(uint64_t handle, const char *property, scriptgo_value *out_value) { + if (scriptgo_dynamic_js_context == NULL || property == NULL || out_value == NULL) return -1; + scriptgo_dynamic_engine_val *ref = (scriptgo_dynamic_engine_val *)(uintptr_t)handle; + JSAtom atom = JS_NewAtom(scriptgo_dynamic_js_context, property); + JSValue val = JS_GetProperty(scriptgo_dynamic_js_context, ref->val, atom); + JS_FreeAtom(scriptgo_dynamic_js_context, atom); + if (JS_IsException(val)) { + JSValue ex = JS_GetException(scriptgo_dynamic_js_context); + const char *msg = JS_ToCString(scriptgo_dynamic_js_context, ex); + if (msg) { scriptgo_runtime_set_error(msg); JS_FreeCString(scriptgo_dynamic_js_context, msg); } + JS_FreeValue(scriptgo_dynamic_js_context, ex); + return -1; + } + int status = scriptgo_dynamic_from_js(scriptgo_dynamic_js_context, val, out_value); + JS_FreeValue(scriptgo_dynamic_js_context, val); + return status; +} + +int scriptgo_dynamic_engine_property_set(uint64_t handle, const char *property, const scriptgo_value *value) { + if (scriptgo_dynamic_js_context == NULL || property == NULL || value == NULL) return -1; + scriptgo_dynamic_engine_val *ref = (scriptgo_dynamic_engine_val *)(uintptr_t)handle; + JSAtom atom = JS_NewAtom(scriptgo_dynamic_js_context, property); + JSValue js_val = JS_UNDEFINED; + if (scriptgo_dynamic_to_js(scriptgo_dynamic_js_context, value, &js_val) != 0) { + JS_FreeAtom(scriptgo_dynamic_js_context, atom); + return -1; + } + int ret = JS_SetProperty(scriptgo_dynamic_js_context, ref->val, atom, js_val); + JS_FreeAtom(scriptgo_dynamic_js_context, atom); + if (ret < 0) { + JSValue ex = JS_GetException(scriptgo_dynamic_js_context); + const char *msg = JS_ToCString(scriptgo_dynamic_js_context, ex); + if (msg) { scriptgo_runtime_set_error(msg); JS_FreeCString(scriptgo_dynamic_js_context, msg); } + JS_FreeValue(scriptgo_dynamic_js_context, ex); + return -1; + } + return 0; +} + +int scriptgo_dynamic_engine_has(uint64_t handle, const char *property, int32_t *out_result) { + if (scriptgo_dynamic_js_context == NULL || property == NULL || out_result == NULL) return -1; + scriptgo_dynamic_engine_val *ref = (scriptgo_dynamic_engine_val *)(uintptr_t)handle; + JSAtom atom = JS_NewAtom(scriptgo_dynamic_js_context, property); + int ret = JS_HasProperty(scriptgo_dynamic_js_context, ref->val, atom); + JS_FreeAtom(scriptgo_dynamic_js_context, atom); + if (ret < 0) { + JSValue ex = JS_GetException(scriptgo_dynamic_js_context); + const char *msg = JS_ToCString(scriptgo_dynamic_js_context, ex); + if (msg) { scriptgo_runtime_set_error(msg); JS_FreeCString(scriptgo_dynamic_js_context, msg); } + JS_FreeValue(scriptgo_dynamic_js_context, ex); + return -1; + } + *out_result = (ret > 0) ? 1 : 0; + return 0; +} + +int scriptgo_dynamic_engine_delete(uint64_t handle, const char *property, int32_t *out_result) { + if (scriptgo_dynamic_js_context == NULL || property == NULL || out_result == NULL) return -1; + scriptgo_dynamic_engine_val *ref = (scriptgo_dynamic_engine_val *)(uintptr_t)handle; + JSAtom atom = JS_NewAtom(scriptgo_dynamic_js_context, property); + int ret = JS_DeleteProperty(scriptgo_dynamic_js_context, ref->val, atom, 0); + JS_FreeAtom(scriptgo_dynamic_js_context, atom); + if (ret < 0) { + JSValue ex = JS_GetException(scriptgo_dynamic_js_context); + const char *msg = JS_ToCString(scriptgo_dynamic_js_context, ex); + if (msg) { scriptgo_runtime_set_error(msg); JS_FreeCString(scriptgo_dynamic_js_context, msg); } + JS_FreeValue(scriptgo_dynamic_js_context, ex); + return -1; + } + *out_result = (ret > 0) ? 1 : 0; + return 0; +} + +int scriptgo_dynamic_engine_keys(uint64_t handle, void **out_array) { + if (scriptgo_dynamic_js_context == NULL || out_array == NULL) return -1; + scriptgo_dynamic_engine_val *ref = (scriptgo_dynamic_engine_val *)(uintptr_t)handle; + JSPropertyEnum *tab = NULL; + uint32_t len = 0; + if (JS_GetOwnPropertyNames(scriptgo_dynamic_js_context, &tab, &len, ref->val, + JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY) < 0) { + return -1; + } + if (scriptgo_array_new((int64_t)len, (int64_t)sizeof(char *), out_array) != 0) { + JS_FreePropertyEnum(scriptgo_dynamic_js_context, tab, len); + return -1; + } + scriptgo_array_set_tag(*out_array, SCRIPTGO_OBJECT_TAG_STRING); + for (uint32_t i = 0; i < len; i++) { + const char *prop = JS_AtomToCString(scriptgo_dynamic_js_context, tab[i].atom); + char *copy = prop ? strdup(prop) : strdup(""); + if (prop) JS_FreeCString(scriptgo_dynamic_js_context, prop); + scriptgo_array_set(*out_array, (double)i, ©); + } + JS_FreePropertyEnum(scriptgo_dynamic_js_context, tab, len); + return 0; +} + +int scriptgo_dynamic_new_proxy(const scriptgo_value *target, const scriptgo_value *handler, scriptgo_value *out) { + if (out == NULL) return scriptgo_runtime_set_error("scriptgo new proxy output is null"); + scriptgo_value_init_undefined(out); + int status = scriptgo_dynamic_ensure_context(); + if (status != 0) return status; + JSContext *ctx = scriptgo_dynamic_js_context; + JSValue js_target = JS_UNDEFINED; + JSValue js_handler = JS_UNDEFINED; + if (scriptgo_dynamic_to_js(ctx, target, &js_target) != 0) { + return scriptgo_runtime_set_error("Failed to convert Proxy target to JavaScript"); + } + if (scriptgo_dynamic_to_js(ctx, handler, &js_handler) != 0) { + JS_FreeValue(ctx, js_target); + return scriptgo_runtime_set_error("Failed to convert Proxy handler to JavaScript"); + } + JSValue global = JS_GetGlobalObject(ctx); + JSValue proxy_ctor = JS_GetPropertyStr(ctx, global, "Proxy"); + JS_FreeValue(ctx, global); + if (!JS_IsFunction(ctx, proxy_ctor)) { + JS_FreeValue(ctx, proxy_ctor); + JS_FreeValue(ctx, js_target); + JS_FreeValue(ctx, js_handler); + return scriptgo_runtime_set_error("Proxy constructor is not available"); + } + JSValue args[2] = { js_target, js_handler }; + JSValue proxy_val = JS_CallConstructor(ctx, proxy_ctor, 2, args); + JS_FreeValue(ctx, proxy_ctor); + JS_FreeValue(ctx, js_target); + JS_FreeValue(ctx, js_handler); + if (JS_IsException(proxy_val)) { + JSValue exception = JS_GetException(ctx); + const char *err_msg = JS_ToCString(ctx, exception); + if (err_msg != NULL) { + scriptgo_runtime_set_error(err_msg); + JS_FreeCString(ctx, err_msg); + } else { + scriptgo_runtime_set_error("Proxy construction failed"); + } + JS_FreeValue(ctx, exception); + return -1; + } + status = scriptgo_dynamic_adopt_object(proxy_val, out); + JS_FreeValue(ctx, proxy_val); + return status; +} + int scriptgo_dynamic_invoke_function(void *callable, const scriptgo_value *this_value, - int32_t argument_count, - const scriptgo_value *a1, const scriptgo_value *a2, - const scriptgo_value *a3, const scriptgo_value *a4, + int32_t argument_count, const scriptgo_value *arguments, int32_t expected_tag, scriptgo_value *out_result) { + if (out_result == NULL) return scriptgo_runtime_set_error("SG5002: Dynamic call invalid result pointer"); + scriptgo_value_init_undefined(out_result); + if (callable == NULL) return scriptgo_runtime_set_error("SG5002: Dynamic call callable is null"); + + // Check if callable is an engine reference or a native closure + scriptgo_engine_ref *ref = find_engine_ref((uint64_t)(uintptr_t)callable); + if (ref == NULL) { + return scriptgo_closure_invoke_value(callable, argument_count, + argument_count > 0 ? &arguments[0] : NULL, + argument_count > 1 ? &arguments[1] : NULL, + argument_count > 2 ? &arguments[2] : NULL, + argument_count > 3 ? &arguments[3] : NULL, + out_result); + } + scriptgo_value callable_value = {SCRIPTGO_TAG_FUNCTION, SCRIPTGO_VALUE_OWNED | SCRIPTGO_VALUE_ENGINE_REF, (uint64_t)(uintptr_t)callable, 0}; - scriptgo_value arguments[4] = {{0}}; scriptgo_value exception = {0}; scriptgo_value_constraint any = {UINT64_C(0x3ff)}; scriptgo_boundary_descriptor descriptor = {SCRIPTGO_BOUNDARY_FORMAT_V1, 0, NULL, any, any, "dynamic-function", "", 0, 1}; uint32_t count = argument_count < 0 ? 0 : (uint32_t)argument_count; int32_t status; - if (count > 4 || out_result == NULL) return scriptgo_runtime_set_error("SG5002: Dynamic call arity mismatch"); - if (count > 0 && a1 != NULL) arguments[0] = *a1; - if (count > 1 && a2 != NULL) arguments[1] = *a2; - if (count > 2 && a3 != NULL) arguments[2] = *a3; - if (count > 3 && a4 != NULL) arguments[3] = *a4; descriptor.parameter_count = count; descriptor.parameters = count == 0 ? NULL : calloc(count, sizeof(*descriptor.parameters)); if (count != 0 && descriptor.parameters == NULL) return scriptgo_runtime_set_error("Dynamic call descriptor allocation failed"); diff --git a/internal/runtime/native/dynamic/values.c b/internal/runtime/native/dynamic/values.c index 9bf38e0..bd7fa6f 100644 --- a/internal/runtime/native/dynamic/values.c +++ b/internal/runtime/native/dynamic/values.c @@ -4,12 +4,15 @@ int scriptgo_closure_invoke_value(void *closure_handle, int32_t arg_count, const scriptgo_value *a3, const scriptgo_value *a4, scriptgo_value *out_value); int scriptgo_dynamic_adopt_function(JSValue value, scriptgo_value *out); +int scriptgo_dynamic_adopt_object(JSValue value, scriptgo_value *out); int scriptgo_dynamic_engine_call_js(uint64_t handle, JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, JSValue *out); int scriptgo_promise_create(void **out_promise); int scriptgo_promise_resolve_value(void *promise_handle, const scriptgo_value *value); int scriptgo_promise_reject_value(void *promise_handle, const scriptgo_value *value); int scriptgo_timers_has_active(void); +void scriptgo_object_attach_engine_ref(void *object, void *ref); +void *scriptgo_object_get_engine_ref(void *object); static JSValue scriptgo_dynamic_promise_bridge_fulfill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, @@ -143,7 +146,7 @@ static int scriptgo_dynamic_engine_function_to_js(JSContext *ctx, const scriptgo if (value == NULL || out == NULL || value->payload == 0) return -1; handle = (int64_t)(intptr_t)value->payload; data[0] = JS_NewInt64(ctx, handle); - *out = JS_NewCFunctionData(ctx, scriptgo_dynamic_engine_closure, 4, 0, 1, data); + *out = JS_NewCFunctionData(ctx, scriptgo_dynamic_engine_closure, 0, 0, 1, data); JS_FreeValue(ctx, data[0]); return JS_IsException(*out) ? -1 : 0; } @@ -161,7 +164,13 @@ static JSValue scriptgo_dynamic_native_closure(JSContext *ctx, JSValueConst this if (argc > 4 || JS_ToInt64(ctx, &handle, func_data[0]) < 0) return JS_ThrowTypeError(ctx, "SG5002: callback arity exceeds native limit"); for (i = 0; i < argc; i++) { - if (scriptgo_dynamic_from_js(ctx, argv[i], &arguments[i]) != 0) { + int from_status; + if (JS_IsObject(argv[i]) && !JS_IsArray(argv[i]) && !JS_IsFunction(ctx, argv[i])) { + from_status = scriptgo_dynamic_adopt_object(argv[i], &arguments[i]); + } else { + from_status = scriptgo_dynamic_from_js(ctx, argv[i], &arguments[i]); + } + if (from_status != 0) { while (i > 0) scriptgo_value_release(&arguments[--i]); return JS_ThrowTypeError(ctx, "SG5002: callback argument is outside the native boundary"); } @@ -188,8 +197,8 @@ static JSValue scriptgo_dynamic_engine_closure(JSContext *ctx, JSValueConst this int magic, JSValueConst *func_data) { int64_t handle; JSValue result = JS_UNDEFINED; - if (argc > 4 || JS_ToInt64(ctx, &handle, func_data[0]) < 0) - return JS_ThrowTypeError(ctx, "SG5002: callback arity exceeds native limit"); + if (JS_ToInt64(ctx, &handle, func_data[0]) < 0) + return JS_ThrowTypeError(ctx, "SG5002: callback argument is outside the native boundary"); if (scriptgo_dynamic_engine_call_js((uint64_t)handle, ctx, this_val, argc, argv, &result) != 0) return JS_ThrowInternalError(ctx, "Dynamic function call failed"); return result; @@ -277,12 +286,42 @@ static int scriptgo_dynamic_to_js(JSContext *ctx, const scriptgo_value *value, J value->aux == 0 ? strlen((const char *)(uintptr_t)value->payload) : (size_t)value->aux); return JS_IsException(*out) ? -1 : 0; case SCRIPTGO_TAG_OBJECT: + if ((value->flags & SCRIPTGO_VALUE_ENGINE_REF) != 0) { + scriptgo_engine_ref *ref = find_engine_ref(value->payload); + if (ref != NULL) { + scriptgo_dynamic_engine_val *val = (scriptgo_dynamic_engine_val *)(uintptr_t)ref->handle; + if (val != NULL) { + *out = JS_DupValue(ctx, val->val); + return 0; + } + } + } + { + void *ref_ptr = scriptgo_object_get_engine_ref((void *)(uintptr_t)value->payload); + if (ref_ptr != NULL) { + scriptgo_engine_ref *ref = (scriptgo_engine_ref *)ref_ptr; + scriptgo_dynamic_engine_val *val = (scriptgo_dynamic_engine_val *)(uintptr_t)ref->handle; + if (val != NULL) { + *out = JS_DupValue(ctx, val->val); + return 0; + } + } + } return scriptgo_dynamic_object_to_js(ctx, value, out); case SCRIPTGO_TAG_ARRAY: return scriptgo_dynamic_array_to_js(ctx, value, out); case SCRIPTGO_TAG_FUNCTION: - if ((value->flags & SCRIPTGO_VALUE_ENGINE_REF) != 0) + if ((value->flags & SCRIPTGO_VALUE_ENGINE_REF) != 0) { + scriptgo_engine_ref *ref = find_engine_ref(value->payload); + if (ref != NULL) { + scriptgo_dynamic_engine_val *val = (scriptgo_dynamic_engine_val *)(uintptr_t)ref->handle; + if (val != NULL) { + *out = JS_DupValue(ctx, val->val); + return 0; + } + } return scriptgo_dynamic_engine_function_to_js(ctx, value, out); + } return scriptgo_dynamic_native_function_to_js(ctx, value, out); default: return -1; } @@ -315,6 +354,9 @@ static int scriptgo_dynamic_from_js(JSContext *ctx, JSValue value, scriptgo_valu out->tag = SCRIPTGO_TAG_STRING; out->flags = SCRIPTGO_VALUE_OWNED; out->payload = (uint64_t)(uintptr_t)copy; out->aux = (uint64_t)length; return 0; } + if (JS_IsProxy(value)) { + return scriptgo_dynamic_adopt_object(value, out); + } if (JS_IsPromise(value)) { return scriptgo_dynamic_materialize_promise(ctx, value, out); } @@ -369,6 +411,13 @@ static int scriptgo_dynamic_from_js(JSContext *ctx, JSValue value, scriptgo_valu JS_FreeCString(ctx, key); } JS_FreePropertyEnum(ctx, properties, count); + scriptgo_value engine_ref_val; + if (scriptgo_dynamic_adopt_object(value, &engine_ref_val) == 0) { + scriptgo_engine_ref *ref = find_engine_ref(engine_ref_val.payload); + if (ref != NULL) { + scriptgo_object_attach_engine_ref(object, ref); + } + } out->tag = SCRIPTGO_TAG_OBJECT; out->flags = 0; out->payload = (uint64_t)(uintptr_t)object; return 0; } diff --git a/internal/runtime/native/objects/runtime.c b/internal/runtime/native/objects/runtime.c index 741e32d..00e154c 100644 --- a/internal/runtime/native/objects/runtime.c +++ b/internal/runtime/native/objects/runtime.c @@ -21,10 +21,41 @@ extern int scriptgo_gc_is_registered(void *ptr); extern const char scriptgo_undefined_sentinel; +int scriptgo_object_property_unknown_get(void *handle, const char *property, scriptgo_value *out_value); +int scriptgo_object_property_unknown_set(void *handle, const char *property, const scriptgo_value *value); +int scriptgo_array_length(void *handle, int64_t *out_length); + static inline int is_invalid_object_handle(void *handle) { return handle == NULL || handle == (void *)&scriptgo_undefined_sentinel; } +typedef struct scriptgo_object_engine_ref_entry { + void *object; + void *ref; + struct scriptgo_object_engine_ref_entry *next; +} scriptgo_object_engine_ref_entry; + +static scriptgo_object_engine_ref_entry *scriptgo_object_engine_refs = NULL; + +void scriptgo_object_attach_engine_ref(void *object, void *ref) { + if (object == NULL || ref == NULL) return; + scriptgo_object_engine_ref_entry *entry = (scriptgo_object_engine_ref_entry *)malloc(sizeof(*entry)); + if (!entry) return; + entry->object = object; + entry->ref = ref; + entry->next = scriptgo_object_engine_refs; + scriptgo_object_engine_refs = entry; +} + +void *scriptgo_object_get_engine_ref(void *object) { + scriptgo_object_engine_ref_entry *curr = scriptgo_object_engine_refs; + while (curr) { + if (curr->object == object) return curr->ref; + curr = curr->next; + } + return NULL; +} + typedef struct { uint64_t magic; int64_t field_count; @@ -221,9 +252,31 @@ int scriptgo_unknown_number_property(const scriptgo_value *value, const char *pr return 0; } void *handle = (void *)(uintptr_t)value->payload; + if ((value->flags & SCRIPTGO_VALUE_ENGINE_REF) != 0 || *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_value prop_val; + scriptgo_value_init_undefined(&prop_val); + if (scriptgo_object_property_unknown_get(handle, property, &prop_val) == 0) { + if (prop_val.tag == SCRIPTGO_TAG_NUMBER) { + union { uint64_t u; double d; } bits; + bits.u = prop_val.payload; + *out_value = bits.d; + return 0; + } + } + return 0; + } if (!scriptgo_gc_is_registered(handle)) { return 0; } + if (value->tag == SCRIPTGO_OBJECT_TAG_ARRAY || scriptgo_gc_get_tag(handle) == 2) { + if (strcmp(property, "length") == 0) { + int64_t len = 0; + if (scriptgo_array_length(handle, &len) == 0) { + *out_value = (double)len; + return 0; + } + } + } uint32_t magic = *(uint32_t *)handle; if (magic == SCRIPTGO_MAGIC_TYPEDARRAY) { scriptgo_typed_array_view *view = (scriptgo_typed_array_view *)handle; @@ -1067,6 +1120,9 @@ extern int scriptgo_gc_get_tag(void *ptr); static void *resolve_object_handle(void *handle, int for_set) { if (is_invalid_object_handle(handle)) return NULL; + if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + return handle; + } if (((scriptgo_object *)handle)->magic == SCRIPTGO_OBJECT_MAGIC) { return handle; } @@ -1090,10 +1146,39 @@ int scriptgo_object_property_unknown_get(void *handle, const char *property, return object_fail("scriptgo object property output is invalid"); } scriptgo_value_init_undefined(out_value); + if (handle == NULL || property == NULL) return 0; + if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (scriptgo_dynamic_hook_prop_get != NULL) { + scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; + return scriptgo_dynamic_hook_prop_get(ref->handle, property, out_value); + } + return 0; + } + if (scriptgo_gc_get_tag(handle) == 2) { + if (strcmp(property, "length") == 0) { + int64_t len = 0; + if (scriptgo_array_length(handle, &len) == 0) { + out_value->tag = SCRIPTGO_TAG_NUMBER; + union { double d; uint64_t u; } bits; + bits.d = (double)len; + out_value->payload = bits.u; + return 0; + } + } + return 0; + } handle = resolve_object_handle(handle, 0); - if (handle == NULL || property == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; + if (handle == NULL) return 0; + if (((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; index = object_field_index((const scriptgo_object *)handle, property); - if (index < 0) return 0; + if (index < 0) { + void *ref_ptr = scriptgo_object_get_engine_ref(handle); + if (ref_ptr != NULL && scriptgo_dynamic_hook_prop_get != NULL) { + scriptgo_engine_ref *ref = (scriptgo_engine_ref *)ref_ptr; + return scriptgo_dynamic_hook_prop_get(ref->handle, property, out_value); + } + return 0; + } return scriptgo_object_unknown_get(handle, index, out_value); } @@ -1149,8 +1234,30 @@ int scriptgo_object_property_number_get(void *handle, const char *property, doub int index; if (out_value == NULL) return object_fail("scriptgo object property number output is invalid"); *out_value = NAN; + if (handle == NULL || property == NULL) return 0; + if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_value val; + scriptgo_value_init_undefined(&val); + if (scriptgo_object_property_unknown_get(handle, property, &val) != 0) return -1; + if (val.tag == SCRIPTGO_TAG_NUMBER) { + union { uint64_t u; double d; } bits; + bits.u = val.payload; + *out_value = bits.d; + } + return 0; + } + if (scriptgo_gc_get_tag(handle) == 2) { + if (strcmp(property, "length") == 0) { + int64_t len = 0; + if (scriptgo_array_length(handle, &len) == 0) { + *out_value = (double)len; + return 0; + } + } + return 0; + } handle = resolve_object_handle(handle, 0); - if (is_invalid_object_handle(handle) || property == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; + if (is_invalid_object_handle(handle) || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; index = object_field_index((const scriptgo_object *)handle, property); return index < 0 ? 0 : scriptgo_object_number_get(handle, index, out_value); } @@ -1160,6 +1267,15 @@ int scriptgo_object_property_string_get(void *handle, const char *property, cons if (out_value == NULL) return object_fail("scriptgo object property string output is invalid"); *out_value = &scriptgo_undefined_sentinel; handle = resolve_object_handle(handle, 0); + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (scriptgo_object_property_unknown_get(handle, property, &value) != 0) return -1; + if (value.tag == SCRIPTGO_TAG_STRING) { + *out_value = (const char *)(uintptr_t)value.payload; + } else if (value.tag == SCRIPTGO_TAG_NULL) { + *out_value = NULL; + } + return 0; + } if (is_invalid_object_handle(handle) || property == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; if (scriptgo_object_property_unknown_get(handle, property, &value) != 0) return -1; if (value.tag == SCRIPTGO_TAG_STRING) { @@ -1171,6 +1287,14 @@ int scriptgo_object_property_string_get(void *handle, const char *property, cons } int scriptgo_object_property_string_set(void *handle, const char *property, const char *value) { + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_value sval; + scriptgo_value_init_undefined(&sval); + sval.tag = SCRIPTGO_TAG_STRING; + sval.payload = (uint64_t)(uintptr_t)value; + sval.aux = value ? strlen(value) : 0; + return scriptgo_object_property_unknown_set(handle, property, &sval); + } handle = resolve_object_handle(handle, 1); int index = object_property_index_for_set(handle, property); return index < 0 ? object_fail("scriptgo object property string set failed") : scriptgo_object_string_set(handle, index, value); @@ -1181,6 +1305,15 @@ int scriptgo_object_property_bool_get(void *handle, const char *property, int32_ if (out_value == NULL) return object_fail("scriptgo object property bool output is invalid"); *out_value = 0; handle = resolve_object_handle(handle, 0); + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_value val; + scriptgo_value_init_undefined(&val); + if (scriptgo_object_property_unknown_get(handle, property, &val) != 0) return -1; + if (val.tag == SCRIPTGO_TAG_BOOLEAN) { + *out_value = val.payload != 0 ? 1 : 0; + } + return 0; + } if (is_invalid_object_handle(handle) || property == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; index = object_field_index((const scriptgo_object *)handle, property); return index < 0 ? 0 : scriptgo_object_bool_get(handle, index, out_value); @@ -1191,6 +1324,15 @@ int scriptgo_object_property_bigint_get(void *handle, const char *property, int6 if (out_value == NULL) return object_fail("scriptgo object property bigint output is invalid"); *out_value = 0; handle = resolve_object_handle(handle, 0); + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_value val; + scriptgo_value_init_undefined(&val); + if (scriptgo_object_property_unknown_get(handle, property, &val) != 0) return -1; + if (val.tag == SCRIPTGO_TAG_BIGINT) { + *out_value = (int64_t)val.payload; + } + return 0; + } if (is_invalid_object_handle(handle) || property == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; index = object_field_index((const scriptgo_object *)handle, property); return index < 0 ? 0 : scriptgo_object_bigint_get(handle, index, out_value); @@ -1207,18 +1349,41 @@ int scriptgo_object_property_ptr_get(void *handle, const char *property, void ** } int scriptgo_object_property_number_set(void *handle, const char *property, double value) { + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_value nval; + scriptgo_value_init_undefined(&nval); + nval.tag = SCRIPTGO_TAG_NUMBER; + union { double d; uint64_t u; } bits; + bits.d = value; + nval.payload = bits.u; + return scriptgo_object_property_unknown_set(handle, property, &nval); + } handle = resolve_object_handle(handle, 1); int index = object_property_index_for_set(handle, property); return index < 0 ? object_fail("scriptgo object property number set failed") : scriptgo_object_number_set(handle, index, value); } int scriptgo_object_property_bool_set(void *handle, const char *property, int32_t value) { + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_value bval; + scriptgo_value_init_undefined(&bval); + bval.tag = SCRIPTGO_TAG_BOOLEAN; + bval.payload = value != 0 ? 1 : 0; + return scriptgo_object_property_unknown_set(handle, property, &bval); + } handle = resolve_object_handle(handle, 1); int index = object_property_index_for_set(handle, property); return index < 0 ? object_fail("scriptgo object property bool set failed") : scriptgo_object_bool_set(handle, index, value); } int scriptgo_object_property_bigint_set(void *handle, const char *property, int64_t value) { + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_value bval; + scriptgo_value_init_undefined(&bval); + bval.tag = SCRIPTGO_TAG_BIGINT; + bval.payload = (uint64_t)value; + return scriptgo_object_property_unknown_set(handle, property, &bval); + } handle = resolve_object_handle(handle, 1); int index = object_property_index_for_set(handle, property); return index < 0 ? object_fail("scriptgo object property bigint set failed") : scriptgo_object_bigint_set(handle, index, value); @@ -1232,7 +1397,19 @@ int scriptgo_object_property_ptr_set(void *handle, const char *property, void *v int scriptgo_object_property_unknown_set(void *handle, const char *property, const scriptgo_value *value) { + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (scriptgo_dynamic_hook_prop_set != NULL) { + scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; + return scriptgo_dynamic_hook_prop_set(ref->handle, property, value); + } + return 0; + } handle = resolve_object_handle(handle, 1); + void *ref_ptr = scriptgo_object_get_engine_ref(handle); + if (ref_ptr != NULL && scriptgo_dynamic_hook_prop_set != NULL) { + scriptgo_engine_ref *ref = (scriptgo_engine_ref *)ref_ptr; + scriptgo_dynamic_hook_prop_set(ref->handle, property, value); + } int index = object_property_index_for_set(handle, property); return index < 0 ? object_fail("scriptgo object property set failed") : scriptgo_object_unknown_set(handle, index, value); } @@ -1276,6 +1453,29 @@ int scriptgo_object_type_get(void *handle, const char **out_type) { return 0; } +int scriptgo_object_delete_property(void *handle, const char *property, int32_t *out_result) { + if (out_result == NULL) return object_fail("scriptgo delete property null output"); + *out_result = 1; + if (is_invalid_object_handle(handle) || property == NULL) return 0; + if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (scriptgo_dynamic_hook_delete != NULL) { + scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; + return scriptgo_dynamic_hook_delete(ref->handle, property, out_result); + } + return 0; + } + handle = resolve_object_handle(handle, 0); + if (handle == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; + scriptgo_object *obj = (scriptgo_object *)handle; + int index = object_field_index(obj, property); + if (index >= 0 && index < obj->field_count) { + scriptgo_value undefined_val; + scriptgo_value_init_undefined(&undefined_val); + scriptgo_object_unknown_set(obj, index, &undefined_val); + } + return 0; +} + int scriptgo_object_instanceof(void *handle, const char *class_name, int32_t *out_result) { if (out_result == NULL) { return object_fail("scriptgo instanceof null output"); @@ -1284,6 +1484,14 @@ int scriptgo_object_instanceof(void *handle, const char *class_name, int32_t *ou *out_result = 0; return 0; } + if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (scriptgo_dynamic_hook_has != NULL) { + scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; + return scriptgo_dynamic_hook_has(ref->handle, class_name, out_result); + } + *out_result = 0; + return 0; + } if (!scriptgo_gc_is_registered(handle)) { *out_result = 0; return 0; @@ -1477,6 +1685,13 @@ int scriptgo_object_keys(void *handle, void **out_array) { return object_fail("scriptgo object keys arguments are invalid"); } handle = resolve_object_handle(handle, 0); + if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (scriptgo_dynamic_hook_keys != NULL) { + scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; + return scriptgo_dynamic_hook_keys(ref->handle, out_array); + } + return -1; + } if (handle == NULL) { if (scriptgo_array_new(0, (int64_t)sizeof(char *), out_array) != 0) return -1; return scriptgo_array_set_tag(*out_array, SCRIPTGO_OBJECT_TAG_STRING); diff --git a/internal/runtime/native/values/runtime.c b/internal/runtime/native/values/runtime.c index 430da2c..2556b49 100644 --- a/internal/runtime/native/values/runtime.c +++ b/internal/runtime/native/values/runtime.c @@ -6,17 +6,6 @@ int scriptgo_runtime_set_error(const char *message); -typedef struct scriptgo_engine_ref { - uint64_t magic; - uint32_t refs; - uint32_t tag; - scriptgo_dynamic_context *context; - uint64_t handle; - scriptgo_engine_retain_fn retain; - scriptgo_engine_release_fn release; - struct scriptgo_engine_ref *next; -} scriptgo_engine_ref; - struct scriptgo_dynamic_context { uint64_t magic; uint32_t live_refs; @@ -28,12 +17,17 @@ struct scriptgo_dynamic_context { scriptgo_dynamic_call_fn call; }; -#define SCRIPTGO_ENGINE_REF_MAGIC 0x5347524546455231ULL #define SCRIPTGO_CONTEXT_MAGIC 0x534743545856315FULL +scriptgo_dynamic_engine_prop_get_fn scriptgo_dynamic_hook_prop_get = NULL; +scriptgo_dynamic_engine_prop_set_fn scriptgo_dynamic_hook_prop_set = NULL; +scriptgo_dynamic_engine_has_fn scriptgo_dynamic_hook_has = NULL; +scriptgo_dynamic_engine_delete_fn scriptgo_dynamic_hook_delete = NULL; +scriptgo_dynamic_engine_keys_fn scriptgo_dynamic_hook_keys = NULL; + static scriptgo_engine_ref *scriptgo_engine_refs = NULL; -static scriptgo_engine_ref *find_engine_ref(uint64_t payload) { +scriptgo_engine_ref *find_engine_ref(uint64_t payload) { scriptgo_engine_ref *ref = scriptgo_engine_refs; while (ref != NULL) { if ((uint64_t)(uintptr_t)ref == payload) return ref; diff --git a/internal/runtime/native/values/scriptgo_value.h b/internal/runtime/native/values/scriptgo_value.h index 7be9bbf..3c670ed 100644 --- a/internal/runtime/native/values/scriptgo_value.h +++ b/internal/runtime/native/values/scriptgo_value.h @@ -54,6 +54,33 @@ typedef void (*scriptgo_engine_retain_fn)(scriptgo_dynamic_context *context, typedef void (*scriptgo_engine_release_fn)(scriptgo_dynamic_context *context, uint64_t handle); +#define SCRIPTGO_ENGINE_REF_MAGIC 0x5347524546455231ULL + +typedef struct scriptgo_engine_ref { + uint64_t magic; + uint32_t refs; + uint32_t tag; + scriptgo_dynamic_context *context; + uint64_t handle; + scriptgo_engine_retain_fn retain; + scriptgo_engine_release_fn release; + struct scriptgo_engine_ref *next; +} scriptgo_engine_ref; + +scriptgo_engine_ref *find_engine_ref(uint64_t payload); + +typedef int (*scriptgo_dynamic_engine_prop_get_fn)(uint64_t handle, const char *prop, scriptgo_value *out); +typedef int (*scriptgo_dynamic_engine_prop_set_fn)(uint64_t handle, const char *prop, const scriptgo_value *val); +typedef int (*scriptgo_dynamic_engine_has_fn)(uint64_t handle, const char *prop, int32_t *out); +typedef int (*scriptgo_dynamic_engine_delete_fn)(uint64_t handle, const char *prop, int32_t *out); +typedef int (*scriptgo_dynamic_engine_keys_fn)(uint64_t handle, void **out_array); + +extern scriptgo_dynamic_engine_prop_get_fn scriptgo_dynamic_hook_prop_get; +extern scriptgo_dynamic_engine_prop_set_fn scriptgo_dynamic_hook_prop_set; +extern scriptgo_dynamic_engine_has_fn scriptgo_dynamic_hook_has; +extern scriptgo_dynamic_engine_delete_fn scriptgo_dynamic_hook_delete; +extern scriptgo_dynamic_engine_keys_fn scriptgo_dynamic_hook_keys; + typedef struct { uint64_t allowed_tags; } scriptgo_value_constraint; diff --git a/internal/typescriptgo/syntax_expr.go b/internal/typescriptgo/syntax_expr.go index b3c7aea..0a37c7f 100644 --- a/internal/typescriptgo/syntax_expr.go +++ b/internal/typescriptgo/syntax_expr.go @@ -92,6 +92,16 @@ func syntaxExpressionInner(node *ast.Node, chk *checker.Checker) *SyntaxExpressi Left: inner, InferredType: "undefined", } + case ast.KindDeleteExpression: + delExpr := node.AsDeleteExpression() + inner := syntaxExpression(delExpr.Expression, chk) + return &SyntaxExpression{ + Span: sourceSpan(node), + Kind: "unary", + Operator: "delete", + Left: inner, + InferredType: "boolean", + } case ast.KindAsExpression: asExpr := node.AsAsExpression() targetType := syntaxType(asExpr.Type) From 9bc82dc6219b62b7073db042595282515307439b Mon Sep 17 00:00:00 2001 From: Pham Tan Minh Tien Date: Fri, 25 Sep 2026 19:01:27 +0700 Subject: [PATCH 2/2] fix(runtime): safely check engine refs to prevent out-of-bounds access and SEGV --- internal/runtime/native/objects/runtime.c | 75 ++++++++++++++--------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/internal/runtime/native/objects/runtime.c b/internal/runtime/native/objects/runtime.c index 00e154c..137fc3f 100644 --- a/internal/runtime/native/objects/runtime.c +++ b/internal/runtime/native/objects/runtime.c @@ -252,7 +252,10 @@ int scriptgo_unknown_number_property(const scriptgo_value *value, const char *pr return 0; } void *handle = (void *)(uintptr_t)value->payload; - if ((value->flags & SCRIPTGO_VALUE_ENGINE_REF) != 0 || *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle)) { + return 0; + } + if ((value->flags & SCRIPTGO_VALUE_ENGINE_REF) != 0 || find_engine_ref((uintptr_t)handle) != NULL) { scriptgo_value prop_val; scriptgo_value_init_undefined(&prop_val); if (scriptgo_object_property_unknown_get(handle, property, &prop_val) == 0) { @@ -1120,7 +1123,7 @@ extern int scriptgo_gc_get_tag(void *ptr); static void *resolve_object_handle(void *handle, int for_set) { if (is_invalid_object_handle(handle)) return NULL; - if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (find_engine_ref((uintptr_t)handle) != NULL) { return handle; } if (((scriptgo_object *)handle)->magic == SCRIPTGO_OBJECT_MAGIC) { @@ -1146,10 +1149,10 @@ int scriptgo_object_property_unknown_get(void *handle, const char *property, return object_fail("scriptgo object property output is invalid"); } scriptgo_value_init_undefined(out_value); - if (handle == NULL || property == NULL) return 0; - if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { if (scriptgo_dynamic_hook_prop_get != NULL) { - scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; return scriptgo_dynamic_hook_prop_get(ref->handle, property, out_value); } return 0; @@ -1234,8 +1237,9 @@ int scriptgo_object_property_number_get(void *handle, const char *property, doub int index; if (out_value == NULL) return object_fail("scriptgo object property number output is invalid"); *out_value = NAN; - if (handle == NULL || property == NULL) return 0; - if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { scriptgo_value val; scriptgo_value_init_undefined(&val); if (scriptgo_object_property_unknown_get(handle, property, &val) != 0) return -1; @@ -1266,8 +1270,9 @@ int scriptgo_object_property_string_get(void *handle, const char *property, cons scriptgo_value value; if (out_value == NULL) return object_fail("scriptgo object property string output is invalid"); *out_value = &scriptgo_undefined_sentinel; - handle = resolve_object_handle(handle, 0); - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { if (scriptgo_object_property_unknown_get(handle, property, &value) != 0) return -1; if (value.tag == SCRIPTGO_TAG_STRING) { *out_value = (const char *)(uintptr_t)value.payload; @@ -1276,6 +1281,7 @@ int scriptgo_object_property_string_get(void *handle, const char *property, cons } return 0; } + handle = resolve_object_handle(handle, 0); if (is_invalid_object_handle(handle) || property == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; if (scriptgo_object_property_unknown_get(handle, property, &value) != 0) return -1; if (value.tag == SCRIPTGO_TAG_STRING) { @@ -1287,7 +1293,9 @@ int scriptgo_object_property_string_get(void *handle, const char *property, cons } int scriptgo_object_property_string_set(void *handle, const char *property, const char *value) { - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { scriptgo_value sval; scriptgo_value_init_undefined(&sval); sval.tag = SCRIPTGO_TAG_STRING; @@ -1304,8 +1312,9 @@ int scriptgo_object_property_bool_get(void *handle, const char *property, int32_ int index; if (out_value == NULL) return object_fail("scriptgo object property bool output is invalid"); *out_value = 0; - handle = resolve_object_handle(handle, 0); - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { scriptgo_value val; scriptgo_value_init_undefined(&val); if (scriptgo_object_property_unknown_get(handle, property, &val) != 0) return -1; @@ -1314,6 +1323,7 @@ int scriptgo_object_property_bool_get(void *handle, const char *property, int32_ } return 0; } + handle = resolve_object_handle(handle, 0); if (is_invalid_object_handle(handle) || property == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; index = object_field_index((const scriptgo_object *)handle, property); return index < 0 ? 0 : scriptgo_object_bool_get(handle, index, out_value); @@ -1323,8 +1333,9 @@ int scriptgo_object_property_bigint_get(void *handle, const char *property, int6 int index; if (out_value == NULL) return object_fail("scriptgo object property bigint output is invalid"); *out_value = 0; - handle = resolve_object_handle(handle, 0); - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { scriptgo_value val; scriptgo_value_init_undefined(&val); if (scriptgo_object_property_unknown_get(handle, property, &val) != 0) return -1; @@ -1333,6 +1344,7 @@ int scriptgo_object_property_bigint_get(void *handle, const char *property, int6 } return 0; } + handle = resolve_object_handle(handle, 0); if (is_invalid_object_handle(handle) || property == NULL || ((scriptgo_object *)handle)->magic != SCRIPTGO_OBJECT_MAGIC) return 0; index = object_field_index((const scriptgo_object *)handle, property); return index < 0 ? 0 : scriptgo_object_bigint_get(handle, index, out_value); @@ -1349,7 +1361,9 @@ int scriptgo_object_property_ptr_get(void *handle, const char *property, void ** } int scriptgo_object_property_number_set(void *handle, const char *property, double value) { - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { scriptgo_value nval; scriptgo_value_init_undefined(&nval); nval.tag = SCRIPTGO_TAG_NUMBER; @@ -1364,7 +1378,9 @@ int scriptgo_object_property_number_set(void *handle, const char *property, doub } int scriptgo_object_property_bool_set(void *handle, const char *property, int32_t value) { - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { scriptgo_value bval; scriptgo_value_init_undefined(&bval); bval.tag = SCRIPTGO_TAG_BOOLEAN; @@ -1377,7 +1393,9 @@ int scriptgo_object_property_bool_set(void *handle, const char *property, int32_ } int scriptgo_object_property_bigint_set(void *handle, const char *property, int64_t value) { - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { scriptgo_value bval; scriptgo_value_init_undefined(&bval); bval.tag = SCRIPTGO_TAG_BIGINT; @@ -1397,9 +1415,10 @@ int scriptgo_object_property_ptr_set(void *handle, const char *property, void *v int scriptgo_object_property_unknown_set(void *handle, const char *property, const scriptgo_value *value) { - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + if (is_invalid_object_handle(handle) || property == NULL) return 0; + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { if (scriptgo_dynamic_hook_prop_set != NULL) { - scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; return scriptgo_dynamic_hook_prop_set(ref->handle, property, value); } return 0; @@ -1407,8 +1426,8 @@ int scriptgo_object_property_unknown_set(void *handle, const char *property, handle = resolve_object_handle(handle, 1); void *ref_ptr = scriptgo_object_get_engine_ref(handle); if (ref_ptr != NULL && scriptgo_dynamic_hook_prop_set != NULL) { - scriptgo_engine_ref *ref = (scriptgo_engine_ref *)ref_ptr; - scriptgo_dynamic_hook_prop_set(ref->handle, property, value); + scriptgo_engine_ref *eref = (scriptgo_engine_ref *)ref_ptr; + scriptgo_dynamic_hook_prop_set(eref->handle, property, value); } int index = object_property_index_for_set(handle, property); return index < 0 ? object_fail("scriptgo object property set failed") : scriptgo_object_unknown_set(handle, index, value); @@ -1457,9 +1476,9 @@ int scriptgo_object_delete_property(void *handle, const char *property, int32_t if (out_result == NULL) return object_fail("scriptgo delete property null output"); *out_result = 1; if (is_invalid_object_handle(handle) || property == NULL) return 0; - if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { if (scriptgo_dynamic_hook_delete != NULL) { - scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; return scriptgo_dynamic_hook_delete(ref->handle, property, out_result); } return 0; @@ -1484,9 +1503,9 @@ int scriptgo_object_instanceof(void *handle, const char *class_name, int32_t *ou *out_result = 0; return 0; } - if (*(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { if (scriptgo_dynamic_hook_has != NULL) { - scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; return scriptgo_dynamic_hook_has(ref->handle, class_name, out_result); } *out_result = 0; @@ -1684,14 +1703,14 @@ int scriptgo_object_keys(void *handle, void **out_array) { if (out_array == NULL || is_invalid_object_handle(handle)) { return object_fail("scriptgo object keys arguments are invalid"); } - handle = resolve_object_handle(handle, 0); - if (handle != NULL && *(const uint64_t *)handle == SCRIPTGO_ENGINE_REF_MAGIC) { + scriptgo_engine_ref *ref = find_engine_ref((uintptr_t)handle); + if (ref != NULL) { if (scriptgo_dynamic_hook_keys != NULL) { - scriptgo_engine_ref *ref = (scriptgo_engine_ref *)handle; return scriptgo_dynamic_hook_keys(ref->handle, out_array); } return -1; } + handle = resolve_object_handle(handle, 0); if (handle == NULL) { if (scriptgo_array_new(0, (int64_t)sizeof(char *), out_array) != 0) return -1; return scriptgo_array_set_tag(*out_array, SCRIPTGO_OBJECT_TAG_STRING);