Browse Source

Fixed typed arrays' defineProperty and indexing. Fixes #308.

Dmitry Panov 4 years ago
parent
commit
946559a566
14 changed files with 530 additions and 189 deletions
  1. 5 115
      array.go
  2. 5 5
      array_sparse.go
  3. 13 0
      array_test.go
  4. 12 8
      builtin_proxy.go
  5. 19 1
      builtin_proxy_test.go
  6. 14 2
      object.go
  7. 6 6
      object_dynamic.go
  8. 4 16
      proxy.go
  9. 221 4
      runtime.go
  10. 78 0
      runtime_test.go
  11. 2 2
      string_ascii.go
  12. 16 7
      tc39_test.go
  13. 58 23
      typedarrays.go
  14. 77 0
      typedarrays_test.go

+ 5 - 115
array.go

@@ -162,7 +162,7 @@ func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value {
 
 func (a *arrayObject) getOwnPropStr(name unistring.String) Value {
 	if len(a.values) > 0 {
-		if i := strToIdx(name); i != math.MaxUint32 {
+		if i := strToArrayIdx(name); i != math.MaxUint32 {
 			if i < uint32(len(a.values)) {
 				return a.values[i]
 			}
@@ -264,7 +264,7 @@ func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
 }
 
 func (a *arrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		return a._setOwnIdx(idx, val, throw)
 	} else {
 		if name == "length" {
@@ -325,7 +325,7 @@ func (a *arrayObject) ownKeys(all bool, accum []Value) []Value {
 }
 
 func (a *arrayObject) hasOwnPropertyStr(name unistring.String) bool {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		return idx < uint32(len(a.values)) && a.values[idx] != nil
 	} else {
 		return a.baseObject.hasOwnPropertyStr(name)
@@ -433,7 +433,7 @@ func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, th
 }
 
 func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 	}
 	if name == "length" {
@@ -467,7 +467,7 @@ func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
 }
 
 func (a *arrayObject) deleteStr(name unistring.String, throw bool) bool {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 	}
 	return a.baseObject.deleteStr(name, throw)
@@ -521,113 +521,3 @@ func toIdx(v valueInt) uint32 {
 	}
 	return math.MaxUint32
 }
-
-func strToIdx64(s unistring.String) int64 {
-	if s == "" {
-		return -1
-	}
-	l := len(s)
-	if s[0] == '0' {
-		if l == 1 {
-			return 0
-		}
-		return -1
-	}
-	var n int64
-	if l < 19 {
-		// guaranteed not to overflow
-		for i := 0; i < len(s); i++ {
-			c := s[i]
-			if c < '0' || c > '9' {
-				return -1
-			}
-			n = n*10 + int64(c-'0')
-		}
-		return n
-	}
-	if l > 19 {
-		// guaranteed to overflow
-		return -1
-	}
-	c18 := s[18]
-	if c18 < '0' || c18 > '9' {
-		return -1
-	}
-	for i := 0; i < 18; i++ {
-		c := s[i]
-		if c < '0' || c > '9' {
-			return -1
-		}
-		n = n*10 + int64(c-'0')
-	}
-	if n >= math.MaxInt64/10+1 {
-		return -1
-	}
-	n *= 10
-	n1 := n + int64(c18-'0')
-	if n1 < n {
-		return -1
-	}
-	return n1
-}
-
-func strToIdx(s unistring.String) uint32 {
-	if s == "" {
-		return math.MaxUint32
-	}
-	l := len(s)
-	if s[0] == '0' {
-		if l == 1 {
-			return 0
-		}
-		return math.MaxUint32
-	}
-	var n uint32
-	if l < 10 {
-		// guaranteed not to overflow
-		for i := 0; i < len(s); i++ {
-			c := s[i]
-			if c < '0' || c > '9' {
-				return math.MaxUint32
-			}
-			n = n*10 + uint32(c-'0')
-		}
-		return n
-	}
-	if l > 10 {
-		// guaranteed to overflow
-		return math.MaxUint32
-	}
-	c9 := s[9]
-	if c9 < '0' || c9 > '9' {
-		return math.MaxUint32
-	}
-	for i := 0; i < 9; i++ {
-		c := s[i]
-		if c < '0' || c > '9' {
-			return math.MaxUint32
-		}
-		n = n*10 + uint32(c-'0')
-	}
-	if n >= math.MaxUint32/10+1 {
-		return math.MaxUint32
-	}
-	n *= 10
-	n1 := n + uint32(c9-'0')
-	if n1 < n {
-		return math.MaxUint32
-	}
-
-	return n1
-}
-
-func strToGoIdx(s unistring.String) int {
-	if bits.UintSize == 64 {
-		return int(strToIdx64(s))
-	}
-	i := strToIdx(s)
-	if i >= math.MaxInt32 {
-		return -1
-	}
-	return int(i)
-}

+ 5 - 5
array_sparse.go

@@ -133,7 +133,7 @@ func (a *sparseArrayObject) getLengthProp() Value {
 }
 
 func (a *sparseArrayObject) getOwnPropStr(name unistring.String) Value {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		return a._getIdx(idx)
 	}
 	if name == "length" {
@@ -214,7 +214,7 @@ func (a *sparseArrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
 }
 
 func (a *sparseArrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		return a._setOwnIdx(idx, val, throw)
 	} else {
 		if name == "length" {
@@ -295,7 +295,7 @@ func (a *sparseArrayObject) setValues(values []Value, objCount int) {
 }
 
 func (a *sparseArrayObject) hasOwnPropertyStr(name unistring.String) bool {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		i := a.findIdx(idx)
 		return i < len(a.items) && a.items[i].idx == idx
 	} else {
@@ -372,7 +372,7 @@ func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescript
 }
 
 func (a *sparseArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		return a._defineIdxProperty(idx, descr, throw)
 	}
 	if name == "length" {
@@ -406,7 +406,7 @@ func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
 }
 
 func (a *sparseArrayObject) deleteStr(name unistring.String, throw bool) bool {
-	if idx := strToIdx(name); idx != math.MaxUint32 {
+	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 		return a._deleteIdxProp(idx, throw)
 	}
 	return a.baseObject.deleteStr(name, throw)

+ 13 - 0
array_test.go

@@ -28,6 +28,19 @@ func TestArrayExportProps(t *testing.T) {
 	}
 }
 
+func TestArrayCanonicalIndex(t *testing.T) {
+	const SCRIPT = `
+	var a = [];
+	a["00"] = 1;
+	a["01"] = 2;
+	if (a[0] !== undefined) {
+		throw new Error("a[0]");
+	}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
 func BenchmarkArrayGetStr(b *testing.B) {
 	b.StopTimer()
 	r := New()

+ 12 - 8
builtin_proxy.go

@@ -38,7 +38,7 @@ func (h *nativeProxyHandler) preventExtensions(target *Object) (bool, bool) {
 
 func (h *nativeProxyHandler) getOwnPropertyDescriptorStr(target *Object, prop unistring.String) (Value, bool) {
 	if trap := h.handler.GetOwnPropertyDescriptorIdx; trap != nil {
-		if idx, ok := strPropToInt(prop); ok {
+		if idx, ok := strToInt(prop); ok {
 			desc := trap(target, idx)
 			return desc.toValue(target.runtime), true
 		}
@@ -72,7 +72,7 @@ func (h *nativeProxyHandler) getOwnPropertyDescriptorSym(target *Object, prop *S
 
 func (h *nativeProxyHandler) definePropertyStr(target *Object, prop unistring.String, desc PropertyDescriptor) (bool, bool) {
 	if trap := h.handler.DefinePropertyIdx; trap != nil {
-		if idx, ok := strPropToInt(prop); ok {
+		if idx, ok := strToInt(prop); ok {
 			return trap(target, idx, desc), true
 		}
 	}
@@ -101,7 +101,7 @@ func (h *nativeProxyHandler) definePropertySym(target *Object, prop *Symbol, des
 
 func (h *nativeProxyHandler) hasStr(target *Object, prop unistring.String) (bool, bool) {
 	if trap := h.handler.HasIdx; trap != nil {
-		if idx, ok := strPropToInt(prop); ok {
+		if idx, ok := strToInt(prop); ok {
 			return trap(target, idx), true
 		}
 	}
@@ -130,7 +130,7 @@ func (h *nativeProxyHandler) hasSym(target *Object, prop *Symbol) (bool, bool) {
 
 func (h *nativeProxyHandler) getStr(target *Object, prop unistring.String, receiver Value) (Value, bool) {
 	if trap := h.handler.GetIdx; trap != nil {
-		if idx, ok := strPropToInt(prop); ok {
+		if idx, ok := strToInt(prop); ok {
 			return trap(target, idx, receiver), true
 		}
 	}
@@ -159,7 +159,7 @@ func (h *nativeProxyHandler) getSym(target *Object, prop *Symbol, receiver Value
 
 func (h *nativeProxyHandler) setStr(target *Object, prop unistring.String, value Value, receiver Value) (bool, bool) {
 	if trap := h.handler.SetIdx; trap != nil {
-		if idx, ok := strPropToInt(prop); ok {
+		if idx, ok := strToInt(prop); ok {
 			return trap(target, idx, value, receiver), true
 		}
 	}
@@ -188,7 +188,7 @@ func (h *nativeProxyHandler) setSym(target *Object, prop *Symbol, value Value, r
 
 func (h *nativeProxyHandler) deleteStr(target *Object, prop unistring.String) (bool, bool) {
 	if trap := h.handler.DeletePropertyIdx; trap != nil {
-		if idx, ok := strPropToInt(prop); ok {
+		if idx, ok := strToInt(prop); ok {
 			return trap(target, idx), true
 		}
 	}
@@ -246,8 +246,12 @@ func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) proxyHan
 
 // ProxyTrapConfig provides a simplified Go-friendly API for implementing Proxy traps.
 // If an *Idx trap is defined it gets called for integer property keys, including negative ones. Note that
-// this also includes string property keys that can be parsed into an integer. This allows more efficient
-// array operations.
+// this only includes string property keys that represent a canonical integer
+// (i.e. "0", "123", but not "00", "01", " 1" or "-0").
+// For efficiency strings representing integers exceeding 2^53 are not checked to see if they are canonical,
+// i.e. the *Idx traps will receive "9007199254740993" as well as "9007199254740994", even though the former is not
+// a canonical representation in ECMAScript (Number("9007199254740993") === 9007199254740992).
+// See https://262.ecma-international.org/#sec-canonicalnumericindexstring
 // If an *Idx trap is not set, the corresponding string one is used.
 type ProxyTrapConfig struct {
 	// A trap for Object.getPrototypeOf, Reflect.getPrototypeOf, __proto__, Object.prototype.isPrototypeOf, instanceof

+ 19 - 1
builtin_proxy_test.go

@@ -326,7 +326,7 @@ func TestProxy_native_proxy_getOwnPropertyDescriptorIdx(t *testing.T) {
 	a := vm.NewArray()
 	proxy1 := vm.NewProxy(a, &ProxyTrapConfig{
 		GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor {
-			panic(vm.NewTypeError("GetOwnPropertyDescriptor was called"))
+			panic(vm.NewTypeError("GetOwnPropertyDescriptor was called for %q", prop))
 		},
 		GetOwnPropertyDescriptorIdx: func(target *Object, prop int) PropertyDescriptor {
 			if prop >= -1 && prop <= 1 {
@@ -352,8 +352,21 @@ func TestProxy_native_proxy_getOwnPropertyDescriptorIdx(t *testing.T) {
 		},
 	})
 
+	proxy3 := vm.NewProxy(a, &ProxyTrapConfig{
+		GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor {
+			return PropertyDescriptor{
+				Value:        vm.ToValue(prop),
+				Configurable: FLAG_TRUE,
+			}
+		},
+		GetOwnPropertyDescriptorIdx: func(target *Object, prop int) PropertyDescriptor {
+			panic(vm.NewTypeError("GetOwnPropertyDescriptorIdx was called for %d", prop))
+		},
+	})
+
 	vm.Set("proxy1", proxy1)
 	vm.Set("proxy2", proxy2)
+	vm.Set("proxy3", proxy3)
 	_, err := vm.RunString(TESTLIBX + `
 	var desc;
 	for (var i = -1; i <= 1; i++) {
@@ -369,6 +382,11 @@ func TestProxy_native_proxy_getOwnPropertyDescriptorIdx(t *testing.T) {
 		desc = Object.getOwnPropertyDescriptor(proxy2, ""+i);
 		assert(deepEqual(desc, {value: ""+i, writable: false, enumerable: false, configurable: true}), "2. str "+i);
 	}
+
+	for (const prop of ["00", " 0", "-0", "01"]) {
+		desc = Object.getOwnPropertyDescriptor(proxy3, prop);
+		assert(deepEqual(desc, {value: prop, writable: false, enumerable: false, configurable: true}), "3. "+prop);
+	}
 	`)
 	if err != nil {
 		t.Fatal(err)

+ 14 - 2
object.go

@@ -65,6 +65,18 @@ func (p *PropertyDescriptor) Empty() bool {
 	return *p == empty
 }
 
+func (p *PropertyDescriptor) IsAccessor() bool {
+	return p.Setter != nil || p.Getter != nil
+}
+
+func (p *PropertyDescriptor) IsData() bool {
+	return p.Value != nil || p.Writable != FLAG_NOT_SET
+}
+
+func (p *PropertyDescriptor) IsGeneric() bool {
+	return !p.IsAccessor() && !p.IsData()
+}
+
 func (p *PropertyDescriptor) toValue(r *Runtime) Value {
 	if p.jsDescriptor != nil {
 		return p.jsDescriptor
@@ -1121,9 +1133,9 @@ func (o *baseObject) fixPropOrder() {
 	names := o.propNames
 	for i := o.lastSortedPropLen; i < len(names); i++ {
 		name := names[i]
-		if idx := strToIdx(name); idx != math.MaxUint32 {
+		if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 			k := sort.Search(o.idxPropCount, func(j int) bool {
-				return strToIdx(names[j]) >= idx
+				return strToArrayIdx(names[j]) >= idx
 			})
 			if k < i {
 				if namesMarkedForCopy(names) {

+ 6 - 6
object_dynamic.go

@@ -531,7 +531,7 @@ func (a *dynamicArray) getStr(p unistring.String, receiver Value) Value {
 	if p == "length" {
 		return intToValue(int64(a.a.Len()))
 	}
-	if idx, ok := strPropToInt(p); ok {
+	if idx, ok := strToInt(p); ok {
 		return a.a.Get(idx)
 	}
 	return a.getParentStr(p, receiver)
@@ -551,7 +551,7 @@ func (a *dynamicArray) getOwnPropStr(u unistring.String) Value {
 			writable: true,
 		}
 	}
-	if idx, ok := strPropToInt(u); ok {
+	if idx, ok := strToInt(u); ok {
 		return a.a.Get(idx)
 	}
 	return nil
@@ -573,7 +573,7 @@ func (a *dynamicArray) setOwnStr(p unistring.String, v Value, throw bool) bool {
 	if p == "length" {
 		return a._setLen(v, throw)
 	}
-	if idx, ok := strPropToInt(p); ok {
+	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())
@@ -628,7 +628,7 @@ func (a *dynamicArray) hasOwnPropertyStr(u unistring.String) bool {
 	if u == "length" {
 		return true
 	}
-	if idx, ok := strPropToInt(u); ok {
+	if idx, ok := strToInt(u); ok {
 		return a._has(idx)
 	}
 	return false
@@ -640,7 +640,7 @@ func (a *dynamicArray) hasOwnPropertyIdx(v valueInt) bool {
 
 func (a *dynamicArray) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
 	if a.checkDynamicObjectPropertyDescr(name, desc, throw) {
-		if idx, ok := strPropToInt(name); ok {
+		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())
@@ -663,7 +663,7 @@ func (a *dynamicArray) _delete(idx int, throw bool) bool {
 }
 
 func (a *dynamicArray) deleteStr(name unistring.String, throw bool) bool {
-	if idx, ok := strPropToInt(name); ok {
+	if idx, ok := strToInt(name); ok {
 		return a._delete(idx, throw)
 	}
 	if a.hasOwnPropertyStr(name) {

+ 4 - 16
proxy.go

@@ -891,15 +891,15 @@ func (p *proxyObject) __isCompatibleDescriptor(extensible bool, desc *PropertyDe
 			return false
 		}
 
-		if p.__isGenericDescriptor(desc) {
+		if desc.IsGeneric() {
 			return true
 		}
 
-		if p.__isDataDescriptor(desc) != !current.accessor {
+		if desc.IsData() != !current.accessor {
 			return desc.Configurable != FLAG_FALSE
 		}
 
-		if p.__isDataDescriptor(desc) && !current.accessor {
+		if desc.IsData() && !current.accessor {
 			if !current.configurable {
 				if desc.Writable == FLAG_TRUE && !current.writable {
 					return false
@@ -912,7 +912,7 @@ func (p *proxyObject) __isCompatibleDescriptor(extensible bool, desc *PropertyDe
 			}
 			return true
 		}
-		if p.__isAccessorDescriptor(desc) && current.accessor {
+		if desc.IsAccessor() && current.accessor {
 			if !current.configurable {
 				if desc.Setter != nil && desc.Setter.SameAs(current.setterFunc) {
 					return false
@@ -926,18 +926,6 @@ func (p *proxyObject) __isCompatibleDescriptor(extensible bool, desc *PropertyDe
 	return true
 }
 
-func (p *proxyObject) __isAccessorDescriptor(desc *PropertyDescriptor) bool {
-	return desc.Setter != nil || desc.Getter != nil
-}
-
-func (p *proxyObject) __isDataDescriptor(desc *PropertyDescriptor) bool {
-	return desc.Value != nil || desc.Writable != FLAG_NOT_SET
-}
-
-func (p *proxyObject) __isGenericDescriptor(desc *PropertyDescriptor) bool {
-	return !p.__isAccessorDescriptor(desc) && !p.__isDataDescriptor(desc)
-}
-
 func (p *proxyObject) __sameValue(val1, val2 Value) bool {
 	if val1 == nil && val2 == nil {
 		return true

+ 221 - 4
runtime.go

@@ -1069,6 +1069,18 @@ func toIntStrict(i int64) int {
 	return int(i)
 }
 
+func toIntClamp(i int64) int {
+	if bits.UintSize == 32 {
+		if i > math.MaxInt32 {
+			return math.MaxInt32
+		}
+		if i < math.MinInt32 {
+			return math.MinInt32
+		}
+	}
+	return int(i)
+}
+
 func (r *Runtime) toIndex(v Value) int {
 	intIdx := v.ToInteger()
 	if intIdx >= 0 && intIdx < maxInt {
@@ -2453,9 +2465,214 @@ func (r *Runtime) setGlobal(name unistring.String, v Value, strict bool) {
 	}
 }
 
-func strPropToInt(s unistring.String) (int, bool) {
-	if res, err := strconv.Atoi(string(s)); err == nil {
-		return res, true
+func strToArrayIdx(s unistring.String) uint32 {
+	if s == "" {
+		return math.MaxUint32
+	}
+	l := len(s)
+	if s[0] == '0' {
+		if l == 1 {
+			return 0
+		}
+		return math.MaxUint32
+	}
+	var n uint32
+	if l < 10 {
+		// guaranteed not to overflow
+		for i := 0; i < len(s); i++ {
+			c := s[i]
+			if c < '0' || c > '9' {
+				return math.MaxUint32
+			}
+			n = n*10 + uint32(c-'0')
+		}
+		return n
+	}
+	if l > 10 {
+		// guaranteed to overflow
+		return math.MaxUint32
+	}
+	c9 := s[9]
+	if c9 < '0' || c9 > '9' {
+		return math.MaxUint32
+	}
+	for i := 0; i < 9; i++ {
+		c := s[i]
+		if c < '0' || c > '9' {
+			return math.MaxUint32
+		}
+		n = n*10 + uint32(c-'0')
+	}
+	if n >= math.MaxUint32/10+1 {
+		return math.MaxUint32
+	}
+	n *= 10
+	n1 := n + uint32(c9-'0')
+	if n1 < n {
+		return math.MaxUint32
+	}
+
+	return n1
+}
+
+func strToInt32(s unistring.String) (int32, bool) {
+	if s == "" {
+		return -1, false
+	}
+	neg := s[0] == '-'
+	if neg {
+		s = s[1:]
+	}
+	l := len(s)
+	if s[0] == '0' {
+		if l == 1 {
+			return 0, !neg
+		}
+		return -1, false
+	}
+	var n uint32
+	if l < 10 {
+		// guaranteed not to overflow
+		for i := 0; i < len(s); i++ {
+			c := s[i]
+			if c < '0' || c > '9' {
+				return -1, false
+			}
+			n = n*10 + uint32(c-'0')
+		}
+	} else if l > 10 {
+		// guaranteed to overflow
+		return -1, false
+	} else {
+		c9 := s[9]
+		if c9 >= '0' {
+			if !neg && c9 > '7' || c9 > '8' {
+				// guaranteed to overflow
+				return -1, false
+			}
+			for i := 0; i < 9; i++ {
+				c := s[i]
+				if c < '0' || c > '9' {
+					return -1, false
+				}
+				n = n*10 + uint32(c-'0')
+			}
+			if n >= math.MaxInt32/10+1 {
+				// valid number, but it overflows integer
+				return 0, false
+			}
+			n = n*10 + uint32(c9-'0')
+		} else {
+			return -1, false
+		}
+	}
+	if neg {
+		return int32(-n), true
+	}
+	return int32(n), true
+}
+
+func strToInt64(s unistring.String) (int64, bool) {
+	if s == "" {
+		return -1, false
+	}
+	neg := s[0] == '-'
+	if neg {
+		s = s[1:]
+	}
+	l := len(s)
+	if s[0] == '0' {
+		if l == 1 {
+			return 0, !neg
+		}
+		return -1, false
+	}
+	var n uint64
+	if l < 19 {
+		// guaranteed not to overflow
+		for i := 0; i < len(s); i++ {
+			c := s[i]
+			if c < '0' || c > '9' {
+				return -1, false
+			}
+			n = n*10 + uint64(c-'0')
+		}
+	} else if l > 19 {
+		// guaranteed to overflow
+		return -1, false
+	} else {
+		c18 := s[18]
+		if c18 >= '0' {
+			if !neg && c18 > '7' || c18 > '8' {
+				// guaranteed to overflow
+				return -1, false
+			}
+			for i := 0; i < 18; i++ {
+				c := s[i]
+				if c < '0' || c > '9' {
+					return -1, false
+				}
+				n = n*10 + uint64(c-'0')
+			}
+			if n >= math.MaxInt64/10+1 {
+				// valid number, but it overflows integer
+				return 0, false
+			}
+			n = n*10 + uint64(c18-'0')
+		} else {
+			return -1, false
+		}
+	}
+	if neg {
+		return int64(-n), true
+	}
+	return int64(n), true
+}
+
+func strToInt(s unistring.String) (int, bool) {
+	if bits.UintSize == 32 {
+		n, ok := strToInt32(s)
+		return int(n), ok
+	}
+	n, ok := strToInt64(s)
+	return int(n), ok
+}
+
+// Attempts to convert a string into a canonical integer.
+// On success returns (number, true).
+// If it was a canonical number, but not an integer returns (0, false). This includes -0 and overflows.
+// In all other cases returns (-1, false).
+// See https://262.ecma-international.org/#sec-canonicalnumericindexstring
+func strToIntNum(s unistring.String) (int, bool) {
+	n, ok := strToInt64(s)
+	if n == 0 {
+		return 0, ok
+	}
+	if ok && n >= -maxInt && n <= maxInt {
+		if bits.UintSize == 32 {
+			if n > math.MaxInt32 || n < math.MinInt32 {
+				return 0, false
+			}
+		}
+		return int(n), true
+	}
+	str := stringValueFromRaw(s)
+	if str.ToNumber().toString().SameAs(str) {
+		return 0, false
+	}
+	return -1, false
+}
+
+func strToGoIdx(s unistring.String) int {
+	if n, ok := strToInt(s); ok {
+		return n
+	}
+	return -1
+}
+
+func strToIdx64(s unistring.String) int64 {
+	if n, ok := strToInt64(s); ok {
+		return n
 	}
-	return 0, false
+	return -1
 }

+ 78 - 0
runtime_test.go

@@ -2104,6 +2104,84 @@ func TestStacktraceLocationThrowFromGo(t *testing.T) {
 	}
 }
 
+func TestStrToInt64(t *testing.T) {
+	if _, ok := strToInt64(""); ok {
+		t.Fatal("<empty>")
+	}
+	if n, ok := strToInt64("0"); !ok || n != 0 {
+		t.Fatal("0", n, ok)
+	}
+	if n, ok := strToInt64("-0"); ok {
+		t.Fatal("-0", n, ok)
+	}
+	if n, ok := strToInt64("-1"); !ok || n != -1 {
+		t.Fatal("-1", n, ok)
+	}
+	if n, ok := strToInt64("9223372036854775808"); ok {
+		t.Fatal("max+1", n, ok)
+	}
+	if n, ok := strToInt64("9223372036854775817"); ok {
+		t.Fatal("9223372036854775817", n, ok)
+	}
+	if n, ok := strToInt64("-9223372036854775818"); ok {
+		t.Fatal("-9223372036854775818", n, ok)
+	}
+	if n, ok := strToInt64("9223372036854775807"); !ok || n != 9223372036854775807 {
+		t.Fatal("max", n, ok)
+	}
+	if n, ok := strToInt64("-9223372036854775809"); ok {
+		t.Fatal("min-1", n, ok)
+	}
+	if n, ok := strToInt64("-9223372036854775808"); !ok || n != -9223372036854775808 {
+		t.Fatal("min", n, ok)
+	}
+	if n, ok := strToInt64("-00"); ok {
+		t.Fatal("-00", n, ok)
+	}
+	if n, ok := strToInt64("-01"); ok {
+		t.Fatal("-01", n, ok)
+	}
+}
+
+func TestStrToInt32(t *testing.T) {
+	if _, ok := strToInt32(""); ok {
+		t.Fatal("<empty>")
+	}
+	if n, ok := strToInt32("0"); !ok || n != 0 {
+		t.Fatal("0", n, ok)
+	}
+	if n, ok := strToInt32("-0"); ok {
+		t.Fatal("-0", n, ok)
+	}
+	if n, ok := strToInt32("-1"); !ok || n != -1 {
+		t.Fatal("-1", n, ok)
+	}
+	if n, ok := strToInt32("2147483648"); ok {
+		t.Fatal("max+1", n, ok)
+	}
+	if n, ok := strToInt32("2147483657"); ok {
+		t.Fatal("2147483657", n, ok)
+	}
+	if n, ok := strToInt32("-2147483658"); ok {
+		t.Fatal("-2147483658", n, ok)
+	}
+	if n, ok := strToInt32("2147483647"); !ok || n != 2147483647 {
+		t.Fatal("max", n, ok)
+	}
+	if n, ok := strToInt32("-2147483649"); ok {
+		t.Fatal("min-1", n, ok)
+	}
+	if n, ok := strToInt32("-2147483648"); !ok || n != -2147483648 {
+		t.Fatal("min", n, ok)
+	}
+	if n, ok := strToInt32("-00"); ok {
+		t.Fatal("-00", n, ok)
+	}
+	if n, ok := strToInt32("-01"); ok {
+		t.Fatal("-01", n, ok)
+	}
+}
+
 /*
 func TestArrayConcatSparse(t *testing.T) {
 function foo(a,b,c)

+ 2 - 2
string_ascii.go

@@ -49,7 +49,7 @@ func (s asciiString) utf16Runes() []rune {
 }
 
 // ss must be trimmed
-func strToInt(ss string) (int64, error) {
+func stringToInt(ss string) (int64, error) {
 	if ss == "" {
 		return 0, nil
 	}
@@ -70,7 +70,7 @@ func strToInt(ss string) (int64, error) {
 }
 
 func (s asciiString) _toInt() (int64, error) {
-	return strToInt(strings.TrimSpace(string(s)))
+	return stringToInt(strings.TrimSpace(string(s)))
 }
 
 func isRangeErr(err error) bool {

+ 16 - 7
tc39_test.go

@@ -56,6 +56,13 @@ var (
 		"test/language/expressions/arrow-function/scope-param-elem-var-close.js":      true,
 		"test/language/expressions/arrow-function/scope-param-elem-var-open.js":       true,
 
+		// These tests are out of date (fixed in https://github.com/tc39/test262/commit/7d998a098e5420cb4b6ee4a05eb8c386d750c596)
+		"test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/key-is-numericindex.js":                   true,
+		"test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/key-is-numericindex-desc-configurable.js": true,
+
+		// Fixed in https://github.com/tc39/test262/commit/7d998a098e5420cb4b6ee4a05eb8c386d750c596
+		"test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/detached-buffer.js": true,
+
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
@@ -242,13 +249,14 @@ var (
 		"test/language/expressions/call/spread-obj-spread-order.js":                                     true,
 
 		// template strings
-		"test/built-ins/String/raw/zero-literal-segments.js":                                           true,
-		"test/built-ins/String/raw/template-substitutions-are-appended-on-same-index.js":               true,
-		"test/built-ins/String/raw/special-characters.js":                                              true,
-		"test/built-ins/String/raw/return-the-string-value-from-template.js":                           true,
-		"test/built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js": true,
-		"test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js":       true,
-		"test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js":         true,
+		"test/built-ins/String/raw/zero-literal-segments.js":                                                       true,
+		"test/built-ins/String/raw/template-substitutions-are-appended-on-same-index.js":                           true,
+		"test/built-ins/String/raw/special-characters.js":                                                          true,
+		"test/built-ins/String/raw/return-the-string-value-from-template.js":                                       true,
+		"test/built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js":             true,
+		"test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js":                   true,
+		"test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js":                     true,
+		"test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/conversion-operation-consistent-nan.js": true,
 
 		// restricted unicode regexp syntax
 		"test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js":         true,
@@ -409,6 +417,7 @@ var (
 		"sec-functiondeclarationinstantiation",
 		"sec-functiondeclarations-in-ifstatement-statement-clauses",
 		"sec-evaldeclarationinstantiation",
+		"sec-integer-indexed-exotic-objects-defineownproperty-p-desc",
 	}
 )
 

+ 58 - 23
typedarrays.go

@@ -448,15 +448,16 @@ func (a *float64Array) typeMatch(v Value) bool {
 }
 
 func (a *typedArrayObject) _getIdx(idx int) Value {
-	a.viewedArrayBuf.ensureNotDetached()
 	if 0 <= idx && idx < a.length {
+		a.viewedArrayBuf.ensureNotDetached()
 		return a.typedArray.get(idx + a.offset)
 	}
 	return nil
 }
 
 func (a *typedArrayObject) getOwnPropStr(name unistring.String) Value {
-	if idx, ok := strPropToInt(name); ok {
+	idx, ok := strToIntNum(name)
+	if ok {
 		v := a._getIdx(idx)
 		if v != nil {
 			return &valueProperty{
@@ -467,24 +468,32 @@ func (a *typedArrayObject) getOwnPropStr(name unistring.String) Value {
 		}
 		return nil
 	}
+	if idx == 0 {
+		return nil
+	}
 	return a.baseObject.getOwnPropStr(name)
 }
 
 func (a *typedArrayObject) getOwnPropIdx(idx valueInt) Value {
-	v := a._getIdx(toIntStrict(int64(idx)))
+	v := a._getIdx(toIntClamp(int64(idx)))
 	if v != nil {
 		return &valueProperty{
-			value:      v,
-			writable:   true,
-			enumerable: true,
+			value:        v,
+			writable:     true,
+			enumerable:   true,
+			configurable: true,
 		}
 	}
 	return nil
 }
 
 func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value {
-	if idx, ok := strPropToInt(name); ok {
-		prop := a._getIdx(idx)
+	idx, ok := strToIntNum(name)
+	if ok || idx == 0 {
+		var prop Value
+		if ok {
+			prop = a._getIdx(idx)
+		}
 		if prop == nil {
 			if a.prototype != nil {
 				if receiver == nil {
@@ -499,7 +508,7 @@ func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value {
 }
 
 func (a *typedArrayObject) getIdx(idx valueInt, receiver Value) Value {
-	prop := a._getIdx(toIntStrict(int64(idx)))
+	prop := a._getIdx(toIntClamp(int64(idx)))
 	if prop == nil {
 		if a.prototype != nil {
 			if receiver == nil {
@@ -513,8 +522,8 @@ func (a *typedArrayObject) getIdx(idx valueInt, receiver Value) Value {
 
 func (a *typedArrayObject) _putIdx(idx int, v Value, throw bool) bool {
 	v = v.ToNumber()
-	a.viewedArrayBuf.ensureNotDetached()
 	if idx >= 0 && idx < a.length {
+		a.viewedArrayBuf.ensureNotDetached()
 		a.typedArray.set(idx+a.offset, v)
 		return true
 	}
@@ -528,14 +537,18 @@ func (a *typedArrayObject) _hasIdx(idx int) bool {
 }
 
 func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
-	if idx, ok := strPropToInt(p); ok {
+	idx, ok := strToIntNum(p)
+	if ok {
 		return a._putIdx(idx, v, throw)
 	}
+	if idx == 0 {
+		return false
+	}
 	return a.baseObject.setOwnStr(p, v, throw)
 }
 
 func (a *typedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
-	return a._putIdx(toIntStrict(int64(p)), v, throw)
+	return a._putIdx(toIntClamp(int64(p)), v, throw)
 }
 
 func (a *typedArrayObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
@@ -547,50 +560,72 @@ func (a *typedArrayObject) setForeignIdx(p valueInt, v, receiver Value, throw bo
 }
 
 func (a *typedArrayObject) hasOwnPropertyStr(name unistring.String) bool {
-	if idx, ok := strPropToInt(name); ok {
+	idx, ok := strToIntNum(name)
+	if ok {
 		a.viewedArrayBuf.ensureNotDetached()
-		return idx < a.length
+		return idx >= 0 && idx < a.length
+	}
+	if idx == 0 {
+		return false
 	}
-
 	return a.baseObject.hasOwnPropertyStr(name)
 }
 
 func (a *typedArrayObject) hasOwnPropertyIdx(idx valueInt) bool {
-	return a._hasIdx(toIntStrict(int64(idx)))
+	return a._hasIdx(toIntClamp(int64(idx)))
 }
 
 func (a *typedArrayObject) _defineIdxProperty(idx int, desc PropertyDescriptor, throw bool) bool {
-	prop, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw)
+	if desc.Configurable == FLAG_FALSE || desc.Enumerable == FLAG_FALSE {
+		return false
+	}
+	if desc.IsAccessor() {
+		return false
+	}
+	if desc.Writable == FLAG_FALSE {
+		return false
+	}
+	_, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw)
 	if ok {
-		return a._putIdx(idx, prop, throw)
+		return a._putIdx(idx, desc.Value, throw)
 	}
 	return ok
 }
 
 func (a *typedArrayObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
-	if idx, ok := strPropToInt(name); ok {
+	idx, ok := strToIntNum(name)
+	if ok {
 		return a._defineIdxProperty(idx, desc, throw)
 	}
+	if idx == 0 {
+		return false
+	}
 	return a.baseObject.defineOwnPropertyStr(name, desc, throw)
 }
 
 func (a *typedArrayObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
-	return a._defineIdxProperty(toIntStrict(int64(name)), desc, throw)
+	return a._defineIdxProperty(toIntClamp(int64(name)), desc, throw)
 }
 
 func (a *typedArrayObject) deleteStr(name unistring.String, throw bool) bool {
-	if idx, ok := strPropToInt(name); ok {
-		if idx < a.length {
+	idx, ok := strToIntNum(name)
+	if ok {
+		if idx >= 0 && idx < a.length {
 			a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String())
+			return false
 		}
+		return true
+	}
+	if idx == 0 {
+		return true
 	}
-
 	return a.baseObject.deleteStr(name, throw)
 }
 
 func (a *typedArrayObject) deleteIdx(idx valueInt, throw bool) bool {
 	if idx >= 0 && int64(idx) < int64(a.length) {
 		a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String())
+		return false
 	}
 
 	return true

+ 77 - 0
typedarrays_test.go

@@ -69,3 +69,80 @@ func TestArrayBufferGoWrapper(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestTypedArrayIdx(t *testing.T) {
+	const SCRIPT = `
+	var a = new Uint8Array(1);
+
+	// 32-bit integer overflow, should not panic on 32-bit architectures
+	if (a[4294967297] !== undefined) {
+		throw new Error("4294967297");
+	}
+
+	// Canonical non-integer
+	a["Infinity"] = 8;
+	if (a["Infinity"] !== undefined) {
+		throw new Error("Infinity");
+	}
+	a["NaN"] = 1;
+	if (a["NaN"] !== undefined) {
+		throw new Error("NaN");
+	}
+
+	// Non-canonical integer
+	a["00"] = "00";
+	if (a["00"] !== "00") {
+		throw new Error("00");
+	}
+
+	// Non-canonical non-integer
+	a["1e-3"] = "1e-3";
+	if (a["1e-3"] !== "1e-3") {
+		throw new Error("1e-3");
+	}
+	if (a["0.001"] !== undefined) {
+		throw new Error("0.001");
+	}
+
+	// Negative zero
+	a["-0"] = 88;
+	if (a["-0"] !== undefined) {
+		throw new Error("-0");
+	}
+
+	if (a[0] !== 0) {
+		throw new Error("0");
+	}
+
+	a["9007199254740992"] = 1;
+	if (a["9007199254740992"] !== undefined) {
+		throw new Error("9007199254740992");
+	}
+	a["-9007199254740992"] = 1;
+	if (a["-9007199254740992"] !== undefined) {
+		throw new Error("-9007199254740992");
+	}
+
+	// Safe integer overflow, not canonical (Number("9007199254740993") === 9007199254740992)
+	a["9007199254740993"] = 1;
+	if (a["9007199254740993"] !== 1) {
+		throw new Error("9007199254740993");
+	}
+	a["-9007199254740993"] = 1;
+	if (a["-9007199254740993"] !== 1) {
+		throw new Error("-9007199254740993");
+	}
+
+	// Safe integer overflow, canonical Number("9007199254740994") == 9007199254740994
+	a["9007199254740994"] = 1;
+	if (a["9007199254740994"] !== undefined) {
+		throw new Error("9007199254740994");
+	}
+	a["-9007199254740994"] = 1;
+	if (a["-9007199254740994"] !== undefined) {
+		throw new Error("-9007199254740994");
+	}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}