From fca9b959775c67aeaa16687f1ddafdce2d7fef83 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Fri, 29 Jul 2022 11:23:23 +0100 Subject: [PATCH] Added NewSharedDynamicObject() and NewSharedDynamicArray(). Closes #418. --- object.go | 2 +- object_dynamic.go | 63 +++++++++++---- object_dynamic_test.go | 180 ++++++++++++++++++++++++++++++++++++++++- runtime.go | 4 +- value.go | 17 ++++ vm.go | 4 +- 6 files changed, 249 insertions(+), 21 deletions(-) diff --git a/object.go b/object.go index 4f7a9737..3fe5a573 100644 --- a/object.go +++ b/object.go @@ -1423,7 +1423,7 @@ func toMethod(v Value) func(FunctionCall) Value { return call } } - panic(typeError(fmt.Sprintf("%s is not a method", v.String()))) + panic(newTypeError("%s is not a method", v.String())) } func instanceOfOperator(o Value, c *Object) bool { diff --git a/object_dynamic.go b/object_dynamic.go index c34cdcef..120bd04a 100644 --- a/object_dynamic.go +++ b/object_dynamic.go @@ -109,6 +109,22 @@ func (r *Runtime) NewDynamicObject(d DynamicObject) *Object { return v } +/* +NewSharedDynamicObject is similar to Runtime.NewDynamicObject but the resulting Object can be shared across multiple +Runtimes. The Object's prototype will be null. The provided DynamicObject must be goroutine-safe. +*/ +func NewSharedDynamicObject(d DynamicObject) *Object { + v := &Object{} + o := &dynamicObject{ + d: d, + baseDynamicObject: baseDynamicObject{ + val: v, + }, + } + v.self = o + return v +} + /* NewDynamicArray creates an array Object backed by the provided DynamicArray handler. It is similar to NewDynamicObject, the differences are: @@ -132,6 +148,23 @@ func (r *Runtime) NewDynamicArray(a DynamicArray) *Object { return v } +/* +NewSharedDynamicArray is similar to Runtime.NewDynamicArray but the resulting Object can be shared across multiple +Runtimes. The Object's prototype will be null. If you need to run Array's methods on it, use Array.prototype.[...].call(a, ...). +The provided DynamicArray must be goroutine-safe. +*/ +func NewSharedDynamicArray(a DynamicArray) *Object { + v := &Object{} + o := &dynamicArray{ + a: a, + baseDynamicObject: baseDynamicObject{ + val: v, + }, + } + v.self = o + return v +} + func (*dynamicObject) sortLen() int { return 0 } @@ -209,12 +242,12 @@ func (o *dynamicObject) _set(prop string, v Value, throw bool) bool { if o.d.Set(prop, v) { return true } - o.val.runtime.typeErrorResult(throw, "'Set' on a dynamic object returned false") + typeErrorResult(throw, "'Set' on a dynamic object returned false") return false } func (o *baseDynamicObject) _setSym(throw bool) { - o.val.runtime.typeErrorResult(throw, "Dynamic objects do not support Symbol properties") + typeErrorResult(throw, "Dynamic objects do not support Symbol properties") } func (o *dynamicObject) setOwnStr(p unistring.String, v Value, throw bool) bool { @@ -341,19 +374,19 @@ func (*baseDynamicObject) hasOwnPropertySym(_ *Symbol) bool { func (o *baseDynamicObject) checkDynamicObjectPropertyDescr(name fmt.Stringer, descr PropertyDescriptor, throw bool) bool { if descr.Getter != nil || descr.Setter != nil { - o.val.runtime.typeErrorResult(throw, "Dynamic objects do not support accessor properties") + typeErrorResult(throw, "Dynamic objects do not support accessor properties") return false } if descr.Writable == FLAG_FALSE { - o.val.runtime.typeErrorResult(throw, "Dynamic object field %q cannot be made read-only", name.String()) + typeErrorResult(throw, "Dynamic object field %q cannot be made read-only", name.String()) return false } if descr.Enumerable == FLAG_FALSE { - o.val.runtime.typeErrorResult(throw, "Dynamic object field %q cannot be made non-enumerable", name.String()) + typeErrorResult(throw, "Dynamic object field %q cannot be made non-enumerable", name.String()) return false } if descr.Configurable == FLAG_FALSE { - o.val.runtime.typeErrorResult(throw, "Dynamic object field %q cannot be made non-configurable", name.String()) + typeErrorResult(throw, "Dynamic object field %q cannot be made non-configurable", name.String()) return false } return true @@ -382,7 +415,7 @@ func (o *dynamicObject) _delete(prop string, throw bool) bool { if o.d.Delete(prop) { return true } - o.val.runtime.typeErrorResult(throw, "Could not delete property %q of a dynamic object", prop) + typeErrorResult(throw, "Could not delete property %q of a dynamic object", prop) return false } @@ -428,7 +461,7 @@ func (o *baseDynamicObject) setProto(proto *Object, throw bool) bool { } func (o *baseDynamicObject) hasInstance(v Value) bool { - panic(o.val.runtime.NewTypeError("Expecting a function in instanceof check, but got a dynamic object")) + panic(newTypeError("Expecting a function in instanceof check, but got a dynamic object")) } func (*baseDynamicObject) isExtensible() bool { @@ -436,7 +469,7 @@ func (*baseDynamicObject) isExtensible() bool { } func (o *baseDynamicObject) preventExtensions(throw bool) bool { - o.val.runtime.typeErrorResult(throw, "Cannot make a dynamic object non-extensible") + typeErrorResult(throw, "Cannot make a dynamic object non-extensible") return false } @@ -527,7 +560,7 @@ func (*baseDynamicObject) _putSym(s *Symbol, prop Value) { } func (o *baseDynamicObject) getPrivateEnv(*privateEnvType, bool) *privateElements { - panic(o.val.runtime.NewTypeError("Dynamic objects cannot have private elements")) + panic(newTypeError("Dynamic objects cannot have private elements")) } func (a *dynamicArray) sortLen() int { @@ -587,7 +620,7 @@ func (a *dynamicArray) _setLen(v Value, throw bool) bool { if a.a.SetLen(toIntStrict(v.ToInteger())) { return true } - a.val.runtime.typeErrorResult(throw, "'SetLen' on a dynamic array returned false") + typeErrorResult(throw, "'SetLen' on a dynamic array returned false") return false } @@ -598,7 +631,7 @@ func (a *dynamicArray) setOwnStr(p unistring.String, v Value, throw bool) bool { if idx, ok := strToInt(p); ok { return a._setIdx(idx, v, throw) } - a.val.runtime.typeErrorResult(throw, "Cannot set property %q on a dynamic array", p.String()) + typeErrorResult(throw, "Cannot set property %q on a dynamic array", p.String()) return false } @@ -606,7 +639,7 @@ func (a *dynamicArray) _setIdx(idx int, v Value, throw bool) bool { if a.a.Set(idx, v) { return true } - a.val.runtime.typeErrorResult(throw, "'Set' on a dynamic array returned false") + typeErrorResult(throw, "'Set' on a dynamic array returned false") return false } @@ -665,7 +698,7 @@ func (a *dynamicArray) defineOwnPropertyStr(name unistring.String, desc Property if idx, ok := strToInt(name); ok { return a._setIdx(idx, desc.Value, throw) } - a.val.runtime.typeErrorResult(throw, "Cannot define property %q on a dynamic array", name.String()) + typeErrorResult(throw, "Cannot define property %q on a dynamic array", name.String()) } return false } @@ -689,7 +722,7 @@ func (a *dynamicArray) deleteStr(name unistring.String, throw bool) bool { return a._delete(idx, throw) } if a.hasOwnPropertyStr(name) { - a.val.runtime.typeErrorResult(throw, "Cannot delete property %q on a dynamic array", name.String()) + typeErrorResult(throw, "Cannot delete property %q on a dynamic array", name.String()) return false } return true diff --git a/object_dynamic_test.go b/object_dynamic_test.go index a166079d..fedb8f6a 100644 --- a/object_dynamic_test.go +++ b/object_dynamic_test.go @@ -1,6 +1,9 @@ package goja -import "testing" +import ( + "sync" + "testing" +) type testDynObject struct { r *Runtime @@ -240,3 +243,178 @@ func TestDynamicArray(t *testing.T) { `, _undefined, t) } + +type testSharedDynObject struct { + sync.RWMutex + m map[string]Value +} + +func (t *testSharedDynObject) Get(key string) Value { + t.RLock() + val := t.m[key] + t.RUnlock() + return val +} + +func (t *testSharedDynObject) Set(key string, val Value) bool { + t.Lock() + t.m[key] = val + t.Unlock() + return true +} + +func (t *testSharedDynObject) Has(key string) bool { + t.RLock() + _, exists := t.m[key] + t.RUnlock() + return exists +} + +func (t *testSharedDynObject) Delete(key string) bool { + t.Lock() + delete(t.m, key) + t.Unlock() + return true +} + +func (t *testSharedDynObject) Keys() []string { + t.RLock() + keys := make([]string, 0, len(t.m)) + for k := range t.m { + keys = append(keys, k) + } + t.RUnlock() + return keys +} + +func TestSharedDynamicObject(t *testing.T) { + dynObj := &testSharedDynObject{m: make(map[string]Value, 10000)} + o := NewSharedDynamicObject(dynObj) + ch := make(chan error, 1) + go func() { + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + for (let i = 0; i < 10000; i++) { + o[i] = i; + } + `) + ch <- err + }() + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + for (let i = 0; i < 10000; i++) { + o[i] = i+1; + } + `) + if err != nil { + t.Fatal(err) + } + + err = <-ch + if err != nil { + t.Fatal(err) + } +} + +type testSharedDynArray struct { + sync.RWMutex + a []Value +} + +func (t *testSharedDynArray) Len() int { + t.RLock() + l := len(t.a) + t.RUnlock() + return l +} + +func (t *testSharedDynArray) Get(idx int) Value { + t.RLock() + defer t.RUnlock() + if idx < 0 { + idx += len(t.a) + } + if idx >= 0 && idx < len(t.a) { + return t.a[idx] + } + return nil +} + +func (t *testSharedDynArray) expand(newLen int) { + if newLen > cap(t.a) { + a := make([]Value, newLen) + copy(a, t.a) + t.a = a + } else { + t.a = t.a[:newLen] + } +} + +func (t *testSharedDynArray) Set(idx int, val Value) bool { + t.Lock() + defer t.Unlock() + if idx < 0 { + idx += len(t.a) + } + if idx < 0 { + return false + } + if idx >= len(t.a) { + t.expand(idx + 1) + } + t.a[idx] = val + return true +} + +func (t *testSharedDynArray) SetLen(i int) bool { + t.Lock() + defer t.Unlock() + if i > len(t.a) { + t.expand(i) + return true + } + if i < 0 { + return false + } + if i < len(t.a) { + tail := t.a[i:len(t.a)] + for j := range tail { + tail[j] = nil + } + t.a = t.a[:i] + } + return true +} + +func TestSharedDynamicArray(t *testing.T) { + dynObj := &testSharedDynArray{a: make([]Value, 10000)} + o := NewSharedDynamicArray(dynObj) + ch := make(chan error, 1) + go func() { + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + for (let i = 0; i < 10000; i++) { + o[i] = i; + } + `) + ch <- err + }() + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + for (let i = 0; i < 10000; i++) { + o[i] = i+1; + } + `) + if err != nil { + t.Fatal(err) + } + + err = <-ch + if err != nil { + t.Fatal(err) + } +} diff --git a/runtime.go b/runtime.go index a512813e..4c7f6917 100644 --- a/runtime.go +++ b/runtime.go @@ -1716,10 +1716,10 @@ func (r *Runtime) ToValue(i interface{}) Value { case nil: return _null case *Object: - if i == nil || i.runtime == nil { + if i == nil || i.self == nil { return _null } - if i.runtime != r { + if i.runtime != nil && i.runtime != r { panic(r.NewTypeError("Illegal runtime transition of an Object")) } return i diff --git a/value.go b/value.go index 8788a732..5e1d4235 100644 --- a/value.go +++ b/value.go @@ -1,6 +1,7 @@ package goja import ( + "fmt" "hash/maphash" "math" "reflect" @@ -1144,6 +1145,22 @@ func funcName(prefix string, n Value) valueString { return b.String() } +func newTypeError(args ...interface{}) typeError { + msg := "" + if len(args) > 0 { + f, _ := args[0].(string) + msg = fmt.Sprintf(f, args[1:]...) + } + return typeError(msg) +} + +func typeErrorResult(throw bool, args ...interface{}) { + if throw { + panic(newTypeError(args...)) + } + +} + func init() { for i := 0; i < 256; i++ { intCache[i] = valueInt(i - 128) diff --git a/vm.go b/vm.go index 8a7cd285..1d197ff5 100644 --- a/vm.go +++ b/vm.go @@ -3616,7 +3616,7 @@ func getHomeObject(v Value) *Object { return getHomeObject(fn.funcObj) } } - panic(typeError(fmt.Sprintf("Compiler bug: getHomeObject() on the wrong value: %T", v))) + panic(newTypeError("Compiler bug: getHomeObject() on the wrong value: %T", v)) } func (n *newArrowFunc) exec(vm *vm) { @@ -4352,7 +4352,7 @@ func (_typeof) exec(vm *vm) { case *Symbol: r = stringSymbol default: - panic(fmt.Errorf("Unknown type: %T", v)) + panic(newTypeError("Compiler bug: unknown type: %T", v)) } vm.stack[vm.sp-1] = r vm.pc++