Skip to content

Commit

Permalink
Update the length property when it's accessed, rather than on grow/sh…
Browse files Browse the repository at this point in the history
…rink. Maintain pointer type of a wrapped *[]any. Fixes #521.
  • Loading branch information
dop251 committed Jun 21, 2023
1 parent 28ee0ee commit 7749907
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 19 deletions.
8 changes: 7 additions & 1 deletion object_goarray_reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ func (o *objectGoArrayReflect) _init() {
o.objectGoReflect.init()
o.class = classArray
o.prototype = o.val.runtime.global.ArrayPrototype
o.updateLen()
o.baseObject._put("length", &o.lengthProp)
}

func (o *objectGoArrayReflect) init() {
o._init()
o.updateLen()
o.putIdx = o._putIdx
}

Expand Down Expand Up @@ -114,6 +114,9 @@ func (o *objectGoArrayReflect) getStr(name unistring.String, receiver Value) Val
if idx := strToGoIdx(name); idx >= 0 && idx < o.fieldsValue.Len() {
ownProp = o._getIdx(idx)
} else if name == "length" {
if o.fieldsValue.Kind() == reflect.Slice {
o.updateLen()
}
ownProp = &o.lengthProp
} else {
ownProp = o.objectGoReflect.getOwnPropStr(name)
Expand All @@ -133,6 +136,9 @@ func (o *objectGoArrayReflect) getOwnPropStr(name unistring.String) Value {
return nil
}
if name == "length" {
if o.fieldsValue.Kind() == reflect.Slice {
o.updateLen()
}
return &o.lengthProp
}
return o.objectGoReflect.getOwnPropStr(name)
Expand Down
17 changes: 12 additions & 5 deletions object_goslice.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ type objectGoSlice struct {
baseObject
data *[]interface{}
lengthProp valueProperty
origIsPtr bool
}

func (r *Runtime) newObjectGoSlice(data *[]interface{}) *objectGoSlice {
func (r *Runtime) newObjectGoSlice(data *[]interface{}, isPtr bool) *objectGoSlice {
obj := &Object{runtime: r}
a := &objectGoSlice{
baseObject: baseObject{
val: obj,
},
data: data,
data: data,
origIsPtr: isPtr,
}
obj.self = a
a.init()
Expand All @@ -35,7 +37,6 @@ func (o *objectGoSlice) init() {
o.prototype = o.val.runtime.global.ArrayPrototype
o.lengthProp.writable = true
o.extensible = true
o.updateLen()
o.baseObject._put("length", &o.lengthProp)
}

Expand All @@ -52,6 +53,7 @@ func (o *objectGoSlice) getStr(name unistring.String, receiver Value) Value {
if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) {
ownProp = o._getIdx(idx)
} else if name == "length" {
o.updateLen()
ownProp = &o.lengthProp
}

Expand Down Expand Up @@ -83,6 +85,7 @@ func (o *objectGoSlice) getOwnPropStr(name unistring.String) Value {
return nil
}
if name == "length" {
o.updateLen()
return &o.lengthProp
}
return nil
Expand Down Expand Up @@ -112,7 +115,6 @@ func (o *objectGoSlice) grow(size int) {
}
*o.data = (*o.data)[:size]
}
o.updateLen()
}

func (o *objectGoSlice) shrink(size int) {
Expand All @@ -121,7 +123,6 @@ func (o *objectGoSlice) shrink(size int) {
tail[k] = nil
}
*o.data = (*o.data)[:size]
o.updateLen()
}

func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) {
Expand Down Expand Up @@ -297,10 +298,16 @@ func (o *objectGoSlice) stringKeys(_ bool, accum []Value) []Value {
}

func (o *objectGoSlice) export(*objectExportCtx) interface{} {
if o.origIsPtr {
return o.data
}
return *o.data
}

func (o *objectGoSlice) exportType() reflect.Type {
if o.origIsPtr {
return reflectTypeArrayPtr
}
return reflectTypeArray
}

Expand Down
2 changes: 0 additions & 2 deletions object_goslice_reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ func (o *objectGoSliceReflect) grow(size int) {
}
o.fieldsValue.SetLen(size)
}
o.updateLen()
}

