Browse Source

Added Object.entries(). Avoid slice allocation and copy in many cases when iterating over object keys. Avoid duplication of the length property when switching between regular and sparse arrays. Closes #246.

Dmitry Panov 4 years ago
parent
commit
b19cd2fe95
20 changed files with 280 additions and 165 deletions
  1. 4 4
      array.go
  2. 3 10
      array_sparse.go
  3. 8 5
      builtin_json.go
  4. 17 0
      builtin_object.go
  5. 129 61
      object.go
  6. 4 4
      object_args.go
  7. 3 3
      object_gomap.go
  8. 1 4
      object_gomap_reflect.go
  9. 3 6
      object_goreflect.go
  10. 6 20
      object_goslice.go
  11. 5 19
      object_goslice_reflect.go
  12. 2 8
      object_lazy.go
  13. 1 4
      proxy.go
  14. 44 7
      runtime.go
  15. 36 0
      runtime_test.go
  16. 2 2
      string.go
  17. 4 1
      tc39_test.go
  18. 2 2
      typedarrays.go
  19. 5 4
      value.go
  20. 1 1
      vm.go

+ 4 - 4
array.go

@@ -296,10 +296,10 @@ func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
 		}
 		}
 	}
 	}
 
 
-	return i.a.baseObject.enumerateUnfiltered()()
+	return i.a.baseObject.enumerateOwnKeys()()
 }
 }
 
 
-func (a *arrayObject) enumerateUnfiltered() iterNextFunc {
+func (a *arrayObject) enumerateOwnKeys() iterNextFunc {
 	return (&arrayPropIter{
 	return (&arrayPropIter{
 		a: a,
 		a: a,
 	}).next
 	}).next
@@ -345,13 +345,13 @@ func (a *arrayObject) expand(idx uint32) bool {
 				//log.Println("Switching standard->sparse")
 				//log.Println("Switching standard->sparse")
 				sa := &sparseArrayObject{
 				sa := &sparseArrayObject{
 					baseObject:     a.baseObject,
 					baseObject:     a.baseObject,
-					length:         uint32(a.length),
+					length:         a.length,
 					propValueCount: a.propValueCount,
 					propValueCount: a.propValueCount,
 				}
 				}
 				sa.setValues(a.values, a.objCount+1)
 				sa.setValues(a.values, a.objCount+1)
 				sa.val.self = sa
 				sa.val.self = sa
-				sa.init()
 				sa.lengthProp.writable = a.lengthProp.writable
 				sa.lengthProp.writable = a.lengthProp.writable
+				sa._put("length", &sa.lengthProp)
 				return false
 				return false
 			} else {
 			} else {
 				if bits.UintSize == 32 {
 				if bits.UintSize == 32 {

+ 3 - 10
array_sparse.go

@@ -23,13 +23,6 @@ type sparseArrayObject struct {
 	lengthProp     valueProperty
 	lengthProp     valueProperty
 }
 }
 
 
-func (a *sparseArrayObject) init() {
-	a.baseObject.init()
-	a.lengthProp.writable = true
-
-	a._put("length", &a.lengthProp)
-}
-
 func (a *sparseArrayObject) findIdx(idx uint32) int {
 func (a *sparseArrayObject) findIdx(idx uint32) int {
 	return sort.Search(len(a.items), func(i int) bool {
 	return sort.Search(len(a.items), func(i int) bool {
 		return a.items[i].idx >= idx
 		return a.items[i].idx >= idx
@@ -263,10 +256,10 @@ func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
 		}
 		}
 	}
 	}
 
 
-	return i.a.baseObject.enumerateUnfiltered()()
+	return i.a.baseObject.enumerateOwnKeys()()
 }
 }
 
 
-func (a *sparseArrayObject) enumerateUnfiltered() iterNextFunc {
+func (a *sparseArrayObject) enumerateOwnKeys() iterNextFunc {
 	return (&sparseArrayPropIter{
 	return (&sparseArrayPropIter{
 		a: a,
 		a: a,
 	}).next
 	}).next
@@ -333,8 +326,8 @@ func (a *sparseArrayObject) expand(idx uint32) bool {
 			}
 			}
 			ar.setValuesFromSparse(a.items, int(idx))
 			ar.setValuesFromSparse(a.items, int(idx))
 			ar.val.self = ar
 			ar.val.self = ar
-			ar.init()
 			ar.lengthProp.writable = a.lengthProp.writable
 			ar.lengthProp.writable = a.lengthProp.writable
+			a._put("length", &ar.lengthProp)
 			return false
 			return false
 		}
 		}
 	}
 	}

+ 8 - 5
builtin_json.go

@@ -150,12 +150,15 @@ func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holde
 				}
 				}
 			}
 			}
 		} else {
 		} else {
-			for _, itemName := range object.self.ownKeys(false, nil) {
-				value := r.builtinJSON_reviveWalk(reviver, object, itemName)
+			iter := &enumerableIter{
+				wrapped: object.self.enumerateOwnKeys(),
+			}
+			for item, next := iter.next(); next != nil; item, next = next() {
+				value := r.builtinJSON_reviveWalk(reviver, object, stringValueFromRaw(item.name))
 				if value == _undefined {
 				if value == _undefined {
-					object.self.deleteStr(itemName.string(), false)
+					object.self.deleteStr(item.name, false)
 				} else {
 				} else {
-					object.self.setOwnStr(itemName.string(), value, false)
+					object.self.setOwnStr(item.name, value, false)
 				}
 				}
 			}
 			}
 		}
 		}
