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{
 		a: a,
 	}).next
@@ -345,13 +345,13 @@ func (a *arrayObject) expand(idx uint32) bool {
 				//log.Println("Switching standard->sparse")
 				sa := &sparseArrayObject{
 					baseObject:     a.baseObject,
-					length:         uint32(a.length),
+					length:         a.length,
 					propValueCount: a.propValueCount,
 				}
 				sa.setValues(a.values, a.objCount+1)
 				sa.val.self = sa
-				sa.init()
 				sa.lengthProp.writable = a.lengthProp.writable
+				sa._put("length", &sa.lengthProp)
 				return false
 			} else {
 				if bits.UintSize == 32 {

+ 3 - 10
array_sparse.go

@@ -23,13 +23,6 @@ type sparseArrayObject struct {
 	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 {
 	return sort.Search(len(a.items), func(i int) bool {
 		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{
 		a: a,
 	}).next
@@ -333,8 +326,8 @@ func (a *sparseArrayObject) expand(idx uint32) bool {
 			}
 			ar.setValuesFromSparse(a.items, int(idx))
 			ar.val.self = ar
-			ar.init()
 			ar.lengthProp.writable = a.lengthProp.writable
+			a._put("length", &ar.lengthProp)
 			return false
 		}
 	}

+ 8 - 5
builtin_json.go

@@ -150,12 +150,15 @@ func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holde
 				}
 			}
 		} 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 {
-					object.self.deleteStr(itemName.string(), false)
+					object.self.deleteStr(item.name, false)
 				} 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
 	if ctx.propertyList == nil {
-		props = append(props, object.self.ownKeys(false, nil)...)
+		props = object.self.ownKeys(false, nil)
 	} else {
 		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))
 }
 
