Skip to content

Commit

Permalink
Added NewSharedDynamicObject() and NewSharedDynamicArray(). Closes do…
Browse files Browse the repository at this point in the history
  • Loading branch information
dop251 committed Jul 29, 2022
1 parent a070957 commit fca9b95
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 21 deletions.
2 changes: 1 addition & 1 deletion object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
63 changes: 48 additions & 15 deletions object_dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -428,15 +461,15 @@ 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 {
return true
}

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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand All @@ -598,15 +631,15 @@ 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
}

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
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down
180 changes: 179 additions & 1 deletion object_dynamic_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package goja

import "testing"
import (
"sync"
"testing"
)

type testDynObject struct {
r *Runtime
Expand Down Expand Up @@ -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)
}
}
4 changes: 2 additions & 2 deletions runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit fca9b95

Please sign in to comment.