Kaynağa Gözat

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

Dmitry Panov 4 yıl önce
ebeveyn
işleme
a9b721bfc5
5 değiştirilmiş dosya ile 70 ekleme ve 42 silme
  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 {
 	baseObject
-	data            *[]interface{}
-	lengthProp      valueProperty
-	sliceExtensible bool
+	data       *[]interface{}
+	lengthProp valueProperty
 }
 
 func (o *objectGoSlice) init() {
 	o.baseObject.init()
 	o.class = classArray
 	o.prototype = o.val.runtime.global.ArrayPrototype
-	o.lengthProp.writable = o.sliceExtensible
+	o.lengthProp.writable = true
 	o.extensible = true
 	o.updateLen()
 	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) {
 	if idx >= len(*o.data) {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
-			return
-		}
 		o.grow(idx + 1)
 	}
 	(*o.data)[idx] = v.Export()
@@ -124,16 +119,8 @@ func (o *objectGoSlice) putLength(v Value, throw bool) bool {
 	newLen := toIntStrict(toLength(v))
 	curLen := len(*o.data)
 	if newLen > curLen {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
-			return false
-		}
 		o.grow(newLen)
 	} else if newLen < curLen {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot shrink Go slice")
-			return false
-		}
 		o.shrink(newLen)
 	}
 	return true

+ 7 - 16
object_goslice_reflect.go

@@ -9,16 +9,19 @@ import (
 
 type objectGoSliceReflect struct {
 	objectGoReflect
-	lengthProp      valueProperty
-	sliceExtensible bool
+	lengthProp valueProperty
 }
 
 func (o *objectGoSliceReflect) init() {
 	o.objectGoReflect.init()
 	o.class = classArray
 	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.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 {
 	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)
 	}
 	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))
 	curLen := o.value.Len()
 	if newLen > curLen {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
-			return false
-		}
 		o.grow(newLen)
 	} else if newLen < curLen {
-		if !o.sliceExtensible {
-			o.val.runtime.typeErrorResult(throw, "Cannot shrink Go slice")
-			return false
-		}
 		o.shrink(newLen)
 	}
 	return true

+ 15 - 0
object_goslice_reflect_test.go

@@ -319,3 +319,18 @@ func TestGoSliceReflectPop(t *testing.T) {
 		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) {
 	r := New()
 	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
 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:
 
 Primitive types
@@ -1415,14 +1439,11 @@ defining an external getter function.
 Slices
 
 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
 to a Number, String, Boolean or Object.
@@ -1539,8 +1560,7 @@ func (r *Runtime) ToValue(i interface{}) Value {
 			baseObject: baseObject{
 				val: obj,
 			},
-			data:            i,
-			sliceExtensible: true,
+			data: i,
 		}
 		obj.self = a
 		a.init()