@@ -417,7 +420,7 @@ func (ctx *_builtinJSON_stringifyContext) jo(object *Object) {
 
 
 	var props []Value
 	var props []Value
 	if ctx.propertyList == nil {
 	if ctx.propertyList == nil {
-		props = append(props, object.self.ownKeys(false, nil)...)
+		props = object.self.ownKeys(false, nil)
 	} else {
 	} else {
 		props = ctx.propertyList
 		props = ctx.propertyList
 	}
 	}

+ 17 - 0
builtin_object.go

@@ -368,6 +368,22 @@ func (r *Runtime) object_keys(call FunctionCall) Value {
 	return r.newArrayValues(obj.self.ownKeys(false, nil))
 	return r.newArrayValues(obj.self.ownKeys(false, nil))
 }
 }
 
 
+func (r *Runtime) object_entries(call FunctionCall) Value {
+	obj := call.Argument(0).ToObject(r)
+
+	var values []Value
+	iter := &enumerableIter{
+		wrapped: obj.self.enumerateOwnKeys(),
+	}
+
+	for item, next := iter.next(); next != nil; item, next = next() {
+		v := obj.self.getStr(item.name, nil)
+		values = append(values, r.newArrayValues([]Value{stringValueFromRaw(item.name), v}))
+	}
+
+	return r.newArrayValues(values)
+}
+
 func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value {
 func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value {
 	p := toPropertyKey(call.Argument(0))
 	p := toPropertyKey(call.Argument(0))
 	o := call.This.ToObject(r)
 	o := call.This.ToObject(r)
@@ -531,6 +547,7 @@ func (r *Runtime) initObject() {
 	o._putProp("assign", r.newNativeFunc(r.object_assign, nil, "assign", nil, 2), true, false, true)
 	o._putProp("assign", r.newNativeFunc(r.object_assign, nil, "assign", nil, 2), true, false, true)
 	o._putProp("defineProperty", r.newNativeFunc(r.object_defineProperty, nil, "defineProperty", nil, 3), true, false, true)
 	o._putProp("defineProperty", r.newNativeFunc(r.object_defineProperty, nil, "defineProperty", nil, 3), true, false, true)
 	o._putProp("defineProperties", r.newNativeFunc(r.object_defineProperties, nil, "defineProperties", nil, 2), true, false, true)
 	o._putProp("defineProperties", r.newNativeFunc(r.object_defineProperties, nil, "defineProperties", nil, 2), true, false, true)
+	o._putProp("entries", r.newNativeFunc(r.object_entries, nil, "entries", nil, 1), true, false, true)
 	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.object_getOwnPropertyDescriptor, nil, "getOwnPropertyDescriptor", nil, 2), true, false, true)
 	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.object_getOwnPropertyDescriptor, nil, "getOwnPropertyDescriptor", nil, 2), true, false, true)
 	o._putProp("getOwnPropertyDescriptors", r.newNativeFunc(r.object_getOwnPropertyDescriptors, nil, "getOwnPropertyDescriptors", nil, 1), true, false, true)
 	o._putProp("getOwnPropertyDescriptors", r.newNativeFunc(r.object_getOwnPropertyDescriptors, nil, "getOwnPropertyDescriptors", nil, 1), true, false, true)
 	o._putProp("getPrototypeOf", r.newNativeFunc(r.object_getPrototypeOf, nil, "getPrototypeOf", nil, 1), true, false, true)
 	o._putProp("getPrototypeOf", r.newNativeFunc(r.object_getPrototypeOf, nil, "getPrototypeOf", nil, 1), true, false, true)

+ 129 - 61
object.go

