Browse Source

Removed restriction on resizing unaddressable Go slices. See #265.

Dmitry Panov 4 years ago
parent
commit
a9b721bfc5
5 changed files with 70 additions and 42 deletions
  1. 3 16
      object_goslice.go
  2. 7 16
      object_goslice_reflect.go
  3. 15 0
      object_goslice_reflect_test.go
  4. 15 0
      object_goslice_test.go
  5. 30 10
      runtime.go

+ 3 - 16
object_goslice.go

@@ -9,16 +9,15 @@ import (
 
 
 type objectGoSlice struct {
 type objectGoSlice struct {
 	baseObject
 	baseObject
-	data            *[]interface{}
-	lengthProp      valueProperty
-	sliceExtensible bool
+	data       *[]interface{}
+	lengthProp valueProperty
 }
 }
 
 
 func (o *objectGoSlice) init() {
 func (o *objectGoSlice) init() {
 	o.baseObject.init()
 	o.baseObject.init()
 	o.class = classArray
 	o.class = classArray
 	o.prototype = o.val.runtime.global.ArrayPrototype
 	o.prototype = o.val.runtime.global.ArrayPrototype
-	o.lengthProp.writable = o.sliceExtensible
+	o.lengthProp.writable = true
 	o.extensible = true
 	o.extensible = true
 	o.updateLen()
 	o.updateLen()
 	o.baseObject._put("length", &o.lengthProp)
 	o.baseObject._put("length", &o.lengthProp)
@@ -111,10 +110,6 @@ func (o *objectGoSlice) shrink(size int) {
 
 
 func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) {
 func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) {
 	if idx >= len(*o.data) {
 	if idx >= len(*o.data) {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
-			return
-		}
 		o.grow(idx + 1)
 		o.grow(idx + 1)
 	}
 	}
 	(*o.data)[idx] = v.Export()
 	(*o.data)[idx] = v.Export()
@@ -124,16 +119,8 @@ func (o *objectGoSlice) putLength(v Value, throw bool) bool {
 	newLen := toIntStrict(toLength(v))
 	newLen := toIntStrict(toLength(v))
 	curLen := len(*o.data)
 	curLen := len(*o.data)
 	if newLen > curLen {
 	if newLen > curLen {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
-			return false
-		}
 		o.grow(newLen)
 		o.grow(newLen)
 	} else if newLen < curLen {
 	} else if newLen < curLen {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot shrink Go slice")
-			return false
-		}
 		o.shrink(newLen)
 		o.shrink(newLen)
 	}
 	}
 	return true
 	return true

+ 7 - 16
object_goslice_reflect.go

@@ -9,16 +9,19 @@ import (
 
 
 type objectGoSliceReflect struct {
 type objectGoSliceReflect struct {
 	objectGoReflect
 	objectGoReflect
-	lengthProp      valueProperty
-	sliceExtensible bool
+	lengthProp valueProperty
 }
 }
 
 
 func (o *objectGoSliceReflect) init() {
 func (o *objectGoSliceReflect) init() {
 	o.objectGoReflect.init()
 	o.objectGoReflect.init()
 	o.class = classArray
 	o.class = classArray
 	o.prototype = o.val.runtime.global.ArrayPrototype
 	o.prototype = o.val.runtime.global.ArrayPrototype
-	o.sliceExtensible = o.value.CanSet()
-	o.lengthProp.writable = o.sliceExtensible
+	if !o.value.CanSet() {
+		value := reflect.Indirect(reflect.New(o.value.Type()))
+		value.Set(o.value)
+		o.value = value
+	}
+	o.lengthProp.writable = true
 	o.updateLen()
 	o.updateLen()
 	o.baseObject._put("length", &o.lengthProp)
 	o.baseObject._put("length", &o.lengthProp)
 }
 }
@@ -98,10 +101,6 @@ func (o *objectGoSliceReflect) getOwnPropIdx(idx valueInt) Value {
 
 
 func (o *objectGoSliceReflect) putIdx(idx int, v Value, throw bool) bool {
 func (o *objectGoSliceReflect) putIdx(idx int, v Value, throw bool) bool {
 	if idx >= o.value.Len() {
 	if idx >= o.value.Len() {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot extend a Go unaddressable reflect slice")
-			return false
-		}
 		o.grow(idx + 1)
 		o.grow(idx + 1)
 	}
 	}
 	err := o.val.runtime.toReflectValue(v, o.value.Index(idx), &objectExportCtx{})
 	err := o.val.runtime.toReflectValue(v, o.value.Index(idx), &objectExportCtx{})
@@ -143,16 +142,8 @@ func (o *objectGoSliceReflect) putLength(v Value, throw bool) bool {
 	newLen := toIntStrict(toLength(v))
 	newLen := toIntStrict(toLength(v))
 	curLen := o.value.Len()
 	curLen := o.value.Len()
 	if newLen > curLen {
 	if newLen > curLen {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
-			return false
-		}
 		o.grow(newLen)
 		o.grow(newLen)
 	} else if newLen < curLen {
 	} else if newLen < curLen {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot shrink Go slice")
-			return false
-		}
 		o.shrink(newLen)
 		o.shrink(newLen)
 	}
 	}
 	return true
 	return true

+ 15 - 0
object_goslice_reflect_test.go

@@ -319,3 +319,18 @@ func TestGoSliceReflectPop(t *testing.T) {
 		t.Fatal(v)
 		t.Fatal(v)
 	}
 	}
 }
 }
+
+func TestGoSliceReflectPopNoPtr(t *testing.T) {
+	r := New()
+	a := []string{"1", "", "3"}
+	r.Set("a", a)
+	v, err := r.RunString(`
+	a.pop()
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !v.SameAs(asciiString("3")) {
+		t.Fatal(v)
+	}
+}

+ 15 - 0
object_goslice_test.go

@@ -200,6 +200,21 @@ func TestGoSlicePop(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestGoSlicePopNoPtr(t *testing.T) {
+	r := New()
+	a := []interface{}{1, nil, 3}
+	r.Set("a", a)
+	v, err := r.RunString(`
+	a.pop()
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !v.SameAs(intToValue(3)) {
+		t.Fatal(v)
+	}
+}
+
 func TestGoSliceShift(t *testing.T) {
 func TestGoSliceShift(t *testing.T) {
 	r := New()
 	r := New()
 	a := []interface{}{1, nil, 3}
 	a := []interface{}{1, nil, 3}

+ 30 - 10
runtime.go

@@ -1278,6 +1278,30 @@ func (r *Runtime) ClearInterrupt() {
 ToValue converts a Go value into a JavaScript value of a most appropriate type. Structural types (such as structs, maps
 ToValue converts a Go value into a JavaScript value of a most appropriate type. Structural types (such as structs, maps
 and slices) are wrapped so that changes are reflected on the original value which can be retrieved using Value.Export().
 and slices) are wrapped so that changes are reflected on the original value which can be retrieved using Value.Export().
 
 
+WARNING! There are two very important caveats to bear in mind when modifying wrapped Go structs, maps and
+slices.
+
+1. If a slice is passed by value (not as a pointer), resizing the slice does not reflect on the original
+value. Moreover, extending the slice may result in the underlying array being re-allocated and copied.
+For example:
+
+ a := []interface{}{1}
+ vm.Set("a", a)
+ vm.RunString(`a.push(2); a[0] = 0;`)
+ fmt.Println(a[0]) // prints "1"
+
+2. If a regular JavaScript Object is assigned as an element of a wrapped Go struct, map or array, it is
+Export()'ed and therefore copied. This may result in an unexpected behaviour in JavaScript:
+
+ m := map[string]interface{}{}
+ vm.Set("m", m)
+ vm.RunString(`
+ var obj = {test: false};
+ m.obj = obj; // obj gets Export()'ed, i.e. copied to a new map[string]interface{} and then this map is set as m["obj"]
+ obj.test = true; // note, m.obj.test is still false
+ `)
+ fmt.Println(m["obj"].(map[string]interface{})["test"]) // prints "false"
+
 Notes on individual types:
 Notes on individual types:
 
 
 Primitive types
 Primitive types
@@ -1415,14 +1439,11 @@ defining an external getter function.
 Slices
 Slices
 
 
 Slices are converted into host objects that behave largely like JavaScript Array. It has the appropriate
 Slices are converted into host objects that behave largely like JavaScript Array. It has the appropriate
-prototype and all the usual methods should work. There are, however, some caveats:
-
-- If the slice is not addressable, the array cannot be extended or shrunk. Any attempt to do so (by setting an index
-beyond the current length or by modifying the length) will result in a TypeError.
-
-- Converted Arrays may not contain holes (because Go slices cannot). This means that hasOwnProperty(n) always
-returns `true` if n < length. Deleting an item with an index < length will set it to a zero value (but the property will
-remain). Nil slice elements are be converted to `null`. Accessing an element beyond `length` returns `undefined`.
+prototype and all the usual methods should work. There is, however, a caveat: converted Arrays may not contain holes
+(because Go slices cannot). This means that hasOwnProperty(n) always returns `true` if n < length. Deleting an item with
+an index < length will set it to a zero value (but the property will remain). Nil slice elements are be converted to
+`null`. Accessing an element beyond `length` returns `undefined`. Also see the warning above about passing slices as
+values (as opposed to pointers).
 
 
 Any other type is converted to a generic reflect based host object. Depending on the underlying type it behaves similar
 Any other type is converted to a generic reflect based host object. Depending on the underlying type it behaves similar
 to a Number, String, Boolean or Object.
 to a Number, String, Boolean or Object.
@@ -1539,8 +1560,7 @@ func (r *Runtime) ToValue(i interface{}) Value {
 			baseObject: baseObject{
 			baseObject: baseObject{
 				val: obj,
 				val: obj,
 			},
 			},
-			data:            i,
-			sliceExtensible: true,
+			data: i,
 		}
 		}
 		obj.self = a
 		obj.self = a
 		a.init()
 		a.init()