diff --git a/builtin_caller.go b/builtin_caller.go new file mode 100644 index 00000000..64525dfe --- /dev/null +++ b/builtin_caller.go @@ -0,0 +1,278 @@ +package goja + +import ( + "hash/maphash" + "reflect" + "strconv" + "unsafe" + + "github.com/dop251/goja/unistring" +) + +var ( + reflectTypeGoCaller = reflect.TypeOf(&valueGoCaller{}) + _GoRaw = &valueGoCaller{ + value: 1, + hashValue: randomHash(), + stringValue: asciiString("GoRaw"), + } + _GoNumber = &valueGoCaller{ + value: 2, + hashValue: randomHash(), + stringValue: asciiString("GoNumber"), + } + _GoRawNumber = &valueGoCaller{ + value: 1 | 2, + hashValue: randomHash(), + stringValue: asciiString("GoRawNumber"), + } + + _GoAsync = &valueGoCaller{ + value: 4, + hashValue: randomHash(), + stringValue: asciiString("GoAsync"), + } + _GoAsyncRaw = &valueGoCaller{ + value: 1 | 4, + hashValue: randomHash(), + stringValue: asciiString("GoAsyncRaw"), + } + _GoAsyncNumber = &valueGoCaller{ + value: 2 | 4, + hashValue: randomHash(), + stringValue: asciiString("GoAsyncNumber"), + } + _GoAsyncRawNumber = &valueGoCaller{ + value: 1 | 2 | 4, + hashValue: randomHash(), + stringValue: asciiString("GoAsyncRawNumber"), + } +) + +func (r *Runtime) initCaller() { + o := r.globalObject.self + o._putProp("GoRaw", _GoRaw, false, false, true) + o._putProp("GoNumber", _GoNumber, false, false, true) + o._putProp("GoRawNumber", _GoRawNumber, false, false, true) + + o._putProp("GoAsync", _GoAsync, false, false, true) + o._putProp("GoAsyncRaw", _GoAsyncRaw, false, false, true) + o._putProp("GoAsyncNumber", _GoAsyncNumber, false, false, true) + o._putProp("GoAsyncRawNumber", _GoAsyncRawNumber, false, false, true) +} + +// Set up the runner so that calling go functions asynchronously can called resolve/reject on the loop +func (r *Runtime) SetRunOnLoop(runner func(f func(*Runtime))) { + r.runOnLoop = runner +} + +// called f on the loop, must call SetRunOnLoop to set the runner before +func (r *Runtime) RunOnLoop(f func(*Runtime)) { + r.runOnLoop(f) +} + +func toValue64(v interface{}) interface{} { + if strconv.IntSize < 64 { + switch i := v.(type) { + case int64: + return Int64(i) + case uint64: + return Uint64(i) + case []int64: + return *(*[]Int64)(unsafe.Pointer(&i)) + case []uint64: + return *(*[]Uint64)(unsafe.Pointer(&i)) + } + } else { + switch i := v.(type) { + case int64: + return Int64(i) + case int: + return Int64(i) + case uint64: + return Uint64(i) + case uint: + return Uint64(i) + case []int64: + return *(*[]Int64)(unsafe.Pointer(&i)) + case []int: + return *(*[]Int64)(unsafe.Pointer(&i)) + case []uint64: + return *(*[]Uint64)(unsafe.Pointer(&i)) + case []uint: + return *(*[]Uint64)(unsafe.Pointer(&i)) + } + } + return v +} + +type Int64 int64 +type Uint64 uint64 + +func (v Int64) String() string { + return strconv.FormatInt(int64(v), 10) +} +func (v Uint64) String() string { + return strconv.FormatUint(uint64(v), 10) +} +func (v Int64) Uint64() Uint64 { + return Uint64(v) +} +func (v Uint64) Int64() Int64 { + return Int64(v) +} +func (v Int64) Value() int64 { + return int64(v) +} +func (v Uint64) Value() uint64 { + return uint64(v) +} +func (v Int64) Add(o Int64) Int64 { + return v + o +} +func (v Uint64) Add(o Uint64) Uint64 { + return v + o +} +func (v Int64) Sub(o Int64) Int64 { + return v - o +} +func (v Uint64) Sub(o Uint64) Uint64 { + return v - o +} +func (v Int64) Mul(o Int64) Int64 { + return v * o +} +func (v Uint64) Mul(o Uint64) Uint64 { + return v * o +} +func (v Int64) Div(o Int64) Int64 { + return v / o +} +func (v Uint64) Div(o Uint64) Uint64 { + return v / o +} +func (v Int64) Mod(o Int64) Int64 { + return v % o +} +func (v Uint64) Mod(o Uint64) Uint64 { + return v % o +} +func (v Int64) Abs() Int64 { + if v < 0 { + return -v + } + return v +} +func (v Int64) Neg() Int64 { + return -v +} +func (v Int64) And(o Int64) Int64 { + return v & o +} +func (v Uint64) And(o Uint64) Uint64 { + return v & o +} +func (v Int64) Or(o Int64) Int64 { + return v | o +} +func (v Uint64) Or(o Uint64) Uint64 { + return v | o +} +func (v Int64) Xor(o Int64) Int64 { + return v ^ o +} +func (v Uint64) Xor(o Uint64) Uint64 { + return v ^ o +} +func (v Int64) Not() Int64 { + return ^v +} +func (v Uint64) Not() Uint64 { + return ^v +} +func (v Int64) Lsh(n int) Int64 { + return v << n +} +func (v Uint64) Lsh(n int) Uint64 { + return v << n +} +func (v Int64) Rsh(n int) Int64 { + return v >> n +} +func (v Uint64) Rsh(n int) Uint64 { + return v >> n +} +func (v Int64) Cmp(o Int64) int { + if v < o { + return -1 + } else if v > o { + return 1 + } + return 0 +} +func (v Uint64) Cmp(o Uint64) int { + if v < o { + return -1 + } else if v > o { + return 1 + } + return 0 +} + +type valueGoCaller struct { + value int64 + hashValue uint64 + stringValue asciiString +} + +func (v *valueGoCaller) ToInteger() int64 { + return v.value +} +func (v *valueGoCaller) toString() valueString { + return v.stringValue +} +func (v *valueGoCaller) string() unistring.String { + return v.stringValue.string() +} +func (v *valueGoCaller) ToString() Value { + return v.stringValue +} +func (v *valueGoCaller) String() string { + return string(v.stringValue) +} +func (v *valueGoCaller) ToFloat() float64 { + return float64(v.value) +} +func (v *valueGoCaller) ToNumber() Value { + return valueInt(v.value) +} +func (v *valueGoCaller) ToBoolean() bool { + return true +} +func (v *valueGoCaller) ToObject(r *Runtime) *Object { + r.typeErrorResult(true, "Cannot convert "+string(v.stringValue)+" to object") + return nil +} +func (v *valueGoCaller) SameAs(other Value) bool { + _, same := other.(*valueGoCaller) + return same +} +func (v *valueGoCaller) Equals(other Value) bool { + return v.value == other.ToInteger() +} +func (v *valueGoCaller) StrictEquals(other Value) bool { + o, same := other.(*valueGoCaller) + return same && o.value == v.value +} +func (v *valueGoCaller) Export() interface{} { + return v +} +func (v *valueGoCaller) ExportType() reflect.Type { + return reflectTypeGoCaller +} +func (v *valueGoCaller) baseObject(r *Runtime) *Object { + return nil +} +func (v *valueGoCaller) hash(hasher *maphash.Hash) uint64 { + return v.hashValue +} diff --git a/runtime.go b/runtime.go index 32593dc7..29442274 100644 --- a/runtime.go +++ b/runtime.go @@ -196,6 +196,7 @@ type Runtime struct { promiseRejectionTracker PromiseRejectionTracker asyncContextTracker AsyncContextTracker + runOnLoop func(func(*Runtime)) } type StackFrame struct { @@ -464,6 +465,7 @@ func (r *Runtime) init() { r.initMap() r.initSet() r.initPromise() + r.initCaller() r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "", nil, 0) r.global.throwerProperty = &valueProperty{ @@ -2011,6 +2013,11 @@ func (r *Runtime) wrapReflectFunc(value reflect.Value) func(FunctionCall) Value typ := value.Type() nargs := typ.NumIn() var in []reflect.Value + var style uint64 + if len(call.Arguments) > 0 && call.Arguments[0].ExportType() == reflectTypeGoCaller { + style = uint64(call.Arguments[0].ToInteger()) + call.Arguments = call.Arguments[1:] + } if l := len(call.Arguments); l < nargs { // fill missing arguments with zero values @@ -2053,39 +2060,90 @@ func (r *Runtime) wrapReflectFunc(value reflect.Value) func(FunctionCall) Value in[i] = v } + if style&4 != 0 { + runner := r.runOnLoop + if runner == nil { + panic(r.NewGoError(errors.New(`runner nil, please call Runtime.SetRunOnLoop to set.`))) + } + promise, resolve, reject := r.NewPromise() + go func() { + out, err := func() (out []reflect.Value, err interface{}) { + defer func() { + if x := recover(); x != nil { + err = x + } + }() + out = value.Call(in) + return + }() + if err != nil { + reject(err) + return + } + value, err := r.wrapReflectFuncReturn(style, out) + if err == nil { + resolve(value) + } else { + reject(err) + } + }() + return r.ToValue(promise) + } out := value.Call(in) - if len(out) == 0 { - return _undefined + value, err := r.wrapReflectFuncReturn(style, out) + if err != nil { + panic(err) } + return value + } +} +func (r *Runtime) wrapReflectFuncReturn(style uint64, out []reflect.Value) (value Value, e interface{}) { + if len(out) == 0 { + value = _undefined + return + } + if style&1 == 0 { if last := out[len(out)-1]; last.Type() == reflectTypeError { if !last.IsNil() { err := last.Interface().(error) if _, ok := err.(*Exception); ok { - panic(err) + e = err + return } if isUncatchableException(err) { - panic(err) + e = err + return } - panic(r.NewGoError(err)) + e = r.NewGoError(err) + return } out = out[:len(out)-1] } - - switch len(out) { - case 0: - return _undefined - case 1: - return r.ToValue(out[0].Interface()) - default: - s := make([]interface{}, len(out)) + } + switch len(out) { + case 0: + value = _undefined + case 1: + if style&2 == 0 { + value = r.ToValue(out[0].Interface()) + } else { + value = r.ToValue(toValue64(out[0].Interface())) + } + default: + s := make([]interface{}, len(out)) + if style&2 == 0 { for i, v := range out { s[i] = v.Interface() } - - return r.ToValue(s) + } else { + for i, v := range out { + s[i] = toValue64(v.Interface()) + } } + value = r.ToValue(s) } + return } func (r *Runtime) toReflectValue(v Value, dst reflect.Value, ctx *objectExportCtx) error { diff --git a/runtime_wrapReflectFunc_test.go b/runtime_wrapReflectFunc_test.go new file mode 100644 index 00000000..3f50ce0d --- /dev/null +++ b/runtime_wrapReflectFunc_test.go @@ -0,0 +1,213 @@ +package goja_test + +import ( + "errors" + "fmt" + "math" + "strconv" + "sync" + "testing" + + "github.com/dop251/goja" +) + +func TestWrapReflectFunc(t *testing.T) { + vm := goja.New() + done := make(chan struct{}) + ch := make(chan func(*goja.Runtime)) + var wait sync.WaitGroup + wait.Add(1) + go func() { + defer wait.Done() + select { + case <-done: + return + case f := <-ch: + f(vm) + } + }() + vm.SetRunOnLoop(func(f func(vm *goja.Runtime)) { + select { + case <-done: + case ch <- f: + } + }) + vm.Set(`close`, func() { + close(done) + }) + vm.Set(`println`, func(vals ...interface{}) { + fmt.Println(vals...) + }) + vm.Set(`newError`, func(str string) error { + return errors.New(str) + }) + vm.Set(`getInt64`, func() int64 { + return math.MaxInt64 + }) + vm.Set(`getInt64s`, func() []int64 { + return []int64{math.MaxInt64, -100} + }) + vm.Set(`setInt64`, func(v int64) { + if v != math.MaxInt64 { + t.Errorf(`int64 want %v, but got %v`, int64(math.MaxInt64), v) + t.FailNow() + } + }) + vm.Set(`getUint64`, func() uint64 { + return math.MaxUint64 + }) + vm.Set(`getUint64s`, func() []uint64 { + return []uint64{math.MaxUint64, 100} + }) + vm.Set(`setUint64`, func(v uint64) { + if v != math.MaxUint64 { + t.Errorf(`uint64 want %v, but got %v`, uint64(math.MaxUint64), v) + t.FailNow() + } + }) + vm.Set(`get64`, func() (int64, uint64) { + return int64(math.MaxInt64), uint64(math.MaxUint64) + }) + vm.Set(`failMessage`, func(args ...interface{}) { + t.Error(args...) + t.FailNow() + }) + vm.Set(`parseInt64`, func(s string) (goja.Int64, error) { + v, e := strconv.ParseInt(s, 10, 64) + if e != nil { + return 0, e + } + return goja.Int64(v), nil + }) + vm.Set(`parseUint64`, func(s string) (goja.Int64, error) { + v, e := strconv.ParseInt(s, 10, 64) + if e != nil { + return 0, e + } + return goja.Int64(v), nil + }) + vm.RunScript(`test.js`, ` +const MaxInt64='`+strconv.FormatInt(int64(math.MaxInt64), 10)+`' +const MaxUint64='`+strconv.FormatUint(uint64(math.MaxUint64), 10)+`' +function assertEqual(want,actual,label){ + if(want!=actual){ + failMessage(label,'want',want, ', but get',actual) + } +} + +function abc(){ + // int64 + setInt64(getInt64(GoNumber)) + assertEqual(MaxInt64,getInt64(GoNumber).toString(),'getInt64') + assertEqual(getInt64(GoNumber),getInt64(GoNumber),'getInt64 ==') + + let v = parseInt64(GoNumber,"2") + assertEqual(2,v,'getInt64 ==') + assertEqual(3,v.Add(1),'Int64 add') + assertEqual(1,v.Sub(1),'Int64 sub') + assertEqual(-3,v.Sub(5),'Int64 sub5') + assertEqual(6,v.Mul(3),'Int64 mul') + assertEqual(3,v.Mul(3).Div(2),'Int64 div') + assertEqual(2,v.Mul(5).Div(4),'Int64 mod') + assertEqual(-2,v.Neg(),'Int64 neg') + assertEqual(2,v.Neg().Abs(),'Int64 abs') + assertEqual(2,v.And(3),'Int64 and') + assertEqual(6,v.Or(4),'Int64 or') + assertEqual(4,v.Xor(6),'Int64 xor') + assertEqual(2,v.Not().Not(),'Int64 not') + assertEqual(8,v.Lsh(2),'Int64 left shift') + assertEqual(1,v.Rsh(1),'Int64 right shift') + assertEqual(0,v.Cmp(2),'Int64 cmp 2') + assertEqual(-1,v.Cmp(4),'Int64 cmp 4') + assertEqual(1,v.Cmp(0),'Int64 cmp 0') + + // uint64 + setUint64(getUint64(GoRawNumber)) + assertEqual(MaxUint64,getUint64(GoNumber).toString(),'getUint64') + assertEqual(getUint64(GoNumber),getUint64(GoNumber),'getUint64 ==') + + v = parseUint64(GoNumber,"2") + assertEqual(2,v,'getInt64 ==') + assertEqual(3,v.Add(1),'Uint64 add') + assertEqual(1,v.Sub(1),'Uint64 sub') + assertEqual(-3,v.Sub(5),'Uint64 sub5') + assertEqual(6,v.Mul(3),'Uint64 mul') + assertEqual(3,v.Mul(3).Div(2),'Uint64 div') + assertEqual(2,v.Mul(5).Div(4),'Uint64 mod') + assertEqual(2,v.And(3),'Uint64 and') + assertEqual(6,v.Or(4),'Uint64 or') + assertEqual(4,v.Xor(6),'Uint64 xor') + assertEqual(2,v.Not().Not(),'Uint64 not') + assertEqual(8,v.Lsh(2),'Uint64 left shift') + assertEqual(1,v.Rsh(1),'Uint64 right shift') + assertEqual(0,v.Cmp(2),'Uint64 cmp 2') + assertEqual(-1,v.Cmp(4),'Uint64 cmp 4') + assertEqual(1,v.Cmp(0),'Uint64 cmp 0') + + // slice 64 + const [i64,u64] = get64(GoRawNumber) + assertEqual(MaxInt64,i64.toString(),'i64') + assertEqual(MaxUint64,u64.toString(),'u64') + + let vals = getInt64s(GoRawNumber) + assertEqual(MaxInt64,vals[0].toString(),'getInt64s') + assertEqual(-100,vals[1],'getInt64s -100') + vals.push(vals[0]) + assertEqual(MaxInt64,vals[0].toString(),'getInt64s') + assertEqual(-100,vals[1],'getInt64s -100') + assertEqual(MaxInt64,vals[2].toString(),'getInt64s 2') + + vals = getUint64s(GoRawNumber) + assertEqual(MaxUint64,vals[0].toString(),'getUint64s') + assertEqual(100,vals[1],'getUint64s 100') + vals.push(vals[0]) + assertEqual(MaxUint64,vals[0].toString(),'getUint64s') + assertEqual(100,vals[1],'getUint64s 100') + assertEqual(MaxUint64,vals[2].toString(),'getUint64s 2') + + return 1 +} +function err(){ + return newError(GoRaw,"ok") +} +function asyncErr(){ + const v= newError(GoAsyncRaw,"async") + v.then((e)=>{ + assertEqual(e.Error(),"async","then") + close() + }) +}`) + + var call goja.Callable + call, _ = goja.AssertFunction(vm.Get(`abc`)) + v, e := call(goja.Undefined()) + if e != nil { + t.Errorf(`js abc() err: %s`, e.Error()) + t.FailNow() + } + val := v.Export().(int64) + if val != 1 { + t.Errorf(`js abc() return %v, but want 1`, val) + t.FailNow() + } + call, _ = goja.AssertFunction(vm.Get(`err`)) + v, e = call(goja.Undefined()) + if e != nil { + t.Errorf(`js err() err: %s`, e.Error()) + t.FailNow() + } + v0 := v.Export().(error) + if v0.Error() != "ok" { + t.Errorf(`js err() want "ok", but got %s`, v0.Error()) + t.FailNow() + } + + call, _ = goja.AssertFunction(vm.Get(`asyncErr`)) + _, e = call(goja.Undefined()) + if e != nil { + t.Errorf(`js asyncErr() err: %s`, e.Error()) + t.FailNow() + } + + wait.Wait() +}