+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 {
 	p := toPropertyKey(call.Argument(0))
 	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("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("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("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)

+ 129 - 61
object.go

@@ -243,8 +243,7 @@ type objectImpl interface {
 	hasInstance(v Value) bool
 	isExtensible() bool
 	preventExtensions(throw bool) bool
-	enumerate() iterNextFunc
-	enumerateUnfiltered() iterNextFunc
+	enumerateOwnKeys() iterNextFunc
 	export(ctx *objectExportCtx) interface{}
 	exportType() reflect.Type
 	equal(objectImpl) bool
@@ -438,8 +437,17 @@ func (o *baseObject) _delete(name unistring.String) {
 	delete(o.values, name)
 	for i, n := range o.propNames {
 		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 {
 				o.lastSortedPropLen--
 				if i < o.idxPropCount {
@@ -514,7 +522,8 @@ func (o *baseObject) setOwnStr(name unistring.String, val Value, throw bool) boo
 			return false
 		} else {
 			o.values[name] = val
-			o.propNames = append(o.propNames, name)
+			names := copyNamesIfNeeded(o.propNames, 1)
+			o.propNames = append(names, name)
 		}
 		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 {
 		o.values[name] = v
 		if existingVal == nil {
-			o.propNames = append(o.propNames, name)
+			names := copyNamesIfNeeded(o.propNames, 1)
+			o.propNames = append(names, name)
 		}
 		return true
 	}
@@ -805,7 +815,8 @@ func (o *baseObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, t
 
 func (o *baseObject) _put(name unistring.String, v Value) {
 	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
@@ -1011,39 +1022,66 @@ type objectPropIter struct {
 	idx       int
 }
 
-type propFilterIter struct {
+type recursivePropIter struct {
+	o    objectImpl
+	cur  iterNextFunc
+	seen map[unistring.String]struct{}
+}
+
+type enumerableIter struct {
 	wrapped iterNextFunc
-	all     bool
-	seen    map[unistring.String]bool
 }
 
-func (i *propFilterIter) next() (propIterItem, iterNextFunc) {
+func (i *enumerableIter) next() (propIterItem, iterNextFunc) {
 	for {
 		var item propIterItem
 		item, i.wrapped = i.wrapped()
 		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
 				}
-				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
 		}
 	}
 }
 
+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) {
 	for i.idx < len(i.propNames) {
 		name := i.propNames[i.idx]
@@ -1053,55 +1091,76 @@ func (i *objectPropIter) next() (propIterItem, iterNextFunc) {
 			return propIterItem{name: name, value: prop}, i.next
 		}
 	}
-
+	clearNamesCopyMarker(i.propNames)
 	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 {
@@ -1125,7 +1184,16 @@ func (o *baseObject) fixPropOrder() {
 				return strToIdx(names[j]) >= idx
 			})
 			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
 			}
 			o.idxPropCount++

+ 4 - 4
object_args.go

@@ -85,10 +85,10 @@ func (i *argumentsPropIter) next() (propIterItem, iterNextFunc) {
 	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 {

+ 3 - 3
object_gomap.go

@@ -138,7 +138,7 @@ func (i *gomapPropIter) next() (propIterItem, iterNextFunc) {
 	return propIterItem{}, nil
 }
 
-func (o *objectGoMapSimple) enumerateUnfiltered() iterNextFunc {
+func (o *objectGoMapSimple) enumerateOwnKeys() iterNextFunc {
 	propNames := make([]string, len(o.data))
 	i := 0
 	for key := range o.data {
@@ -146,10 +146,10 @@ func (o *objectGoMapSimple) enumerateUnfiltered() iterNextFunc {
 		i++
 	}
 
-	return o.recursiveIter((&gomapPropIter{
+	return (&gomapPropIter{
 		o:         o,
 		propNames: propNames,
-	}).next)
+	}).next
 }
 
 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
 }
 
-func (o *objectGoMapReflect) enumerateUnfiltered() iterNextFunc {
+func (o *objectGoMapReflect) enumerateOwnKeys() iterNextFunc {
 	return (&gomapReflectPropIter{
 		o:    o,
 		keys: o.value.MapKeys(),

+ 3 - 6
object_goreflect.go

@@ -376,18 +376,15 @@ func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
 	return propIterItem{}, nil
 }
 
-func (o *objectGoReflect) enumerateUnfiltered() iterNextFunc {
+func (o *objectGoReflect) enumerateOwnKeys() iterNextFunc {
 	r := &goreflectPropIter{
 		o: o,
 	}
-	var next iterNextFunc
 	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 {

+ 6 - 20
object_goslice.go

@@ -85,23 +85,9 @@ func (o *objectGoSlice) getOwnPropIdx(idx valueInt) Value {
 }
 
 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)
 		*o.data = n
 	} else {
@@ -303,11 +289,11 @@ func (i *goslicePropIter) next() (propIterItem, iterNextFunc) {
 	return propIterItem{}, nil
 }
 
-func (o *objectGoSlice) enumerateUnfiltered() iterNextFunc {
-	return o.recursiveIter((&goslicePropIter{
+func (o *objectGoSlice) enumerateOwnKeys() iterNextFunc {
+	return (&goslicePropIter{
 		o:     o,
 		limit: len(*o.data),
-	}).next)
+	}).next
 }
 
 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) {
-	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)
 		o.value.Set(n)
 	} else {
@@ -314,7 +300,7 @@ func (i *gosliceReflectPropIter) next() (propIterItem, iterNextFunc) {
 		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 {
@@ -325,7 +311,7 @@ func (o *objectGoSliceReflect) ownKeys(all bool, accum []Value) []Value {
 	return o.objectGoReflect.ownKeys(all, accum)
 }
 
-func (o *objectGoSliceReflect) enumerateUnfiltered() iterNextFunc {
+func (o *objectGoSliceReflect) enumerateOwnKeys() iterNextFunc {
 	return (&gosliceReflectPropIter{
 		o:     o,
 		limit: o.value.Len(),

+ 2 - 8
object_lazy.go

@@ -223,16 +223,10 @@ func (o *lazyObject) preventExtensions(throw bool) bool {
 	return obj.preventExtensions(throw)
 }
 
-func (o *lazyObject) enumerateUnfiltered() iterNextFunc {
+func (o *lazyObject) enumerateOwnKeys() iterNextFunc {
 	obj := o.create(o.val)
 	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{} {

+ 1 - 4
proxy.go

@@ -31,9 +31,6 @@ func (i *proxyPropIter) next() (propIterItem, iterNextFunc) {
 			return propIterItem{name: name.string(), value: prop}, i.next
 		}
 	}
-	if proto := i.p.proto(); proto != nil {
-		return proto.self.enumerateUnfiltered()()
-	}
 	return propIterItem{}, nil
 }
 
@@ -596,7 +593,7 @@ func (p *proxyObject) proxyOwnKeys() ([]Value, bool) {
 	return nil, false
 }
 
-func (p *proxyObject) enumerateUnfiltered() iterNextFunc {
+func (p *proxyObject) enumerateOwnKeys() iterNextFunc {
 	return (&proxyPropIter{
 		p:     p,
 		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()
 			elemTyp := typ.Elem()
 			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 err error
 				if needConvertKeys {
 					kv = reflect.New(keyTyp).Elem()
-					err = r.toReflectValue(itemName, kv, ctx)
+					err = r.toReflectValue(stringValueFromRaw(item.name), kv, ctx)
 					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 {
-					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 {
 					vv := reflect.New(elemTyp).Elem()
 					err := r.toReflectValue(ival, vv, ctx)
 					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)
 				} else {
 					m.SetMapIndex(kv, reflect.Zero(elemTyp))
 				}
-
 			}
+
 			return nil
 		}
 	case reflect.Struct:
@@ -2359,3 +2362,37 @@ func limitCallArgs(call FunctionCall, n int) FunctionCall {
 		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) {
 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 i.obj.baseObject.enumerateUnfiltered()()
+	return i.obj.baseObject.enumerateOwnKeys()()
 }
 
-func (s *stringObject) enumerateUnfiltered() iterNextFunc {
+func (s *stringObject) enumerateOwnKeys() iterNextFunc {
 	return (&stringPropIter{
 		str:    s.value,
 		obj:    s,

+ 4 - 1
tc39_test.go

@@ -316,6 +316,7 @@ var (
 		"sec-%typedarray%",
 		"sec-string",
 		"sec-date",
+		"sec-json",
 		"sec-number",
 		"sec-math",
 		"sec-arraybuffer-length",
@@ -323,7 +324,9 @@ var (
 		"sec-regexp",
 		"sec-string.prototype.trimLeft",
 		"sec-string.prototype.trimRight",
+		"sec-object.getownpropertydescriptor",
 		"sec-object.getownpropertydescriptors",
+		"sec-object.entries",
 	}
 )
 
@@ -692,7 +695,7 @@ func TestTC39(t *testing.T) {
 	// Tests ignored: 10,453, passed: 14,782
 
 	// at ddfe24afe3043388827aa220ef623b8540958bbd
-	// Tests ignored: 19,244, passed: 14,443
+	// Tests ignored: 19,212, passed: 14,483
 
 	ctx := &tc39TestCtx{
 		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 i.a.baseObject.enumerateUnfiltered()()
+	return i.a.baseObject.enumerateOwnKeys()()
 }
 
-func (a *typedArrayObject) enumerateUnfiltered() iterNextFunc {
+func (a *typedArrayObject) enumerateOwnKeys() iterNextFunc {
 	return (&typedArrayPropIter{
 		a: a,
 	}).next

+ 5 - 4
value.go

@@ -748,10 +748,11 @@ func (o *Object) GetSymbol(sym *Symbol) Value {
 }
 
 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

+ 1 - 1
vm.go

@@ -2462,7 +2462,7 @@ func (_enumerate) exec(vm *vm) {
 	if v == _undefined || v == _null {
 		vm.iterStack = append(vm.iterStack, iterStackItem{f: emptyIter})
 	} 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.pc++