func (o *objectGoSliceReflect) shrink(size int) {
Expand All @@ -59,7 +58,6 @@ func (o *objectGoSliceReflect) shrink(size int) {
tail.Index(i).Set(zero)
}
o.fieldsValue.SetLen(size)
o.updateLen()
}

func (o *objectGoSliceReflect) putLength(v uint32, throw bool) bool {
Expand Down
25 changes: 25 additions & 0 deletions object_goslice_reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,31 @@ func TestGoSliceReflect111(t *testing.T) {
t.Log(a[0])
}

func TestGoSliceReflectExternalLenUpdate(t *testing.T) {
data := &[]int{1}

vm := New()
vm.Set("data", data)
vm.Set("append", func(a *[]int, v int) {
if a != data {
panic(vm.NewTypeError("a != data"))
}
*a = append(*a, v)
})

vm.testScriptWithTestLib(`
assert.sameValue(data.length, 1);
// modify with js
data.push(1);
assert.sameValue(data.length, 2);
// modify with go
append(data, 2);
assert.sameValue(data.length, 3);
`, _undefined, t)
}

func BenchmarkGoSliceReflectSet(b *testing.B) {
vm := New()
a := vm.ToValue([]int{1}).(*Object)
Expand Down
25 changes: 25 additions & 0 deletions object_goslice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,28 @@ func TestGoSliceToString(t *testing.T) {
t.Fatal(exp)
}
}

func TestGoSliceExternalLenUpdate(t *testing.T) {
data := &[]interface{}{1}

vm := New()
vm.Set("data", data)
vm.Set("append", func(a *[]interface{}, v int) {
if a != data {
panic(vm.NewTypeError("a != data"))
}
*a = append(*a, v)
})

vm.testScriptWithTestLib(`
assert.sameValue(data.length, 1);
// modify with js
data.push(1);
assert.sameValue(data.length, 2);
// modify with go
append(data, 2);
assert.sameValue(data.length, 3);
`, _undefined, t)
}
4 changes: 2 additions & 2 deletions runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -1914,12 +1914,12 @@ func (r *Runtime) toValue(i interface{}, origValue reflect.Value) Value {
m.init()
return obj
case []interface{}:
return r.newObjectGoSlice(&i).val
return r.newObjectGoSlice(&i, false).val
case *[]interface{}:
if i == nil {
return _null
}
return r.newObjectGoSlice(i).val
return r.newObjectGoSlice(i, true).val
}

if !origValue.IsValid() {
Expand Down
19 changes: 10 additions & 9 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,16 @@ var (
)

var (
reflectTypeInt = reflect.TypeOf(int64(0))
reflectTypeBool = reflect.TypeOf(false)
reflectTypeNil = reflect.TypeOf(nil)
reflectTypeFloat = reflect.TypeOf(float64(0))
reflectTypeMap = reflect.TypeOf(map[string]interface{}{})
reflectTypeArray = reflect.TypeOf([]interface{}{})
reflectTypeString = reflect.TypeOf("")
reflectTypeFunc = reflect.TypeOf((func(FunctionCall) Value)(nil))
reflectTypeError = reflect.TypeOf((*error)(nil)).Elem()
reflectTypeInt = reflect.TypeOf(int64(0))
reflectTypeBool = reflect.TypeOf(false)
reflectTypeNil = reflect.TypeOf(nil)
reflectTypeFloat = reflect.TypeOf(float64(0))
reflectTypeMap = reflect.TypeOf(map[string]interface{}{})
reflectTypeArray = reflect.TypeOf([]interface{}{})
reflectTypeArrayPtr = reflect.TypeOf((*[]interface{})(nil))
reflectTypeString = reflect.TypeOf("")
reflectTypeFunc = reflect.TypeOf((func(FunctionCall) Value)(nil))
reflectTypeError = reflect.TypeOf((*error)(nil)).Elem()
)

var intCache [256]Value
Expand Down

0 comments on commit 7749907

Please sign in to comment.