@@ -243,8 +243,7 @@ type objectImpl interface {
 	hasInstance(v Value) bool
 	hasInstance(v Value) bool
 	isExtensible() bool
 	isExtensible() bool
 	preventExtensions(throw bool) bool
 	preventExtensions(throw bool) bool
-	enumerate() iterNextFunc
-	enumerateUnfiltered() iterNextFunc
+	enumerateOwnKeys() iterNextFunc
 	export(ctx *objectExportCtx) interface{}
 	export(ctx *objectExportCtx) interface{}
 	exportType() reflect.Type
 	exportType() reflect.Type
 	equal(objectImpl) bool
 	equal(objectImpl) bool
@@ -438,8 +437,17 @@ func (o *baseObject) _delete(name unistring.String) {
 	delete(o.values, name)
 	delete(o.values, name)
 	for i, n := range o.propNames {
 	for i, n := range o.propNames {
 		if n == name {
 		if n == name {
-			copy(o.propNames[i:], o.propNames[i+1:])
-			o.propNames = o.propNames[:len(o.propNames)-1]
+			names := o.propNames
+			if namesMarkedForCopy(names) {
+				newNames := make([]unistring.String, len(names)-1, shrinkCap(len(names), cap(names)))
+				copy(newNames, names[:i])
+				copy(newNames[i:], names[i+1:])
+				o.propNames = newNames
+			} else {
+				copy(names[i:], names[i+1:])
+				names[len(names)-1] = ""
+				o.propNames = names[:len(names)-1]
+			}
 			if i < o.lastSortedPropLen {
 			if i < o.lastSortedPropLen {
 				o.lastSortedPropLen--
 				o.lastSortedPropLen--
 				if i < o.idxPropCount {
 				if i < o.idxPropCount {
@@ -514,7 +522,8 @@ func (o *baseObject) setOwnStr(name unistring.String, val Value, throw bool) boo
 			return false
 			return false
 		} else {
 		} else {
 			o.values[name] = val
 			o.values[name] = val
-			o.propNames = append(o.propNames, name)
+			names := copyNamesIfNeeded(o.propNames, 1)
+			o.propNames = append(names, name)
 		}
 		}
 		return true
 		return true
 	}
 	}
@@ -777,7 +786,8 @@ func (o *baseObject) defineOwnPropertyStr(name unistring.String, descr PropertyD
 	if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
 	if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
 		o.values[name] = v
 		o.values[name] = v
 		if existingVal == nil {
 		if existingVal == nil {
-			o.propNames = append(o.propNames, name)
+			names := copyNamesIfNeeded(o.propNames, 1)
+			o.propNames = append(names, name)
 		}
 		}
 		return true
 		return true
 	}
 	}
@@ -805,7 +815,8 @@ func (o *baseObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, t
 
 
 func (o *baseObject) _put(name unistring.String, v Value) {
 func (o *baseObject) _put(name unistring.String, v Value) {
 	if _, exists := o.values[name]; !exists {
 	if _, exists := o.values[name]; !exists {
-		o.propNames = append(o.propNames, name)
+		names := copyNamesIfNeeded(o.propNames, 1)
+		o.propNames = append(names, name)
 	}
 	}
 
 
 	o.values[name] = v
 	o.values[name] = v
@@ -1011,39 +1022,66 @@ type objectPropIter struct {
 	idx       int
 	idx       int
 }
 }
 
 
-type propFilterIter struct {
+type recursivePropIter struct {
+	o    objectImpl
+	cur  iterNextFunc
+	seen map[unistring.String]struct{}
+}
+
+type enumerableIter struct {
 	wrapped iterNextFunc
 	wrapped iterNextFunc
-	all     bool
-	seen    map[unistring.String]bool
 }
 }
 
 
-func (i *propFilterIter) next() (propIterItem, iterNextFunc) {
+func (i *enumerableIter) next() (propIterItem, iterNextFunc) {
 	for {
 	for {
 		var item propIterItem
 		var item propIterItem
 		item, i.wrapped = i.wrapped()
 		item, i.wrapped = i.wrapped()
 		if i.wrapped == nil {
 		if i.wrapped == nil {
-			return propIterItem{}, nil
+			return item, nil
 		}
 		}
-
-		if !i.seen[item.name] {
-			i.seen[item.name] = true
-			if !i.all {
-				if item.enumerable == _ENUM_FALSE {
+		if item.enumerable == _ENUM_FALSE {
+			continue
+		}
+		if item.enumerable == _ENUM_UNKNOWN {
+			if prop, ok := item.value.(*valueProperty); ok {
+				if !prop.enumerable {
 					continue
 					continue
 				}
 				}
-				if item.enumerable == _ENUM_UNKNOWN {
-					if prop, ok := item.value.(*valueProperty); ok {
-						if !prop.enumerable {
-							continue
-						}
-					}
-				}
 			}
 			}
+		}
+		return item, i.next
+	}
+}
+
+func (i *recursivePropIter) next() (propIterItem, iterNextFunc) {
+	for {
+		var item propIterItem
+		item, i.cur = i.cur()
+		if i.cur == nil {
+			if proto := i.o.proto(); proto != nil {
+				i.cur = proto.self.enumerateOwnKeys()
+				i.o = proto.self
+				continue
+			}
+			return propIterItem{}, nil
+		}
+		if _, exists := i.seen[item.name]; !exists {
+			i.seen[item.name] = struct{}{}
 			return item, i.next
 			return item, i.next
 		}
 		}
 	}
 	}
 }
 }
 
 
+func enumerateRecursive(o *Object) iterNextFunc {
+	return (&enumerableIter{
+		wrapped: (&recursivePropIter{
+			o:    o.self,
+			cur:  o.self.enumerateOwnKeys(),
+			seen: make(map[unistring.String]struct{}),
+		}).next,
+	}).next
+}
+
 func (i *objectPropIter) next() (propIterItem, iterNextFunc) {
 func (i *objectPropIter) next() (propIterItem, iterNextFunc) {
 	for i.idx < len(i.propNames) {
 	for i.idx < len(i.propNames) {
 		name := i.propNames[i.idx]
 		name := i.propNames[i.idx]
@@ -1053,55 +1091,76 @@ func (i *objectPropIter) next() (propIterItem, iterNextFunc) {
 			return propIterItem{name: name, value: prop}, i.next
 			return propIterItem{name: name, value: prop}, i.next
 		}
 		}
 	}
 	}
-
+	clearNamesCopyMarker(i.propNames)
 	return propIterItem{}, nil
 	return propIterItem{}, nil
 }
 }
 
 
-func (o *baseObject) enumerate() iterNextFunc {
-	return (&propFilterIter{
-		wrapped: o.val.self.enumerateUnfiltered(),
-		seen:    make(map[unistring.String]bool),
-	}).next
-}
-
-func (o *baseObject) ownIter() iterNextFunc {
-	if len(o.propNames) > o.lastSortedPropLen {
-		o.fixPropOrder()
+var copyMarker = unistring.String(" ")
+
+// Set a copy-on-write flag so that any subsequent modifications of anything below the current length
+// trigger a copy.
+// The marker is a special value put at the index position of cap-1. Capacity is set so that the marker is
+// beyond the current length (therefore invisible to normal slice operations).
+// This function is called before an iteration begins to avoid copying of the names array if
+// there are no modifications within the iteration.
+// Note that the copying also occurs in two cases: nested iterations (on the same object) and
+// iterations after a previously abandoned iteration (because there is currently no mechanism to close an
+// iterator). It is still better than copying every time.
+func prepareNamesForCopy(names []unistring.String) []unistring.String {
+	if len(names) == 0 {
+		return names
+	}
+	if namesMarkedForCopy(names) || cap(names) == len(names) {
+		var newcap int
+		if cap(names) == len(names) {
+			newcap = growCap(len(names)+1, len(names), cap(names))
+		} else {
+			newcap = cap(names)
+		}
+		newNames := make([]unistring.String, len(names), newcap)
+		copy(newNames, names)
+		names = newNames
 	}
 	}
-	propNames := make([]unistring.String, len(o.propNames))
-	copy(propNames, o.propNames)
-	return (&objectPropIter{
-		o:         o,
-		propNames: propNames,
-	}).next
+	names[cap(names)-1 : cap(names)][0] = copyMarker
+	return names
 }
 }
 
 
-func (o *baseObject) recursiveIter(iter iterNextFunc) iterNextFunc {
-	return (&recursiveIter{
-		o:       o,
-		wrapped: iter,
-	}).next
+func namesMarkedForCopy(names []unistring.String) bool {
+	return cap(names) > len(names) && names[cap(names)-1 : cap(names)][0] == copyMarker
 }
 }
 
 
-func (o *baseObject) enumerateUnfiltered() iterNextFunc {
-	return o.recursiveIter(o.ownIter())
+func clearNamesCopyMarker(names []unistring.String) {
+	if cap(names) > len(names) {
+		names[cap(names)-1 : cap(names)][0] = ""
+	}
 }
 }
 
 
-type recursiveIter struct {
-	o       *baseObject
-	wrapped iterNextFunc
+func copyNamesIfNeeded(names []unistring.String, extraCap int) []unistring.String {
+	if namesMarkedForCopy(names) && len(names)+extraCap >= cap(names) {
+		var newcap int
+		newsize := len(names) + extraCap + 1
+		if newsize > cap(names) {
+			newcap = growCap(newsize, len(names), cap(names))
+		} else {
+			newcap = cap(names)
+		}
+		newNames := make([]unistring.String, len(names), newcap)
+		copy(newNames, names)
+		return newNames
+	}
+	return names
 }
 }
 
 
-func (iter *recursiveIter) next() (propIterItem, iterNextFunc) {
-	item, next := iter.wrapped()
-	if next != nil {
-		iter.wrapped = next
-		return item, iter.next
-	}
-	if proto := iter.o.prototype; proto != nil {
-		return proto.self.enumerateUnfiltered()()
+func (o *baseObject) enumerateOwnKeys() iterNextFunc {
+	if len(o.propNames) > o.lastSortedPropLen {
+		o.fixPropOrder()
 	}
 	}
-	return propIterItem{}, nil
+	propNames := prepareNamesForCopy(o.propNames)
+	o.propNames = propNames
+	return (&objectPropIter{
+		o:         o,
+		propNames: propNames,
+	}).next
 }
 }
 
 
 func (o *baseObject) equal(objectImpl) bool {
 func (o *baseObject) equal(objectImpl) bool {
@@ -1125,7 +1184,16 @@ func (o *baseObject) fixPropOrder() {
 				return strToIdx(names[j]) >= idx
 				return strToIdx(names[j]) >= idx
 			})
 			})
 			if k < i {
 			if k < i {
-				copy(names[k+1:i+1], names[k:i])
+				if namesMarkedForCopy(names) {
+					newNames := make([]unistring.String, len(names), cap(names))
+					copy(newNames[:k], names)
+					copy(newNames[k+1:i+1], names[k:i])
+					copy(newNames[i+1:], names[i+1:])
+					names = newNames
+					o.propNames = names
+				} else {
+					copy(names[k+1:i+1], names[k:i])
+				}
 				names[k] = name
 				names[k] = name
 			}
 			}
 			o.idxPropCount++
 			o.idxPropCount++

+ 4 - 4
object_args.go

@@ -85,10 +85,10 @@ func (i *argumentsPropIter) next() (propIterItem, iterNextFunc) {
 	return item, i.next
 	return item, i.next
 }
 }
 
 
-func (a *argumentsObject) enumerateUnfiltered() iterNextFunc {
-	return a.recursiveIter((&argumentsPropIter{
-		wrapped: a.ownIter(),
-	}).next)
+func (a *argumentsObject) enumerateOwnKeys() iterNextFunc {
+	return (&argumentsPropIter{
+		wrapped: a.baseObject.enumerateOwnKeys(),
+	}).next
 }
 }
 
 
 func (a *argumentsObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 func (a *argumentsObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {

+ 3 - 3
object_gomap.go

@@ -138,7 +138,7 @@ func (i *gomapPropIter) next() (propIterItem, iterNextFunc) {
 	return propIterItem{}, nil
 	return propIterItem{}, nil
 }
 }
 
 
-func (o *objectGoMapSimple) enumerateUnfiltered() iterNextFunc {
+func (o *objectGoMapSimple) enumerateOwnKeys() iterNextFunc {
 	propNames := make([]string, len(o.data))
 	propNames := make([]string, len(o.data))
 	i := 0
 	i := 0
 	for key := range o.data {
 	for key := range o.data {
@@ -146,10 +146,10 @@ func (o *objectGoMapSimple) enumerateUnfiltered() iterNextFunc {
 		i++
 		i++
 	}
 	}
 
 
-	return o.recursiveIter((&gomapPropIter{
+	return (&gomapPropIter{
 		o:         o,
 		o:         o,
 		propNames: propNames,
 		propNames: propNames,
-	}).next)
+	}).next
 }
 }
 
 
 func (o *objectGoMapSimple) ownKeys(_ bool, accum []Value) []Value {
 func (o *objectGoMapSimple) ownKeys(_ bool, accum []Value) []Value {

+ 1 - 4
object_gomap_reflect.go

@@ -246,13 +246,10 @@ func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) {
 		}
 		}
 	}
 	}
 
 
-	if i.o.prototype != nil {
-		return i.o.prototype.self.enumerateUnfiltered()()
-	}
 	return propIterItem{}, nil
 	return propIterItem{}, nil
 }
 }
 
 
-func (o *objectGoMapReflect) enumerateUnfiltered() iterNextFunc {
+func (o *objectGoMapReflect) enumerateOwnKeys() iterNextFunc {
 	return (&gomapReflectPropIter{
 	return (&gomapReflectPropIter{
 		o:    o,
 		o:    o,
 		keys: o.value.MapKeys(),
 		keys: o.value.MapKeys(),

+ 3 - 6
object_goreflect.go

@@ -376,18 +376,15 @@ func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
 	return propIterItem{}, nil
 	return propIterItem{}, nil
 }
 }
 
 
-func (o *objectGoReflect) enumerateUnfiltered() iterNextFunc {
+func (o *objectGoReflect) enumerateOwnKeys() iterNextFunc {
 	r := &goreflectPropIter{
 	r := &goreflectPropIter{
 		o: o,
 		o: o,
 	}
 	}
-	var next iterNextFunc
 	if o.value.Kind() == reflect.Struct {
 	if o.value.Kind() == reflect.Struct {
-		next = r.nextField
-	} else {
-		next = r.nextMethod
+		return r.nextField
 	}
 	}
 
 
-	return o.recursiveIter(next)
+	return r.nextMethod
 }
 }
 
 
 func (o *objectGoReflect) ownKeys(_ bool, accum []Value) []Value {
 func (o *objectGoReflect) ownKeys(_ bool, accum []Value) []Value {

+ 6 - 20
object_goslice.go

@@ -85,23 +85,9 @@ func (o *objectGoSlice) getOwnPropIdx(idx valueInt) Value {
 }
 }
 
 
 func (o *objectGoSlice) grow(size int) {
 func (o *objectGoSlice) grow(size int) {
-	newcap := cap(*o.data)
-	if newcap < size {
-		// Use the same algorithm as in runtime.growSlice
-		doublecap := newcap + newcap
-		if size > doublecap {
-			newcap = size
-		} else {
-			if len(*o.data) < 1024 {
-				newcap = doublecap
-			} else {
-				for newcap < size {
-					newcap += newcap / 4
-				}
-			}
-		}
-
-		n := make([]interface{}, size, newcap)
+	oldcap := cap(*o.data)
+	if oldcap < size {
+		n := make([]interface{}, size, growCap(size, len(*o.data), oldcap))
 		copy(n, *o.data)
 		copy(n, *o.data)
 		*o.data = n
 		*o.data = n
 	} else {
 	} else {
@@ -303,11 +289,11 @@ func (i *goslicePropIter) next() (propIterItem, iterNextFunc) {
 	return propIterItem{}, nil
 	return propIterItem{}, nil
 }
 }
 
 
-func (o *objectGoSlice) enumerateUnfiltered() iterNextFunc {
-	return o.recursiveIter((&goslicePropIter{
+func (o *objectGoSlice) enumerateOwnKeys() iterNextFunc {
+	return (&goslicePropIter{
 		o:     o,
 		o:     o,
 		limit: len(*o.data),
 		limit: len(*o.data),
-	}).next)
+	}).next
 }
 }
 
 
 func (o *objectGoSlice) ownKeys(_ bool, accum []Value) []Value {
 func (o *objectGoSlice) ownKeys(_ bool, accum []Value) []Value {

+ 5 - 19
object_goslice_reflect.go

@@ -113,23 +113,9 @@ func (o *objectGoSliceReflect) putIdx(idx int, v Value, throw bool) bool {
 }
 }
 
 
 func (o *objectGoSliceReflect) grow(size int) {
 func (o *objectGoSliceReflect) grow(size int) {
-	newcap := o.value.Cap()
-	if newcap < size {
-		// Use the same algorithm as in runtime.growSlice
-		doublecap := newcap + newcap
-		if size > doublecap {
-			newcap = size
-		} else {
-			if o.value.Len() < 1024 {
-				newcap = doublecap
-			} else {
-				for newcap < size {
-					newcap += newcap / 4
-				}
-			}
-		}
-
-		n := reflect.MakeSlice(o.value.Type(), size, newcap)
+	oldcap := o.value.Cap()
+	if oldcap < size {
+		n := reflect.MakeSlice(o.value.Type(), size, growCap(size, o.value.Len(), oldcap))
 		reflect.Copy(n, o.value)
 		reflect.Copy(n, o.value)
 		o.value.Set(n)
 		o.value.Set(n)
 	} else {
 	} else {
@@ -314,7 +300,7 @@ func (i *gosliceReflectPropIter) next() (propIterItem, iterNextFunc) {
 		return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next
 		return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next
 	}
 	}
 
 
-	return i.o.objectGoReflect.enumerateUnfiltered()()
+	return i.o.objectGoReflect.enumerateOwnKeys()()
 }
 }
 
 
 func (o *objectGoSliceReflect) ownKeys(all bool, accum []Value) []Value {
 func (o *objectGoSliceReflect) ownKeys(all bool, accum []Value) []Value {
@@ -325,7 +311,7 @@ func (o *objectGoSliceReflect) ownKeys(all bool, accum []Value) []Value {
 	return o.objectGoReflect.ownKeys(all, accum)
 	return o.objectGoReflect.ownKeys(all, accum)
 }
 }
 
 
-func (o *objectGoSliceReflect) enumerateUnfiltered() iterNextFunc {
+func (o *objectGoSliceReflect) enumerateOwnKeys() iterNextFunc {
 	return (&gosliceReflectPropIter{
 	return (&gosliceReflectPropIter{
 		o:     o,
 		o:     o,
 		limit: o.value.Len(),
 		limit: o.value.Len(),

+ 2 - 8
object_lazy.go

@@ -223,16 +223,10 @@ func (o *lazyObject) preventExtensions(throw bool) bool {
 	return obj.preventExtensions(throw)
 	return obj.preventExtensions(throw)
 }
 }
 
 
-func (o *lazyObject) enumerateUnfiltered() iterNextFunc {
+func (o *lazyObject) enumerateOwnKeys() iterNextFunc {
 	obj := o.create(o.val)
 	obj := o.create(o.val)
 	o.val.self = obj
 	o.val.self = obj
-	return obj.enumerateUnfiltered()
-}
-
-func (o *lazyObject) enumerate() iterNextFunc {
-	obj := o.create(o.val)
-	o.val.self = obj
-	return obj.enumerate()
+	return obj.enumerateOwnKeys()
 }
 }
 
 
 func (o *lazyObject) export(ctx *objectExportCtx) interface{} {
 func (o *lazyObject) export(ctx *objectExportCtx) interface{} {

+ 1 - 4
proxy.go

@@ -31,9 +31,6 @@ func (i *proxyPropIter) next() (propIterItem, iterNextFunc) {
 			return propIterItem{name: name.string(), value: prop}, i.next
 			return propIterItem{name: name.string(), value: prop}, i.next
 		}
 		}
 	}
 	}
-	if proto := i.p.proto(); proto != nil {
-		return proto.self.enumerateUnfiltered()()
-	}
 	return propIterItem{}, nil
 	return propIterItem{}, nil
 }
 }
 
 
@@ -596,7 +593,7 @@ func (p *proxyObject) proxyOwnKeys() ([]Value, bool) {
 	return nil, false
 	return nil, false
 }
 }
 
 
-func (p *proxyObject) enumerateUnfiltered() iterNextFunc {
+func (p *proxyObject) enumerateOwnKeys() iterNextFunc {
 	return (&proxyPropIter{
 	return (&proxyPropIter{
 		p:     p,
 		p:     p,
 		names: p.ownKeys(true, nil),
 		names: p.ownKeys(true, nil),

+ 44 - 7
runtime.go

@@ -1843,32 +1843,35 @@ func (r *Runtime) toReflectValue(v Value, dst reflect.Value, ctx *objectExportCt
 			keyTyp := typ.Key()
 			keyTyp := typ.Key()
 			elemTyp := typ.Elem()
 			elemTyp := typ.Elem()
 			needConvertKeys := !reflect.ValueOf("").Type().AssignableTo(keyTyp)
 			needConvertKeys := !reflect.ValueOf("").Type().AssignableTo(keyTyp)
-			for _, itemName := range o.self.ownKeys(false, nil) {
+			iter := &enumerableIter{
+				wrapped: o.self.enumerateOwnKeys(),
+			}
+			for item, next := iter.next(); next != nil; item, next = next() {
 				var kv reflect.Value
 				var kv reflect.Value
 				var err error
 				var err error
 				if needConvertKeys {
 				if needConvertKeys {
 					kv = reflect.New(keyTyp).Elem()
 					kv = reflect.New(keyTyp).Elem()
-					err = r.toReflectValue(itemName, kv, ctx)
+					err = r.toReflectValue(stringValueFromRaw(item.name), kv, ctx)
 					if err != nil {
 					if err != nil {
-						return fmt.Errorf("could not convert map key %s to %v", itemName.String(), typ)
+						return fmt.Errorf("could not convert map key %s to %v", item.name.String(), typ)
 					}
 					}
 				} else {
 				} else {
-					kv = reflect.ValueOf(itemName.String())
+					kv = reflect.ValueOf(item.name.String())
 				}
 				}
 
 
-				ival := o.get(itemName, nil)
+				ival := o.self.getStr(item.name, nil)
 				if ival != nil {
 				if ival != nil {
 					vv := reflect.New(elemTyp).Elem()
 					vv := reflect.New(elemTyp).Elem()
 					err := r.toReflectValue(ival, vv, ctx)
 					err := r.toReflectValue(ival, vv, ctx)
 					if err != nil {
 					if err != nil {
-						return fmt.Errorf("could not convert map value %v to %v at key %s", ival, typ, itemName.String())
+						return fmt.Errorf("could not convert map value %v to %v at key %s", ival, typ, item.name.String())
 					}
 					}
 					m.SetMapIndex(kv, vv)
 					m.SetMapIndex(kv, vv)
 				} else {
 				} else {
 					m.SetMapIndex(kv, reflect.Zero(elemTyp))
 					m.SetMapIndex(kv, reflect.Zero(elemTyp))
 				}
 				}
-
 			}
 			}
+
 			return nil
 			return nil
 		}
 		}
 	case reflect.Struct:
 	case reflect.Struct:
@@ -2359,3 +2362,37 @@ func limitCallArgs(call FunctionCall, n int) FunctionCall {
 		return call
 		return call
 	}
 	}
 }
 }
+
+func shrinkCap(newSize, oldCap int) int {
+	if oldCap > 8 {
+		if cap := oldCap / 2; cap >= newSize {
+			return cap
+		}
+	}
+	return oldCap
+}
+
+func growCap(newSize, oldSize, oldCap int) int {
+	// Use the same algorithm as in runtime.growSlice
+	doublecap := oldCap + oldCap
+	if newSize > doublecap {
+		return newSize
+	} else {
+		if oldSize < 1024 {
+			return doublecap
+		} else {
+			cap := oldCap
+			// Check 0 < cap to detect overflow
+			// and prevent an infinite loop.
+			for 0 < cap && cap < newSize {
+				cap += cap / 4
+			}
+			// Return the requested cap when
+			// the calculation overflowed.
+			if cap <= 0 {
+				return newSize
+			}
+			return cap
+		}
+	}
+}

+ 36 - 0
runtime_test.go

@@ -1865,6 +1865,42 @@ func TestNativeCallWithRuntimeParameter(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestNestedEnumerate(t *testing.T) {
+	const SCRIPT = `
+	var o = {baz: true, foo: true, bar: true};
+	var res = "";
+	for (var i in o) {
+		delete o.baz;
+		Object.defineProperty(o, "hidden", {value: true, configurable: true});
+		for (var j in o) {
+			Object.defineProperty(o, "0", {value: true, configurable: true});
+			Object.defineProperty(o, "1", {value: true, configurable: true});
+			for (var k in o) {}
+			res += i + "-" + j + " ";
+		}
+	}
+	assert(compareArray(Reflect.ownKeys(o), ["0","1","foo","bar","hidden"]), "keys");
+	res;
+	`
+	testScript1(TESTLIB+SCRIPT, asciiString("baz-foo baz-bar foo-foo foo-bar bar-foo bar-bar "), t)
+}
+
+func TestAbandonedEnumerate(t *testing.T) {
+	const SCRIPT = `
+	var o = {baz: true, foo: true, bar: true};
+	var res = "";
+	for (var i in o) {
+		delete o.baz;
+		for (var j in o) {
+			res += i + "-" + j + " ";
+			break;
+		}
+	}
+	res;
+	`
+	testScript1(SCRIPT, asciiString("baz-foo foo-foo bar-foo "), t)
+}
+
 /*
 /*
 func TestArrayConcatSparse(t *testing.T) {
 func TestArrayConcatSparse(t *testing.T) {
 function foo(a,b,c)
 function foo(a,b,c)

+ 2 - 2
string.go

@@ -290,10 +290,10 @@ func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
 		return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next
 		return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next
 	}
 	}
 
 
-	return i.obj.baseObject.enumerateUnfiltered()()
+	return i.obj.baseObject.enumerateOwnKeys()()
 }
 }
 
 
-func (s *stringObject) enumerateUnfiltered() iterNextFunc {
+func (s *stringObject) enumerateOwnKeys() iterNextFunc {
 	return (&stringPropIter{
 	return (&stringPropIter{
 		str:    s.value,
 		str:    s.value,
 		obj:    s,
 		obj:    s,

+ 4 - 1
tc39_test.go

@@ -316,6 +316,7 @@ var (
 		"sec-%typedarray%",
 		"sec-%typedarray%",
 		"sec-string",
 		"sec-string",
 		"sec-date",
 		"sec-date",
+		"sec-json",
 		"sec-number",
 		"sec-number",
 		"sec-math",
 		"sec-math",
 		"sec-arraybuffer-length",
 		"sec-arraybuffer-length",
@@ -323,7 +324,9 @@ var (
 		"sec-regexp",
 		"sec-regexp",
 		"sec-string.prototype.trimLeft",
 		"sec-string.prototype.trimLeft",
 		"sec-string.prototype.trimRight",
 		"sec-string.prototype.trimRight",
+		"sec-object.getownpropertydescriptor",
 		"sec-object.getownpropertydescriptors",
 		"sec-object.getownpropertydescriptors",
+		"sec-object.entries",
 	}
 	}
 )
 )
 
 
@@ -692,7 +695,7 @@ func TestTC39(t *testing.T) {
 	// Tests ignored: 10,453, passed: 14,782
 	// Tests ignored: 10,453, passed: 14,782
 
 
 	// at ddfe24afe3043388827aa220ef623b8540958bbd
 	// at ddfe24afe3043388827aa220ef623b8540958bbd
-	// Tests ignored: 19,244, passed: 14,443
+	// Tests ignored: 19,212, passed: 14,483
 
 
 	ctx := &tc39TestCtx{
 	ctx := &tc39TestCtx{
 		base: tc39BASE,
 		base: tc39BASE,

+ 2 - 2
typedarrays.go

@@ -628,10 +628,10 @@ func (i *typedArrayPropIter) next() (propIterItem, iterNextFunc) {
 		return propIterItem{name: unistring.String(name), value: prop}, i.next
 		return propIterItem{name: unistring.String(name), value: prop}, i.next
 	}
 	}
 
 
-	return i.a.baseObject.enumerateUnfiltered()()
+	return i.a.baseObject.enumerateOwnKeys()()
 }
 }
 
 
-func (a *typedArrayObject) enumerateUnfiltered() iterNextFunc {
+func (a *typedArrayObject) enumerateOwnKeys() iterNextFunc {
 	return (&typedArrayPropIter{
 	return (&typedArrayPropIter{
 		a: a,
 		a: a,
 	}).next
 	}).next

+ 5 - 4
value.go

@@ -748,10 +748,11 @@ func (o *Object) GetSymbol(sym *Symbol) Value {
 }
 }
 
 
 func (o *Object) Keys() (keys []string) {
 func (o *Object) Keys() (keys []string) {
-	names := o.self.ownKeys(false, nil)
-	keys = make([]string, 0, len(names))
-	for _, name := range names {
-		keys = append(keys, name.String())
+	iter := &enumerableIter{
+		wrapped: o.self.enumerateOwnKeys(),
+	}
+	for item, next := iter.next(); next != nil; item, next = next() {
+		keys = append(keys, item.name.String())
 	}
 	}
 
 
 	return
 	return

+ 1 - 1
vm.go

@@ -2462,7 +2462,7 @@ func (_enumerate) exec(vm *vm) {
 	if v == _undefined || v == _null {
 	if v == _undefined || v == _null {
 		vm.iterStack = append(vm.iterStack, iterStackItem{f: emptyIter})
 		vm.iterStack = append(vm.iterStack, iterStackItem{f: emptyIter})
 	} else {
 	} else {
-		vm.iterStack = append(vm.iterStack, iterStackItem{f: v.ToObject(vm.r).self.enumerate()})
+		vm.iterStack = append(vm.iterStack, iterStackItem{f: enumerateRecursive(v.ToObject(vm.r))})
 	}
 	}
 	vm.sp--
 	vm.sp--
 	vm.pc++
 	vm.pc++