Browse Source

Proxy (#135)

Proxy, Reflect, new.target, tons of refactoring.

Co-authored-by: noctarius <[email protected]>
Dmitry Panov 5 years ago
parent
commit
a13c43b62b
53 changed files with 5643 additions and 2474 deletions
  1. 306 209
      array.go
  2. 171 178
      array_sparse.go
  3. 77 6
      array_sparse_test.go
  4. 10 0
      ast/node.go
  5. 175 175
      builtin_array.go
  6. 15 13
      builtin_date.go
  7. 0 1
      builtin_error.go
  8. 42 35
      builtin_function.go
  9. 27 51
      builtin_json.go
  10. 15 15
      builtin_map.go
  11. 3 3
      builtin_math.go
  12. 5 11
      builtin_number.go
  13. 151 145
      builtin_object.go
  14. 281 0
      builtin_proxy.go
  15. 828 0
      builtin_proxy_test.go
  16. 132 0
      builtin_reflect.go
  17. 48 48
      builtin_regexp.go
  18. 9 9
      builtin_set.go
  19. 12 13
      builtin_string.go
  20. 2 2
      builtin_symbol.go
  21. 10 10
      builtin_weakmap.go
  22. 10 10
      builtin_weakset.go
  23. 3 3
      builtin_weakset_test.go
  24. 30 7
      compiler_expr.go
  25. 1 1
      compiler_test.go
  26. 2 2
      date.go
  27. 58 56
      func.go
  28. 501 232
      object.go
  29. 31 48
      object_args.go
  30. 50 106
      object_gomap.go
  31. 115 80
      object_gomap_reflect.go
  32. 86 1
      object_gomap_reflect_test.go
  33. 124 0
      object_gomap_test.go
  34. 68 110
      object_goreflect.go
  35. 107 10
      object_goreflect_test.go
  36. 182 123
      object_goslice.go
  37. 187 121
      object_goslice_reflect.go
  38. 146 2
      object_goslice_reflect_test.go
  39. 101 2
      object_goslice_test.go
  40. 116 42
      object_lazy.go
  41. 46 16
      object_test.go
  42. 17 0
      parser/expression.go
  43. 764 0
      proxy.go
  44. 5 5
      regexp.go
  45. 178 118
      runtime.go
  46. 69 0
      runtime_test.go
  47. 84 60
      string.go
  48. 5 17
      string_ascii.go
  49. 1 25
      string_unicode.go
  50. 62 32
      tc39_test.go
  51. 50 166
      value.go
  52. 114 110
      vm.go
  53. 11 45
      vm_test.go

+ 306 - 209
array.go

@@ -2,6 +2,7 @@ package goja
 
 import (
 	"math"
+	"math/bits"
 	"reflect"
 	"strconv"
 )
@@ -17,18 +18,18 @@ func (ai *arrayIterObject) next() Value {
 	if ai.obj == nil {
 		return ai.val.runtime.createIterResultObject(_undefined, true)
 	}
-	l := toLength(ai.obj.self.getStr("length"))
+	l := toLength(ai.obj.self.getStr("length", nil))
 	index := ai.nextIdx
 	if index >= l {
 		ai.obj = nil
 		return ai.val.runtime.createIterResultObject(_undefined, true)
 	}
 	ai.nextIdx++
-	idxVal := intToValue(index)
+	idxVal := valueInt(index)
 	if ai.kind == iterationKindKey {
 		return ai.val.runtime.createIterResultObject(idxVal, false)
 	}
-	elementValue := ai.obj.self.get(idxVal)
+	elementValue := ai.obj.self.getIdx(idxVal, nil)
 	var result Value
 	if ai.kind == iterationKindValue {
 		result = elementValue
@@ -58,8 +59,8 @@ func (r *Runtime) createArrayIterator(iterObj *Object, kind iterationKind) Value
 type arrayObject struct {
 	baseObject
 	values         []Value
-	length         int64
-	objCount       int64
+	length         uint32
+	objCount       int
 	propValueCount int
 	lengthProp     valueProperty
 }
@@ -73,20 +74,15 @@ func (a *arrayObject) init() {
 
 func (a *arrayObject) _setLengthInt(l int64, throw bool) bool {
 	if l >= 0 && l <= math.MaxUint32 {
+		l := uint32(l)
 		ret := true
 		if l <= a.length {
 			if a.propValueCount > 0 {
 				// Slow path
-				var s int64
-				if a.length < int64(len(a.values)) {
-					s = a.length - 1
-				} else {
-					s = int64(len(a.values)) - 1
-				}
-				for i := s; i >= l; i-- {
+				for i := len(a.values) - 1; i >= int(l); i-- {
 					if prop, ok := a.values[i].(*valueProperty); ok {
 						if !prop.configurable {
-							l = i + 1
+							l = uint32(i) + 1
 							ret = false
 							break
 						}
@@ -95,8 +91,8 @@ func (a *arrayObject) _setLengthInt(l int64, throw bool) bool {
 				}
 			}
 		}
-		if l <= int64(len(a.values)) {
-			if l >= 16 && l < int64(cap(a.values))>>2 {
+		if l <= uint32(len(a.values)) {
+			if l >= 16 && l < uint32(cap(a.values))>>2 {
 				ar := make([]Value, l)
 				copy(ar, a.values)
 				a.values = ar
@@ -118,7 +114,7 @@ func (a *arrayObject) _setLengthInt(l int64, throw bool) bool {
 }
 
 func (a *arrayObject) setLengthInt(l int64, throw bool) bool {
-	if l == a.length {
+	if l == int64(a.length) {
 		return true
 	}
 	if !a.lengthProp.writable {
@@ -130,7 +126,7 @@ func (a *arrayObject) setLengthInt(l int64, throw bool) bool {
 
 func (a *arrayObject) setLength(v Value, throw bool) bool {
 	l, ok := toIntIgnoreNegZero(v)
-	if ok && l == a.length {
+	if ok && l == int64(a.length) {
 		return true
 	}
 	if !a.lengthProp.writable {
@@ -143,18 +139,46 @@ func (a *arrayObject) setLength(v Value, throw bool) bool {
 	panic(a.val.runtime.newError(a.val.runtime.global.RangeError, "Invalid array length"))
 }
 
-func (a *arrayObject) getIdx(idx int64, origNameStr string, origName Value) (v Value) {
-	if idx >= 0 && idx < int64(len(a.values)) {
-		v = a.values[idx]
+func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value {
+	prop := a.getOwnPropIdx(idx)
+	if prop == nil {
+		if a.prototype != nil {
+			if receiver == nil {
+				return a.prototype.self.getIdx(idx, a.val)
+			}
+			return a.prototype.self.getIdx(idx, receiver)
+		}
 	}
-	if v == nil && a.prototype != nil {
-		if origName != nil {
-			v = a.prototype.self.getProp(origName)
-		} else {
-			v = a.prototype.self.getPropStr(origNameStr)
+	if prop, ok := prop.(*valueProperty); ok {
+		if receiver == nil {
+			return prop.get(a.val)
+		}
+		return prop.get(receiver)
+	}
+	return prop
+}
+
+func (a *arrayObject) getOwnPropStr(name string) Value {
+	if i := strToIdx(name); i != math.MaxUint32 {
+		if i < uint32(len(a.values)) {
+			return a.values[i]
+		}
+	}
+	if name == "length" {
+		return a.getLengthProp()
+	}
+	return a.baseObject.getOwnPropStr(name)
+}
+
+func (a *arrayObject) getOwnPropIdx(idx valueInt) Value {
+	if i := toIdx(idx); i != math.MaxUint32 {
+		if i < uint32(len(a.values)) {
+			return a.values[i]
 		}
+		return nil
 	}
-	return
+
+	return a.baseObject.getOwnPropStr(idx.String())
 }
 
 func (a *arrayObject) sortLen() int64 {
@@ -173,159 +197,91 @@ func (a *arrayObject) swap(i, j int64) {
 	a.values[i], a.values[j] = a.values[j], a.values[i]
 }
 
-func toIdx(v Value) (idx int64) {
-	idx = -1
-	if idxVal, ok1 := v.(valueInt); ok1 {
-		idx = int64(idxVal)
-	} else {
-		if _, ok := v.(*valueSymbol); ok {
-			return -1
-		}
-		if i, err := strconv.ParseInt(v.String(), 10, 64); err == nil {
-			idx = i
-		}
-	}
-	if idx >= 0 && idx < math.MaxUint32 {
-		return
-	}
-	return -1
-}
-
-func strToIdx(s string) (idx int64) {
-	idx = -1
-	if i, err := strconv.ParseInt(s, 10, 64); err == nil {
-		idx = i
-	}
-
-	if idx >= 0 && idx < math.MaxUint32 {
-		return
-	}
-	return -1
-}
-
-func (a *arrayObject) getProp(n Value) Value {
-	if idx := toIdx(n); idx >= 0 {
-		return a.getIdx(idx, "", n)
-	}
-	if _, ok := n.(*valueSymbol); !ok {
-		if n.String() == "length" {
-			return a.getLengthProp()
-		}
-	}
-	return a.baseObject.getProp(n)
+func (a *arrayObject) getStr(name string, receiver Value) Value {
+	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 }
 
 func (a *arrayObject) getLengthProp() Value {
-	a.lengthProp.value = intToValue(a.length)
+	a.lengthProp.value = intToValue(int64(a.length))
 	return &a.lengthProp
 }
 
-func (a *arrayObject) getPropStr(name string) Value {
-	if i := strToIdx(name); i >= 0 {
-		return a.getIdx(i, name, nil)
-	}
-	if name == "length" {
-		return a.getLengthProp()
-	}
-	return a.baseObject.getPropStr(name)
-}
-
-func (a *arrayObject) getOwnPropStr(name string) Value {
-	if i := strToIdx(name); i >= 0 {
-		if i >= 0 && i < int64(len(a.values)) {
-			return a.values[i]
-		}
-	}
-	if name == "length" {
-		return a.getLengthProp()
+func (a *arrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
+	if i := toIdx(idx); i != math.MaxUint32 {
+		return a._setOwnIdx(i, val, throw)
+	} else {
+		return a.baseObject.setOwnStr(idx.String(), val, throw)
 	}
-	return a.baseObject.getOwnPropStr(name)
 }
 
-func (a *arrayObject) putIdx(idx int64, val Value, throw bool, origNameStr string, origName Value) {
+func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
 	var prop Value
-	if idx < int64(len(a.values)) {
+	if idx < uint32(len(a.values)) {
 		prop = a.values[idx]
 	}
 
 	if prop == nil {
-		if a.prototype != nil {
-			var pprop Value
-			if origName != nil {
-				pprop = a.prototype.self.getProp(origName)
-			} else {
-				pprop = a.prototype.self.getPropStr(origNameStr)
-			}
-			if pprop, ok := pprop.(*valueProperty); ok {
-				if !pprop.isWritable() {
-					a.val.runtime.typeErrorResult(throw)
-					return
-				}
-				if pprop.accessor {
-					pprop.set(a.val, val)
-					return
-				}
+		if proto := a.prototype; proto != nil {
+			// we know it's foreign because prototype loops are not allowed
+			if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok {
+				return res
 			}
 		}
-
+		// new property
 		if !a.extensible {
-			a.val.runtime.typeErrorResult(throw)
-			return
-		}
-		if idx >= a.length {
-			if !a.setLengthInt(idx+1, throw) {
-				return
+			a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
+			return false
+		} else {
+			if idx >= a.length {
+				if !a.setLengthInt(int64(idx)+1, throw) {
+					return false
+				}
 			}
-		}
-		if idx >= int64(len(a.values)) {
-			if !a.expand(idx) {
-				a.val.self.(*sparseArrayObject).putIdx(idx, val, throw, origNameStr, origName)
-				return
+			if idx >= uint32(len(a.values)) {
+				if !a.expand(idx) {
+					a.val.self.(*sparseArrayObject).add(idx, val)
+					return true
+				}
 			}
+			a.objCount++
 		}
 	} else {
 		if prop, ok := prop.(*valueProperty); ok {
 			if !prop.isWritable() {
 				a.val.runtime.typeErrorResult(throw)
-				return
+				return false
 			}
 			prop.set(a.val, val)
-			return
+			return true
 		}
 	}
-
 	a.values[idx] = val
-	a.objCount++
+	return true
 }
 
-func (a *arrayObject) put(n Value, val Value, throw bool) {
-	if idx := toIdx(n); idx >= 0 {
-		a.putIdx(idx, val, throw, "", n)
+func (a *arrayObject) setOwnStr(name string, val Value, throw bool) bool {
+	if idx := strToIdx(name); idx != math.MaxUint32 {
+		return a._setOwnIdx(idx, val, throw)
 	} else {
-		if n.String() == "length" {
-			a.setLength(val, throw)
+		if name == "length" {
+			return a.setLength(val, throw)
 		} else {
-			a.baseObject.put(n, val, throw)
+			return a.baseObject.setOwnStr(name, val, throw)
 		}
 	}
 }
 
-func (a *arrayObject) putStr(name string, val Value, throw bool) {
-	if idx := strToIdx(name); idx >= 0 {
-		a.putIdx(idx, val, throw, name, nil)
-	} else {
-		if name == "length" {
-			a.setLength(val, throw)
-		} else {
-			a.baseObject.putStr(name, val, throw)
-		}
-	}
+func (a *arrayObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
+	return a._setForeignIdx(idx, a.getOwnPropIdx(idx), val, receiver, throw)
+}
+
+func (a *arrayObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 }
 
 type arrayPropIter struct {
-	a         *arrayObject
-	recursive bool
-	idx       int
+	a   *arrayObject
+	idx int
 }
 
 func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
@@ -338,74 +294,85 @@ func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
 		}
 	}
 
-	return i.a.baseObject._enumerate(i.recursive)()
+	return i.a.baseObject.enumerateUnfiltered()()
 }
 
-func (a *arrayObject) _enumerate(recursive bool) iterNextFunc {
+func (a *arrayObject) enumerateUnfiltered() iterNextFunc {
 	return (&arrayPropIter{
-		a:         a,
-		recursive: recursive,
+		a: a,
 	}).next
 }
 
-func (a *arrayObject) enumerate(all, recursive bool) iterNextFunc {
-	return (&propFilterIter{
-		wrapped: a._enumerate(recursive),
-		all:     all,
-		seen:    make(map[string]bool),
-	}).next
-}
-
-func (a *arrayObject) hasOwnProperty(n Value) bool {
-	if idx := toIdx(n); idx >= 0 {
-		return idx < int64(len(a.values)) && a.values[idx] != nil
-	} else {
-		return a.baseObject.hasOwnProperty(n)
+func (a *arrayObject) ownKeys(all bool, accum []Value) []Value {
+	for i, prop := range a.values {
+		name := strconv.Itoa(i)
+		if prop != nil {
+			if !all {
+				if prop, ok := prop.(*valueProperty); ok && !prop.enumerable {
+					continue
+				}
+			}
+			accum = append(accum, asciiString(name))
+		}
 	}
+	return a.baseObject.ownKeys(all, accum)
 }
 
 func (a *arrayObject) hasOwnPropertyStr(name string) bool {
-	if idx := strToIdx(name); idx >= 0 {
-		return idx < int64(len(a.values)) && a.values[idx] != nil
+	if idx := strToIdx(name); idx != math.MaxUint32 {
+		return idx < uint32(len(a.values)) && a.values[idx] != nil
 	} else {
 		return a.baseObject.hasOwnPropertyStr(name)
 	}
 }
 
-func (a *arrayObject) expand(idx int64) bool {
+func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool {
+	if idx := toIdx(idx); idx != math.MaxUint32 {
+		return idx < uint32(len(a.values)) && a.values[idx] != nil
+	}
+	return a.baseObject.hasOwnPropertyStr(idx.String())
+}
+
+func (a *arrayObject) expand(idx uint32) bool {
 	targetLen := idx + 1
-	if targetLen > int64(len(a.values)) {
-		if targetLen < int64(cap(a.values)) {
+	if targetLen > uint32(len(a.values)) {
+		if targetLen < uint32(cap(a.values)) {
 			a.values = a.values[:targetLen]
 		} else {
-			if idx > 4096 && (a.objCount == 0 || idx/a.objCount > 10) {
+			if idx > 4096 && (a.objCount == 0 || idx/uint32(a.objCount) > 10) {
 				//log.Println("Switching standard->sparse")
 				sa := &sparseArrayObject{
 					baseObject:     a.baseObject,
-					length:         a.length,
+					length:         uint32(a.length),
 					propValueCount: a.propValueCount,
 				}
-				sa.setValues(a.values)
+				sa.setValues(a.values, a.objCount+1)
 				sa.val.self = sa
 				sa.init()
 				sa.lengthProp.writable = a.lengthProp.writable
 				return false
 			} else {
+				if bits.UintSize == 32 {
+					if targetLen >= math.MaxInt32 {
+						panic(a.val.runtime.NewTypeError("Array index overflows int"))
+					}
+				}
+				tl := int(targetLen)
 				// Use the same algorithm as in runtime.growSlice
-				newcap := int64(cap(a.values))
+				newcap := cap(a.values)
 				doublecap := newcap + newcap
-				if targetLen > doublecap {
-					newcap = targetLen
+				if tl > doublecap {
+					newcap = tl
 				} else {
 					if len(a.values) < 1024 {
 						newcap = doublecap
 					} else {
-						for newcap < targetLen {
+						for newcap < tl {
 							newcap += newcap / 4
 						}
 					}
 				}
-				newValues := make([]Value, targetLen, newcap)
+				newValues := make([]Value, tl, newcap)
 				copy(newValues, a.values)
 				a.values = newValues
 			}
@@ -414,7 +381,7 @@ func (a *arrayObject) expand(idx int64) bool {
 	return true
 }
 
-func (r *Runtime) defineArrayLength(prop *valueProperty, descr propertyDescr, setter func(Value, bool) bool, throw bool) bool {
+func (r *Runtime) defineArrayLength(prop *valueProperty, descr PropertyDescriptor, setter func(Value, bool) bool, throw bool) bool {
 	ret := true
 
 	if descr.Configurable == FLAG_TRUE || descr.Enumerable == FLAG_TRUE || descr.Getter != nil || descr.Setter != nil {
@@ -448,40 +415,50 @@ Reject:
 	return ret
 }
 
-func (a *arrayObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	if idx := toIdx(n); idx >= 0 {
-		var existing Value
-		if idx < int64(len(a.values)) {
-			existing = a.values[idx]
-		}
-		prop, ok := a.baseObject._defineOwnProperty(n, existing, descr, throw)
-		if ok {
-			if idx >= a.length {
-				if !a.setLengthInt(idx+1, throw) {
-					return false
-				}
-			}
-			if a.expand(idx) {
-				a.values[idx] = prop
-				a.objCount++
-				if _, ok := prop.(*valueProperty); ok {
-					a.propValueCount++
-				}
-			} else {
-				a.val.self.(*sparseArrayObject).putIdx(idx, prop, throw, "", nil)
+func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool {
+	var existing Value
+	if idx < uint32(len(a.values)) {
+		existing = a.values[idx]
+	}
+	prop, ok := a.baseObject._defineOwnProperty(strconv.FormatUint(uint64(idx), 10), existing, desc, throw)
+	if ok {
+		if idx >= uint32(a.length) {
+			if !a.setLengthInt(int64(idx)+1, throw) {
+				return false
 			}
 		}
-		return ok
-	} else {
-		if n.String() == "length" {
-			return a.val.runtime.defineArrayLength(&a.lengthProp, descr, a.setLength, throw)
+		if a.expand(idx) {
+			a.values[idx] = prop
+			a.objCount++
+			if _, ok := prop.(*valueProperty); ok {
+				a.propValueCount++
+			}
+		} else {
+			a.val.self.(*sparseArrayObject).add(uint32(idx), prop)
 		}
-		return a.baseObject.defineOwnProperty(n, descr, throw)
 	}
+	return ok
 }
 
-func (a *arrayObject) _deleteProp(idx int64, throw bool) bool {
-	if idx < int64(len(a.values)) {
+func (a *arrayObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+	if idx := strToIdx(name); idx != math.MaxUint32 {
+		return a._defineIdxProperty(idx, descr, throw)
+	}
+	if name == "length" {
+		return a.val.runtime.defineArrayLength(&a.lengthProp, descr, a.setLength, throw)
+	}
+	return a.baseObject.defineOwnPropertyStr(name, descr, throw)
+}
+
+func (a *arrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
+	if idx := toIdx(idx); idx != math.MaxUint32 {
+		return a._defineIdxProperty(idx, descr, throw)
+	}
+	return a.baseObject.defineOwnPropertyStr(idx.String(), descr, throw)
+}
+
+func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
+	if idx < uint32(len(a.values)) {
 		if v := a.values[idx]; v != nil {
 			if p, ok := v.(*valueProperty); ok {
 				if !p.configurable {
@@ -497,18 +474,18 @@ func (a *arrayObject) _deleteProp(idx int64, throw bool) bool {
 	return true
 }
 
-func (a *arrayObject) delete(n Value, throw bool) bool {
-	if idx := toIdx(n); idx >= 0 {
-		return a._deleteProp(idx, throw)
+func (a *arrayObject) deleteStr(name string, throw bool) bool {
+	if idx := strToIdx(name); idx != math.MaxUint32 {
+		return a._deleteIdxProp(idx, throw)
 	}
-	return a.baseObject.delete(n, throw)
+	return a.baseObject.deleteStr(name, throw)
 }
 
-func (a *arrayObject) deleteStr(name string, throw bool) bool {
-	if idx := strToIdx(name); idx >= 0 {
-		return a._deleteProp(idx, throw)
+func (a *arrayObject) deleteIdx(idx valueInt, throw bool) bool {
+	if idx := toIdx(idx); idx != math.MaxUint32 {
+		return a._deleteIdxProp(idx, throw)
 	}
-	return a.baseObject.deleteStr(name, throw)
+	return a.baseObject.deleteStr(idx.String(), throw)
 }
 
 func (a *arrayObject) export() interface{} {
@@ -526,10 +503,130 @@ func (a *arrayObject) exportType() reflect.Type {
 	return reflectTypeArray
 }
 
-func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem) {
-	a.values = make([]Value, int(items[len(items)-1].idx+1))
+func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem, newMaxIdx int) {
+	a.values = make([]Value, newMaxIdx+1)
 	for _, item := range items {
 		a.values[item.idx] = item.value
 	}
-	a.objCount = int64(len(items))
+	a.objCount = len(items)
+}
+
+func toIdx(v valueInt) uint32 {
+	if v >= 0 && v < math.MaxUint32 {
+		return uint32(v)
+	}
+	return math.MaxUint32
+}
+
+func strToIdx64(s 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 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 string) int {
+	if bits.UintSize == 64 {
+		return int(strToIdx64(s))
+	}
+	i := strToIdx(s)
+	if i == math.MaxUint32 {
+		return -1
+	}
+	if i >= math.MaxInt32 {
+		return -1
+	}
+	return int(i)
 }

+ 171 - 178
array_sparse.go

@@ -2,20 +2,21 @@ package goja
 
 import (
 	"math"
+	"math/bits"
 	"reflect"
 	"sort"
 	"strconv"
 )
 
 type sparseArrayItem struct {
-	idx   int64
+	idx   uint32
 	value Value
 }
 
 type sparseArrayObject struct {
 	baseObject
 	items          []sparseArrayItem
-	length         int64
+	length         uint32
 	propValueCount int
 	lengthProp     valueProperty
 }
@@ -27,7 +28,7 @@ func (a *sparseArrayObject) init() {
 	a._put("length", &a.lengthProp)
 }
 
-func (a *sparseArrayObject) findIdx(idx int64) int {
+func (a *sparseArrayObject) findIdx(idx uint32) int {
 	return sort.Search(len(a.items), func(i int) bool {
 		return a.items[i].idx >= idx
 	})
@@ -36,7 +37,7 @@ func (a *sparseArrayObject) findIdx(idx int64) int {
 func (a *sparseArrayObject) _setLengthInt(l int64, throw bool) bool {
 	if l >= 0 && l <= math.MaxUint32 {
 		ret := true
-
+		l := uint32(l)
 		if l <= a.length {
 			if a.propValueCount > 0 {
 				// Slow path
@@ -74,7 +75,7 @@ func (a *sparseArrayObject) _setLengthInt(l int64, throw bool) bool {
 }
 
 func (a *sparseArrayObject) setLengthInt(l int64, throw bool) bool {
-	if l == a.length {
+	if l == int64(a.length) {
 		return true
 	}
 	if !a.lengthProp.writable {
@@ -86,7 +87,7 @@ func (a *sparseArrayObject) setLengthInt(l int64, throw bool) bool {
 
 func (a *sparseArrayObject) setLength(v Value, throw bool) bool {
 	l, ok := toIntIgnoreNegZero(v)
-	if ok && l == a.length {
+	if ok && l == int64(a.length) {
 		return true
 	}
 	if !a.lengthProp.writable {
@@ -99,48 +100,46 @@ func (a *sparseArrayObject) setLength(v Value, throw bool) bool {
 	panic(a.val.runtime.newError(a.val.runtime.global.RangeError, "Invalid array length"))
 }
 
-func (a *sparseArrayObject) getIdx(idx int64, origNameStr string, origName Value) (v Value) {
+func (a *sparseArrayObject) _getIdx(idx uint32) Value {
 	i := a.findIdx(idx)
 	if i < len(a.items) && a.items[i].idx == idx {
 		return a.items[i].value
 	}
 
-	if a.prototype != nil {
-		if origName != nil {
-			v = a.prototype.self.getProp(origName)
-		} else {
-			v = a.prototype.self.getPropStr(origNameStr)
-		}
-	}
-	return
+	return nil
 }
 
-func (a *sparseArrayObject) getProp(n Value) Value {
-	if idx := toIdx(n); idx >= 0 {
-		return a.getIdx(idx, "", n)
-	}
+func (a *sparseArrayObject) getStr(name string, receiver Value) Value {
+	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
+}
 
-	if _, ok := n.(*valueSymbol); !ok {
-		if n.String() == "length" {
-			return a.getLengthProp()
+func (a *sparseArrayObject) getIdx(idx valueInt, receiver Value) Value {
+	prop := a.getOwnPropIdx(idx)
+	if prop == nil {
+		if a.prototype != nil {
+			if receiver == nil {
+				return a.prototype.self.getIdx(idx, a.val)
+			}
+			return a.prototype.self.getIdx(idx, receiver)
 		}
 	}
-
-	return a.baseObject.getProp(n)
+	if prop, ok := prop.(*valueProperty); ok {
+		if receiver == nil {
+			return prop.get(a.val)
+		}
+		return prop.get(receiver)
+	}
+	return prop
 }
 
 func (a *sparseArrayObject) getLengthProp() Value {
-	a.lengthProp.value = intToValue(a.length)
+	a.lengthProp.value = intToValue(int64(a.length))
 	return &a.lengthProp
 }
 
 func (a *sparseArrayObject) getOwnPropStr(name string) Value {
-	if idx := strToIdx(name); idx >= 0 {
-		i := a.findIdx(idx)
-		if i < len(a.items) && a.items[i].idx == idx {
-			return a.items[i].value
-		}
-		return nil
+	if idx := strToIdx(name); idx != math.MaxUint32 {
+		return a._getIdx(idx)
 	}
 	if name == "length" {
 		return a.getLengthProp()
@@ -148,17 +147,24 @@ func (a *sparseArrayObject) getOwnPropStr(name string) Value {
 	return a.baseObject.getOwnPropStr(name)
 }
 
-func (a *sparseArrayObject) getPropStr(name string) Value {
-	if i := strToIdx(name); i >= 0 {
-		return a.getIdx(i, name, nil)
+func (a *sparseArrayObject) getOwnPropIdx(idx valueInt) Value {
+	if idx := toIdx(idx); idx != math.MaxUint32 {
+		return a._getIdx(idx)
 	}
-	if name == "length" {
-		return a.getLengthProp()
+	return a.baseObject.getOwnPropStr(idx.String())
+}
+
+func (a *sparseArrayObject) add(idx uint32, val Value) {
+	i := a.findIdx(idx)
+	a.items = append(a.items, sparseArrayItem{})
+	copy(a.items[i+1:], a.items[i:])
+	a.items[i] = sparseArrayItem{
+		idx:   idx,
+		value: val,
 	}
-	return a.baseObject.getPropStr(name)
 }
 
-func (a *sparseArrayObject) putIdx(idx int64, val Value, throw bool, origNameStr string, origName Value) {
+func (a *sparseArrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
 	var prop Value
 	i := a.findIdx(idx)
 	if i < len(a.items) && a.items[i].idx == idx {
@@ -166,37 +172,26 @@ func (a *sparseArrayObject) putIdx(idx int64, val Value, throw bool, origNameStr
 	}
 
 	if prop == nil {
-		if a.prototype != nil {
-			var pprop Value
-			if origName != nil {
-				pprop = a.prototype.self.getProp(origName)
-			} else {
-				pprop = a.prototype.self.getPropStr(origNameStr)
-			}
-			if pprop, ok := pprop.(*valueProperty); ok {
-				if !pprop.isWritable() {
-					a.val.runtime.typeErrorResult(throw)
-					return
-				}
-				if pprop.accessor {
-					pprop.set(a.val, val)
-					return
-				}
+		if proto := a.prototype; proto != nil {
+			// we know it's foreign because prototype loops are not allowed
+			if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok {
+				return res
 			}
 		}
 
+		// new property
 		if !a.extensible {
-			a.val.runtime.typeErrorResult(throw)
-			return
+			a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
+			return false
 		}
 
 		if idx >= a.length {
-			if !a.setLengthInt(idx+1, throw) {
-				return
+			if !a.setLengthInt(int64(idx)+1, throw) {
+				return false
 			}
 		}
 
-		if a.expand() {
+		if a.expand(idx) {
 			a.items = append(a.items, sparseArrayItem{})
 			copy(a.items[i+1:], a.items[i:])
 			a.items[i] = sparseArrayItem{
@@ -204,52 +199,56 @@ func (a *sparseArrayObject) putIdx(idx int64, val Value, throw bool, origNameStr
 				value: val,
 			}
 		} else {
-			a.val.self.(*arrayObject).putIdx(idx, val, throw, origNameStr, origName)
-			return
+			ar := a.val.self.(*arrayObject)
+			ar.values[idx] = val
+			ar.objCount++
+			return true
 		}
 	} else {
 		if prop, ok := prop.(*valueProperty); ok {
 			if !prop.isWritable() {
 				a.val.runtime.typeErrorResult(throw)
-				return
+				return false
 			}
 			prop.set(a.val, val)
-			return
 		} else {
 			a.items[i].value = val
 		}
 	}
-
+	return true
 }
 
-func (a *sparseArrayObject) put(n Value, val Value, throw bool) {
-	if idx := toIdx(n); idx >= 0 {
-		a.putIdx(idx, val, throw, "", n)
+func (a *sparseArrayObject) setOwnStr(name string, val Value, throw bool) bool {
+	if idx := strToIdx(name); idx != math.MaxUint32 {
+		return a._setOwnIdx(idx, val, throw)
 	} else {
-		if n.String() == "length" {
-			a.setLength(val, throw)
+		if name == "length" {
+			return a.setLength(val, throw)
 		} else {
-			a.baseObject.put(n, val, throw)
+			return a.baseObject.setOwnStr(name, val, throw)
 		}
 	}
 }
 
-func (a *sparseArrayObject) putStr(name string, val Value, throw bool) {
-	if idx := strToIdx(name); idx >= 0 {
-		a.putIdx(idx, val, throw, name, nil)
-	} else {
-		if name == "length" {
-			a.setLength(val, throw)
-		} else {
-			a.baseObject.putStr(name, val, throw)
-		}
+func (a *sparseArrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
+	if idx := toIdx(idx); idx != math.MaxUint32 {
+		return a._setOwnIdx(idx, val, throw)
 	}
+
+	return a.baseObject.setOwnStr(idx.String(), val, throw)
+}
+
+func (a *sparseArrayObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
+}
+
+func (a *sparseArrayObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
+	return a._setForeignIdx(name, a.getOwnPropIdx(name), val, receiver, throw)
 }
 
 type sparseArrayPropIter struct {
-	a         *sparseArrayObject
-	recursive bool
-	idx       int
+	a   *sparseArrayObject
+	idx int
 }
 
 func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
@@ -262,70 +261,75 @@ func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
 		}
 	}
 
-	return i.a.baseObject._enumerate(i.recursive)()
+	return i.a.baseObject.enumerateUnfiltered()()
 }
 
-func (a *sparseArrayObject) _enumerate(recursive bool) iterNextFunc {
+func (a *sparseArrayObject) enumerateUnfiltered() iterNextFunc {
 	return (&sparseArrayPropIter{
-		a:         a,
-		recursive: recursive,
+		a: a,
 	}).next
 }
 
-func (a *sparseArrayObject) enumerate(all, recursive bool) iterNextFunc {
-	return (&propFilterIter{
-		wrapped: a._enumerate(recursive),
-		all:     all,
-		seen:    make(map[string]bool),
-	}).next
+func (a *sparseArrayObject) ownKeys(all bool, accum []Value) []Value {
+	if all {
+		for _, item := range a.items {
+			accum = append(accum, asciiString(strconv.FormatUint(uint64(item.idx), 10)))
+		}
+	} else {
+		for _, item := range a.items {
+			if prop, ok := item.value.(*valueProperty); ok && !prop.enumerable {
+				continue
+			}
+			accum = append(accum, asciiString(strconv.FormatUint(uint64(item.idx), 10)))
+		}
+	}
+
+	return a.baseObject.ownKeys(all, accum)
 }
 
-func (a *sparseArrayObject) setValues(values []Value) {
-	a.items = nil
+func (a *sparseArrayObject) setValues(values []Value, objCount int) {
+	a.items = make([]sparseArrayItem, 0, objCount)
 	for i, val := range values {
 		if val != nil {
 			a.items = append(a.items, sparseArrayItem{
-				idx:   int64(i),
+				idx:   uint32(i),
 				value: val,
 			})
 		}
 	}
 }
 
-func (a *sparseArrayObject) hasOwnProperty(n Value) bool {
-	if idx := toIdx(n); idx >= 0 {
+func (a *sparseArrayObject) hasOwnPropertyStr(name string) bool {
+	if idx := strToIdx(name); idx != math.MaxUint32 {
 		i := a.findIdx(idx)
-		if i < len(a.items) && a.items[i].idx == idx {
-			return a.items[i].value != _undefined
-		}
-		return false
+		return i < len(a.items) && a.items[i].idx == idx
 	} else {
-		return a.baseObject.hasOwnProperty(n)
+		return a.baseObject.hasOwnPropertyStr(name)
 	}
 }
 
-func (a *sparseArrayObject) hasOwnPropertyStr(name string) bool {
-	if idx := strToIdx(name); idx >= 0 {
+func (a *sparseArrayObject) hasOwnPropertyIdx(idx valueInt) bool {
+	if idx := toIdx(idx); idx != math.MaxUint32 {
 		i := a.findIdx(idx)
-		if i < len(a.items) && a.items[i].idx == idx {
-			return a.items[i].value != _undefined
-		}
-		return false
-	} else {
-		return a.baseObject.hasOwnPropertyStr(name)
+		return i < len(a.items) && a.items[i].idx == idx
 	}
+
+	return a.baseObject.hasOwnPropertyStr(idx.String())
 }
 
-func (a *sparseArrayObject) expand() bool {
+func (a *sparseArrayObject) expand(idx uint32) bool {
 	if l := len(a.items); l >= 1024 {
-		if int(a.items[l-1].idx)/l < 8 {
+		if ii := a.items[l-1].idx; ii > idx {
+			idx = ii
+		}
+		if (bits.UintSize == 64 || idx < math.MaxInt32) && int(idx)>>3 < l {
 			//log.Println("Switching sparse->standard")
 			ar := &arrayObject{
 				baseObject:     a.baseObject,
 				length:         a.length,
 				propValueCount: a.propValueCount,
 			}
-			ar.setValuesFromSparse(a.items)
+			ar.setValuesFromSparse(a.items, int(idx))
 			ar.val.self = ar
 			ar.init()
 			ar.lengthProp.writable = a.lengthProp.writable
@@ -335,51 +339,61 @@ func (a *sparseArrayObject) expand() bool {
 	return true
 }
 
-func (a *sparseArrayObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	if idx := toIdx(n); idx >= 0 {
-		var existing Value
-		i := a.findIdx(idx)
-		if i < len(a.items) && a.items[i].idx == idx {
-			existing = a.items[i].value
+func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool {
+	var existing Value
+	i := a.findIdx(idx)
+	if i < len(a.items) && a.items[i].idx == idx {
+		existing = a.items[i].value
+	}
+	prop, ok := a.baseObject._defineOwnProperty(strconv.FormatUint(uint64(idx), 10), existing, desc, throw)
+	if ok {
+		if idx >= a.length {
+			if !a.setLengthInt(int64(idx)+1, throw) {
+				return false
+			}
 		}
-		prop, ok := a.baseObject._defineOwnProperty(n, existing, descr, throw)
-		if ok {
-			if idx >= a.length {
-				if !a.setLengthInt(idx+1, throw) {
-					return false
+		if i >= len(a.items) || a.items[i].idx != idx {
+			if a.expand(idx) {
+				a.items = append(a.items, sparseArrayItem{})
+				copy(a.items[i+1:], a.items[i:])
+				a.items[i] = sparseArrayItem{
+					idx:   idx,
+					value: prop,
 				}
-			}
-			if i >= len(a.items) || a.items[i].idx != idx {
-				if a.expand() {
-					a.items = append(a.items, sparseArrayItem{})
-					copy(a.items[i+1:], a.items[i:])
-					a.items[i] = sparseArrayItem{
-						idx:   idx,
-						value: prop,
-					}
-					if idx >= a.length {
-						a.length = idx + 1
-					}
-				} else {
-					return a.val.self.defineOwnProperty(n, descr, throw)
+				if idx >= a.length {
+					a.length = idx + 1
 				}
 			} else {
-				a.items[i].value = prop
-			}
-			if _, ok := prop.(*valueProperty); ok {
-				a.propValueCount++
+				a.val.self.(*arrayObject).values[idx] = prop
 			}
+		} else {
+			a.items[i].value = prop
 		}
-		return ok
-	} else {
-		if n.String() == "length" {
-			return a.val.runtime.defineArrayLength(&a.lengthProp, descr, a.setLength, throw)
+		if _, ok := prop.(*valueProperty); ok {
+			a.propValueCount++
 		}
-		return a.baseObject.defineOwnProperty(n, descr, throw)
 	}
+	return ok
+}
+
+func (a *sparseArrayObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+	if idx := strToIdx(name); idx != math.MaxUint32 {
+		return a._defineIdxProperty(idx, descr, throw)
+	}
+	if name == "length" {
+		return a.val.runtime.defineArrayLength(&a.lengthProp, descr, a.setLength, throw)
+	}
+	return a.baseObject.defineOwnPropertyStr(name, descr, throw)
+}
+
+func (a *sparseArrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
+	if idx := toIdx(idx); idx != math.MaxUint32 {
+		return a._defineIdxProperty(idx, descr, throw)
+	}
+	return a.baseObject.defineOwnPropertyStr(idx.String(), descr, throw)
 }
 
-func (a *sparseArrayObject) _deleteProp(idx int64, throw bool) bool {
+func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
 	i := a.findIdx(idx)
 	if i < len(a.items) && a.items[i].idx == idx {
 		if p, ok := a.items[i].value.(*valueProperty); ok {
@@ -396,49 +410,28 @@ func (a *sparseArrayObject) _deleteProp(idx int64, throw bool) bool {
 	return true
 }
 
-func (a *sparseArrayObject) delete(n Value, throw bool) bool {
-	if idx := toIdx(n); idx >= 0 {
-		return a._deleteProp(idx, throw)
+func (a *sparseArrayObject) deleteStr(name string, throw bool) bool {
+	if idx := strToIdx(name); idx != math.MaxUint32 {
+		return a._deleteIdxProp(idx, throw)
 	}
-	return a.baseObject.delete(n, throw)
+	return a.baseObject.deleteStr(name, throw)
 }
 
-func (a *sparseArrayObject) deleteStr(name string, throw bool) bool {
-	if idx := strToIdx(name); idx >= 0 {
-		return a._deleteProp(idx, throw)
+func (a *sparseArrayObject) deleteIdx(idx valueInt, throw bool) bool {
+	if idx := toIdx(idx); idx != math.MaxUint32 {
+		return a._deleteIdxProp(idx, throw)
 	}
-	return a.baseObject.deleteStr(name, throw)
+	return a.baseObject.deleteStr(idx.String(), throw)
 }
 
 func (a *sparseArrayObject) sortLen() int64 {
 	if len(a.items) > 0 {
-		return a.items[len(a.items)-1].idx + 1
+		return int64(a.items[len(a.items)-1].idx) + 1
 	}
 
 	return 0
 }
 
-func (a *sparseArrayObject) sortGet(i int64) Value {
-	idx := a.findIdx(i)
-	if idx < len(a.items) && a.items[idx].idx == i {
-		v := a.items[idx].value
-		if p, ok := v.(*valueProperty); ok {
-			v = p.get(a.val)
-		}
-		return v
-	}
-	return nil
-}
-
-func (a *sparseArrayObject) swap(i, j int64) {
-	idxI := a.findIdx(i)
-	idxJ := a.findIdx(j)
-
-	if idxI < len(a.items) && a.items[idxI].idx == i && idxJ < len(a.items) && a.items[idxJ].idx == j {
-		a.items[idxI].value, a.items[idxJ].value = a.items[idxJ].value, a.items[idxI].value
-	}
-}
-
 func (a *sparseArrayObject) export() interface{} {
 	arr := make([]interface{}, a.length)
 	for _, item := range a.items {

+ 77 - 6
array_sparse_test.go

@@ -1,6 +1,8 @@
 package goja
 
-import "testing"
+import (
+	"testing"
+)
 
 func TestSparseArraySetLengthWithPropItems(t *testing.T) {
 	const SCRIPT = `
@@ -21,9 +23,18 @@ func TestSparseArraySetLengthWithPropItems(t *testing.T) {
 }
 
 func TestSparseArraySwitch(t *testing.T) {
-	const SCRIPT = `
+	vm := New()
+	_, err := vm.RunString(`
 	var a = [];
-	a[20470] = 5; // switch to sparse
+	a[20470] = 5; // switch to sparse`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	a := vm.Get("a").(*Object)
+	if _, ok := a.self.(*sparseArrayObject); !ok {
+		t.Fatal("1: array is not sparse")
+	}
+	_, err = vm.RunString(`
 	var cutoffIdx = Math.round(20470 - 20470/8);
 	for (var i = a.length - 1; i >= cutoffIdx; i--) {
 		a[i] = i;
@@ -44,8 +55,14 @@ func TestSparseArraySwitch(t *testing.T) {
 		if (a[i] !== i) {
 			throw new Error("Invalid value at " + i + ": " + a[i]);
 		}
+	}`)
+	if err != nil {
+		t.Fatal(err)
 	}
-
+	if _, ok := a.self.(*arrayObject); !ok {
+		t.Fatal("2: array is not normal")
+	}
+	_, err = vm.RunString(`
 	// Now try to expand. Should stay a normal array
 	a[20471] = 20471;
 	if (a.length != 20472) {
@@ -62,8 +79,14 @@ func TestSparseArraySwitch(t *testing.T) {
 		if (a[i] !== i) {
 			throw new Error("Invalid value at " + i + ": " + a[i]);
 		}
+	}`)
+	if err != nil {
+		t.Fatal(err)
 	}
-
+	if _, ok := a.self.(*arrayObject); !ok {
+		t.Fatal("3: array is not normal")
+	}
+	_, err = vm.RunString(`
 	// Delete enough elements for it to become sparse again.
 	var cutoffIdx1 = Math.round(20472 - 20472/10);
 	for (var i = cutoffIdx; i < cutoffIdx1; i++) {
@@ -97,7 +120,55 @@ func TestSparseArraySwitch(t *testing.T) {
 	if (a[25590] !== 25590) {
 		throw new Error("Invalid value at 25590: " + a[25590]);
 	}
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, ok := a.self.(*sparseArrayObject); !ok {
+		t.Fatal("4: array is not sparse")
+	}
+}
+
+func TestSparseArrayOwnKeys(t *testing.T) {
+	const SCRIPT = `
+	var a1 = [];
+	a1[500000] = 1;
+	var seen = false;
+	var count = 0;
+	var keys = Object.keys(a1);
+	keys.length === 1 && keys[0] === "500000"; 
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestSparseArrayEnumerate(t *testing.T) {
+	const SCRIPT = `
+	var a1 = [];
+	a1[500000] = 1;
+	var seen = false;
+	var count = 0;
+	for (var i in a1) {
+		if (i === "500000") {
+			if (seen) {
+				throw new Error("seen twice");
+			}
+			seen = true;
+		}
+		count++;
+	}
+	seen && count === 1;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArraySparseMaxLength(t *testing.T) {
+	const SCRIPT = `
+	var a = [];
+	a[4294967294]=1;
+	a.length === 4294967295 && a[4294967294] === 1;
 	`
 
-	testScript1(SCRIPT, _undefined, t)
+	testScript1(SCRIPT, valueTrue, t)
 }

+ 10 - 0
ast/node.go

@@ -172,6 +172,11 @@ type (
 		Idx         file.Idx
 		Initializer Expression
 	}
+
+	MetaProperty struct {
+		Meta, Property *Identifier
+		Idx            file.Idx
+	}
 )
 
 // _expressionNode
@@ -197,6 +202,7 @@ func (*StringLiteral) _expressionNode()         {}
 func (*ThisExpression) _expressionNode()        {}
 func (*UnaryExpression) _expressionNode()       {}
 func (*VariableExpression) _expressionNode()    {}
+func (*MetaProperty) _expressionNode()          {}
 
 // ========= //
 // Statement //
@@ -413,6 +419,7 @@ func (self *StringLiteral) Idx0() file.Idx         { return self.Idx }
 func (self *ThisExpression) Idx0() file.Idx        { return self.Idx }
 func (self *UnaryExpression) Idx0() file.Idx       { return self.Idx }
 func (self *VariableExpression) Idx0() file.Idx    { return self.Idx }
+func (self *MetaProperty) Idx0() file.Idx          { return self.Idx }
 
 func (self *BadStatement) Idx0() file.Idx        { return self.From }
 func (self *BlockStatement) Idx0() file.Idx      { return self.LeftBrace }
@@ -471,6 +478,9 @@ func (self *VariableExpression) Idx1() file.Idx {
 	}
 	return self.Initializer.Idx1()
 }
+func (self *MetaProperty) Idx1() file.Idx {
+	return self.Property.Idx1()
+}
 
 func (self *BadStatement) Idx1() file.Idx        { return self.To }
 func (self *BlockStatement) Idx1() file.Idx      { return self.RightBrace + 1 }

+ 175 - 175
builtin_array.go

@@ -1,6 +1,7 @@
 package goja
 
 import (
+	"math"
 	"sort"
 	"strings"
 )
@@ -24,21 +25,21 @@ func (r *Runtime) newArrayObject() *arrayObject {
 
 func setArrayValues(a *arrayObject, values []Value) *arrayObject {
 	a.values = values
-	a.length = int64(len(values))
-	a.objCount = a.length
+	a.length = uint32(len(values))
+	a.objCount = len(values)
 	return a
 }
 
 func setArrayLength(a *arrayObject, l int64) *arrayObject {
-	a.putStr("length", intToValue(l), true)
+	a.setOwnStr("length", intToValue(l), true)
 	return a
 }
 
 func arraySpeciesCreate(obj *Object, size int64) *Object {
 	if isArray(obj) {
-		v := obj.self.getStr("constructor")
+		v := obj.self.getStr("constructor", nil)
 		if constructObj, ok := v.(*Object); ok {
-			v = constructObj.self.get(symSpecies)
+			v = constructObj.self.getSym(symSpecies, nil)
 			if v == _null {
 				v = nil
 			}
@@ -47,9 +48,8 @@ func arraySpeciesCreate(obj *Object, size int64) *Object {
 		if v != nil && v != _undefined {
 			constructObj, _ := v.(*Object)
 			if constructObj != nil {
-				constructor := getConstructor(constructObj)
-				if constructor != nil {
-					return constructor([]Value{intToValue(size)})
+				if constructor := constructObj.self.assertConstructor(); constructor != nil {
+					return constructor([]Value{intToValue(size)}, constructObj)
 				}
 			}
 			panic(obj.runtime.NewTypeError("Species is not a constructor"))
@@ -90,11 +90,11 @@ func (r *Runtime) newArrayLength(l int64) *Object {
 func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object {
 	l := len(args)
 	if l == 1 {
-		if al, ok := args[0].assertInt(); ok {
-			return setArrayLength(r.newArray(proto), al).val
-		} else if f, ok := args[0].assertFloat(); ok {
+		if al, ok := args[0].(valueInt); ok {
+			return setArrayLength(r.newArray(proto), int64(al)).val
+		} else if f, ok := args[0].(valueFloat); ok {
 			al := int64(f)
-			if float64(al) == f {
+			if float64(al) == float64(f) {
 				return r.newArrayLength(al)
 			} else {
 				panic(r.newError(r.global.RangeError, "Invalid array length"))
@@ -109,17 +109,17 @@ func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object {
 }
 
 func (r *Runtime) generic_push(obj *Object, call FunctionCall) Value {
-	l := toLength(obj.self.getStr("length"))
+	l := toLength(obj.self.getStr("length", nil))
 	nl := l + int64(len(call.Arguments))
 	if nl >= maxInt {
 		r.typeErrorResult(true, "Invalid array length")
 		panic("unreachable")
 	}
 	for i, arg := range call.Arguments {
-		obj.self.put(intToValue(l+int64(i)), arg, true)
+		obj.self.setOwnIdx(valueInt(l+int64(i)), arg, true)
 	}
-	n := intToValue(nl)
-	obj.self.putStr("length", n, true)
+	n := valueInt(nl)
+	obj.self.setOwnStr("length", n, true)
 	return n
 }
 
@@ -129,15 +129,15 @@ func (r *Runtime) arrayproto_push(call FunctionCall) Value {
 }
 
 func (r *Runtime) arrayproto_pop_generic(obj *Object) Value {
-	l := toLength(obj.self.getStr("length"))
+	l := toLength(obj.self.getStr("length", nil))
 	if l == 0 {
-		obj.self.putStr("length", intToValue(0), true)
+		obj.self.setOwnStr("length", intToValue(0), true)
 		return _undefined
 	}
-	idx := intToValue(l - 1)
-	val := obj.self.get(idx)
-	obj.self.delete(idx, true)
-	obj.self.putStr("length", idx, true)
+	idx := valueInt(l - 1)
+	val := obj.self.getIdx(idx, nil)
+	obj.self.deleteIdx(idx, true)
+	obj.self.setOwnStr("length", idx, true)
 	return val
 }
 
@@ -148,7 +148,7 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value {
 		if l > 0 {
 			var val Value
 			l--
-			if l < int64(len(a.values)) {
+			if l < uint32(len(a.values)) {
 				val = a.values[l]
 			}
 			if val == nil {
@@ -173,7 +173,7 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_join(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	l := int(toLength(o.self.getStr("length")))
+	l := int(toLength(o.self.getStr("length", nil)))
 	sep := ""
 	if s := call.Argument(0); s != _undefined {
 		sep = s.String()
@@ -186,14 +186,14 @@ func (r *Runtime) arrayproto_join(call FunctionCall) Value {
 
 	var buf strings.Builder
 
-	element0 := o.self.get(intToValue(0))
+	element0 := o.self.getIdx(valueInt(0), nil)
 	if element0 != nil && element0 != _undefined && element0 != _null {
 		buf.WriteString(element0.String())
 	}
 
 	for i := 1; i < l; i++ {
 		buf.WriteString(sep)
-		element := o.self.get(intToValue(int64(i)))
+		element := o.self.getIdx(valueInt(int64(i)), nil)
 		if element != nil && element != _undefined && element != _null {
 			buf.WriteString(element.String())
 		}
@@ -204,7 +204,7 @@ func (r *Runtime) arrayproto_join(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_toString(call FunctionCall) Value {
 	array := call.This.ToObject(r)
-	f := array.self.getStr("join")
+	f := array.self.getStr("join", nil)
 	if fObj, ok := f.(*Object); ok {
 		if fcall, ok := fObj.self.assertCallable(); ok {
 			return fcall(FunctionCall{
@@ -243,12 +243,12 @@ func (r *Runtime) arrayproto_toLocaleString(call FunctionCall) Value {
 			r.writeItemLocaleString(item, &buf)
 		}
 	} else {
-		length := toLength(array.self.getStr("length"))
+		length := toLength(array.self.getStr("length", nil))
 		for i := int64(0); i < length; i++ {
 			if i > 0 {
 				buf.WriteByte(',')
 			}
-			item := array.self.get(intToValue(i))
+			item := array.self.getIdx(valueInt(i), nil)
 			r.writeItemLocaleString(item, &buf)
 		}
 	}
@@ -257,7 +257,7 @@ func (r *Runtime) arrayproto_toLocaleString(call FunctionCall) Value {
 }
 
 func isConcatSpreadable(obj *Object) bool {
-	spreadable := obj.self.get(symIsConcatSpreadable)
+	spreadable := obj.self.getSym(symIsConcatSpreadable, nil)
 	if spreadable != nil && spreadable != _undefined {
 		return spreadable.ToBoolean()
 	}
@@ -265,21 +265,21 @@ func isConcatSpreadable(obj *Object) bool {
 }
 
 func (r *Runtime) arrayproto_concat_append(a *Object, item Value) {
-	aLength := toLength(a.self.getStr("length"))
+	aLength := toLength(a.self.getStr("length", nil))
 	if obj, ok := item.(*Object); ok && isConcatSpreadable(obj) {
-		length := toLength(obj.self.getStr("length"))
+		length := toLength(obj.self.getStr("length", nil))
 		for i := int64(0); i < length; i++ {
-			v := obj.self.get(intToValue(i))
+			v := obj.self.getIdx(valueInt(i), nil)
 			if v != nil {
-				defineDataPropertyOrThrow(a, intToValue(aLength), v)
+				createDataPropertyOrThrow(a, intToValue(aLength), v)
 			}
 			aLength++
 		}
 	} else {
-		defineDataPropertyOrThrow(a, intToValue(aLength), item)
+		createDataPropertyOrThrow(a, intToValue(aLength), item)
 		aLength++
 	}
-	a.self.putStr("length", intToValue(aLength), true)
+	a.self.setOwnStr("length", intToValue(aLength), true)
 }
 
 func (r *Runtime) arrayproto_concat(call FunctionCall) Value {
@@ -294,7 +294,7 @@ func (r *Runtime) arrayproto_concat(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_slice(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	start := relToIdx(call.Argument(0).ToInteger(), length)
 	var end int64
 	if endArg := call.Argument(1); endArg != _undefined {
@@ -321,9 +321,9 @@ func (r *Runtime) arrayproto_slice(call FunctionCall) Value {
 
 	n := int64(0)
 	for start < end {
-		p := o.self.get(intToValue(start))
+		p := o.self.getIdx(valueInt(start), nil)
 		if p != nil {
-			defineDataPropertyOrThrow(a, intToValue(n), p)
+			createDataPropertyOrThrow(a, valueInt(n), p)
 		}
 		start++
 		n++
@@ -351,7 +351,7 @@ func (r *Runtime) arrayproto_sort(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_splice(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	actualStart := relToIdx(call.Argument(0).ToInteger(), length)
 	var actualDeleteCount int64
 	switch len(call.Arguments) {
@@ -371,7 +371,7 @@ func (r *Runtime) arrayproto_splice(call FunctionCall) Value {
 			setArrayValues(dst, values)
 		} else {
 			for k := int64(0); k < actualDeleteCount; k++ {
-				defineDataPropertyOrThrow(a, intToValue(k), src.values[k+actualStart])
+				createDataPropertyOrThrow(a, intToValue(k), src.values[k+actualStart])
 			}
 		}
 		var values []Value
@@ -399,60 +399,60 @@ func (r *Runtime) arrayproto_splice(call FunctionCall) Value {
 			copy(values[actualStart:], call.Arguments[2:])
 		}
 		src.values = values
-		src.objCount = int64(len(values))
+		src.objCount = len(values)
 	} else {
 		for k := int64(0); k < actualDeleteCount; k++ {
-			from := intToValue(k + actualStart)
-			if o.self.hasProperty(from) {
-				defineDataPropertyOrThrow(a, intToValue(k), o.self.get(from))
+			from := valueInt(k + actualStart)
+			if o.self.hasPropertyIdx(from) {
+				createDataPropertyOrThrow(a, valueInt(k), o.self.getIdx(from, nil))
 			}
 		}
 
 		if itemCount < actualDeleteCount {
 			for k := actualStart; k < length-actualDeleteCount; k++ {
-				from := intToValue(k + actualDeleteCount)
-				to := intToValue(k + itemCount)
-				if o.self.hasProperty(from) {
-					o.self.put(to, o.self.get(from), true)
+				from := valueInt(k + actualDeleteCount)
+				to := valueInt(k + itemCount)
+				if o.self.hasPropertyIdx(from) {
+					o.self.setOwnIdx(to, o.self.getIdx(from, nil), true)
 				} else {
-					o.self.delete(to, true)
+					o.self.deleteIdx(to, true)
 				}
 			}
 
 			for k := length; k > length-actualDeleteCount+itemCount; k-- {
-				o.self.delete(intToValue(k-1), true)
+				o.self.deleteIdx(valueInt(k-1), true)
 			}
 		} else if itemCount > actualDeleteCount {
 			for k := length - actualDeleteCount; k > actualStart; k-- {
-				from := intToValue(k + actualDeleteCount - 1)
-				to := intToValue(k + itemCount - 1)
-				if o.self.hasProperty(from) {
-					o.self.put(to, o.self.get(from), true)
+				from := valueInt(k + actualDeleteCount - 1)
+				to := valueInt(k + itemCount - 1)
+				if o.self.hasPropertyIdx(from) {
+					o.self.setOwnIdx(to, o.self.getIdx(from, nil), true)
 				} else {
-					o.self.delete(to, true)
+					o.self.deleteIdx(to, true)
 				}
 			}
 		}
 
 		if itemCount > 0 {
 			for i, item := range call.Arguments[2:] {
-				o.self.put(intToValue(actualStart+int64(i)), item, true)
+				o.self.setOwnIdx(valueInt(actualStart+int64(i)), item, true)
 			}
 		}
 	}
 
-	o.self.putStr("length", intToValue(newLength), true)
+	o.self.setOwnStr("length", intToValue(newLength), true)
 
 	return a
 }
 
 func (r *Runtime) arrayproto_unshift(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	argCount := int64(len(call.Arguments))
 	newLen := intToValue(length + argCount)
-	if arr := r.checkStdArrayObj(o); arr != nil {
-		newSize := length + argCount
+	newSize := length + argCount
+	if arr := r.checkStdArrayObj(o); arr != nil && newSize < math.MaxUint32 {
 		if int64(cap(arr.values)) >= newSize {
 			arr.values = arr.values[:newSize]
 			copy(arr.values[argCount:], arr.values[:length])
@@ -462,30 +462,30 @@ func (r *Runtime) arrayproto_unshift(call FunctionCall) Value {
 			arr.values = values
 		}
 		copy(arr.values, call.Arguments)
-		arr.objCount = arr.length
+		arr.objCount = int(arr.length)
 	} else {
 		for k := length - 1; k >= 0; k-- {
-			from := intToValue(k)
-			to := intToValue(k + argCount)
-			if o.self.hasProperty(from) {
-				o.self.put(to, o.self.get(from), true)
+			from := valueInt(k)
+			to := valueInt(k + argCount)
+			if o.self.hasPropertyIdx(from) {
+				o.self.setOwnIdx(to, o.self.getIdx(from, nil), true)
 			} else {
-				o.self.delete(to, true)
+				o.self.deleteIdx(to, true)
 			}
 		}
 
 		for k, arg := range call.Arguments {
-			o.self.put(intToValue(int64(k)), arg, true)
+			o.self.setOwnIdx(valueInt(int64(k)), arg, true)
 		}
 	}
 
-	o.self.putStr("length", newLen, true)
+	o.self.setOwnStr("length", newLen, true)
 	return newLen
 }
 
 func (r *Runtime) arrayproto_indexOf(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	if length == 0 {
 		return intToValue(-1)
 	}
@@ -511,22 +511,22 @@ func (r *Runtime) arrayproto_indexOf(call FunctionCall) Value {
 	}
 
 	for ; n < length; n++ {
-		idx := intToValue(n)
-		if val := o.self.get(idx); val != nil {
+		idx := valueInt(n)
+		if val := o.self.getIdx(idx, nil); val != nil {
 			if searchElement.StrictEquals(val) {
 				return idx
 			}
 		}
 	}
 
-	return intToValue(-1)
+	return valueInt(-1)
 }
 
 func (r *Runtime) arrayproto_lastIndexOf(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	if length == 0 {
-		return intToValue(-1)
+		return valueInt(-1)
 	}
 
 	var fromIndex int64
@@ -555,8 +555,8 @@ func (r *Runtime) arrayproto_lastIndexOf(call FunctionCall) Value {
 	}
 
 	for k := fromIndex; k >= 0; k-- {
-		idx := intToValue(k)
-		if val := o.self.get(idx); val != nil {
+		idx := valueInt(k)
+		if val := o.self.getIdx(idx, nil); val != nil {
 			if searchElement.StrictEquals(val) {
 				return idx
 			}
@@ -568,15 +568,15 @@ func (r *Runtime) arrayproto_lastIndexOf(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_every(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	callbackFn := r.toCallable(call.Argument(0))
 	fc := FunctionCall{
 		This:      call.Argument(1),
 		Arguments: []Value{nil, nil, o},
 	}
 	for k := int64(0); k < length; k++ {
-		idx := intToValue(k)
-		if val := o.self.get(idx); val != nil {
+		idx := valueInt(k)
+		if val := o.self.getIdx(idx, nil); val != nil {
 			fc.Arguments[0] = val
 			fc.Arguments[1] = idx
 			if !callbackFn(fc).ToBoolean() {
@@ -589,15 +589,15 @@ func (r *Runtime) arrayproto_every(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_some(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	callbackFn := r.toCallable(call.Argument(0))
 	fc := FunctionCall{
 		This:      call.Argument(1),
 		Arguments: []Value{nil, nil, o},
 	}
 	for k := int64(0); k < length; k++ {
-		idx := intToValue(k)
-		if val := o.self.get(idx); val != nil {
+		idx := valueInt(k)
+		if val := o.self.getIdx(idx, nil); val != nil {
 			fc.Arguments[0] = val
 			fc.Arguments[1] = idx
 			if callbackFn(fc).ToBoolean() {
@@ -610,15 +610,15 @@ func (r *Runtime) arrayproto_some(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_forEach(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	callbackFn := r.toCallable(call.Argument(0))
 	fc := FunctionCall{
 		This:      call.Argument(1),
 		Arguments: []Value{nil, nil, o},
 	}
 	for k := int64(0); k < length; k++ {
-		idx := intToValue(k)
-		if val := o.self.get(idx); val != nil {
+		idx := valueInt(k)
+		if val := o.self.getIdx(idx, nil); val != nil {
 			fc.Arguments[0] = val
 			fc.Arguments[1] = idx
 			callbackFn(fc)
@@ -629,7 +629,7 @@ func (r *Runtime) arrayproto_forEach(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_map(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	callbackFn := r.toCallable(call.Argument(0))
 	fc := FunctionCall{
 		This:      call.Argument(1),
@@ -640,8 +640,8 @@ func (r *Runtime) arrayproto_map(call FunctionCall) Value {
 		if arr, ok := a.self.(*arrayObject); ok {
 			values := make([]Value, length)
 			for k := int64(0); k < length; k++ {
-				idx := intToValue(k)
-				if val := o.self.get(idx); val != nil {
+				idx := valueInt(k)
+				if val := o.self.getIdx(idx, nil); val != nil {
 					fc.Arguments[0] = val
 					fc.Arguments[1] = idx
 					values[k] = callbackFn(fc)
@@ -652,11 +652,11 @@ func (r *Runtime) arrayproto_map(call FunctionCall) Value {
 		}
 	}
 	for k := int64(0); k < length; k++ {
-		idx := intToValue(k)
-		if val := o.self.get(idx); val != nil {
+		idx := valueInt(k)
+		if val := o.self.getIdx(idx, nil); val != nil {
 			fc.Arguments[0] = val
 			fc.Arguments[1] = idx
-			defineDataPropertyOrThrow(a, idx, callbackFn(fc))
+			createDataPropertyOrThrow(a, idx, callbackFn(fc))
 		}
 	}
 	return a
@@ -664,7 +664,7 @@ func (r *Runtime) arrayproto_map(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_filter(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	callbackFn := call.Argument(0).ToObject(r)
 	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
 		a := arraySpeciesCreate(o, 0)
@@ -676,8 +676,8 @@ func (r *Runtime) arrayproto_filter(call FunctionCall) Value {
 			if arr := r.checkStdArrayObj(a); arr != nil {
 				var values []Value
 				for k := int64(0); k < length; k++ {
-					idx := intToValue(k)
-					if val := o.self.get(idx); val != nil {
+					idx := valueInt(k)
+					if val := o.self.getIdx(idx, nil); val != nil {
 						fc.Arguments[0] = val
 						fc.Arguments[1] = idx
 						if callbackFn(fc).ToBoolean() {
@@ -692,12 +692,12 @@ func (r *Runtime) arrayproto_filter(call FunctionCall) Value {
 
 		to := int64(0)
 		for k := int64(0); k < length; k++ {
-			idx := intToValue(k)
-			if val := o.self.get(idx); val != nil {
+			idx := valueInt(k)
+			if val := o.self.getIdx(idx, nil); val != nil {
 				fc.Arguments[0] = val
 				fc.Arguments[1] = idx
 				if callbackFn(fc).ToBoolean() {
-					defineDataPropertyOrThrow(a, intToValue(to), val)
+					createDataPropertyOrThrow(a, intToValue(to), val)
 					to++
 				}
 			}
@@ -711,7 +711,7 @@ func (r *Runtime) arrayproto_filter(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_reduce(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	callbackFn := call.Argument(0).ToObject(r)
 	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
 		fc := FunctionCall{
@@ -725,8 +725,8 @@ func (r *Runtime) arrayproto_reduce(call FunctionCall) Value {
 			fc.Arguments[0] = call.Argument(1)
 		} else {
 			for ; k < length; k++ {
-				idx := intToValue(k)
-				if val := o.self.get(idx); val != nil {
+				idx := valueInt(k)
+				if val := o.self.getIdx(idx, nil); val != nil {
 					fc.Arguments[0] = val
 					break
 				}
@@ -739,8 +739,8 @@ func (r *Runtime) arrayproto_reduce(call FunctionCall) Value {
 		}
 
 		for ; k < length; k++ {
-			idx := intToValue(k)
-			if val := o.self.get(idx); val != nil {
+			idx := valueInt(k)
+			if val := o.self.getIdx(idx, nil); val != nil {
 				fc.Arguments[1] = val
 				fc.Arguments[2] = idx
 				fc.Arguments[0] = callbackFn(fc)
@@ -755,7 +755,7 @@ func (r *Runtime) arrayproto_reduce(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_reduceRight(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	callbackFn := call.Argument(0).ToObject(r)
 	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
 		fc := FunctionCall{
@@ -769,8 +769,8 @@ func (r *Runtime) arrayproto_reduceRight(call FunctionCall) Value {
 			fc.Arguments[0] = call.Argument(1)
 		} else {
 			for ; k >= 0; k-- {
-				idx := intToValue(k)
-				if val := o.self.get(idx); val != nil {
+				idx := valueInt(k)
+				if val := o.self.getIdx(idx, nil); val != nil {
 					fc.Arguments[0] = val
 					break
 				}
@@ -783,8 +783,8 @@ func (r *Runtime) arrayproto_reduceRight(call FunctionCall) Value {
 		}
 
 		for ; k >= 0; k-- {
-			idx := intToValue(k)
-			if val := o.self.get(idx); val != nil {
+			idx := valueInt(k)
+			if val := o.self.getIdx(idx, nil); val != nil {
 				fc.Arguments[1] = val
 				fc.Arguments[2] = idx
 				fc.Arguments[0] = callbackFn(fc)
@@ -798,24 +798,24 @@ func (r *Runtime) arrayproto_reduceRight(call FunctionCall) Value {
 }
 
 func arrayproto_reverse_generic_step(o *Object, lower, upper int64) {
-	lowerP := intToValue(lower)
-	upperP := intToValue(upper)
-	lowerValue := o.self.get(lowerP)
-	upperValue := o.self.get(upperP)
+	lowerP := valueInt(lower)
+	upperP := valueInt(upper)
+	lowerValue := o.self.getIdx(lowerP, nil)
+	upperValue := o.self.getIdx(upperP, nil)
 	if lowerValue != nil && upperValue != nil {
-		o.self.put(lowerP, upperValue, true)
-		o.self.put(upperP, lowerValue, true)
+		o.self.setOwnIdx(lowerP, upperValue, true)
+		o.self.setOwnIdx(upperP, lowerValue, true)
 	} else if lowerValue == nil && upperValue != nil {
-		o.self.put(lowerP, upperValue, true)
-		o.self.delete(upperP, true)
+		o.self.setOwnIdx(lowerP, upperValue, true)
+		o.self.deleteIdx(upperP, true)
 	} else if lowerValue != nil && upperValue == nil {
-		o.self.delete(lowerP, true)
-		o.self.put(upperP, lowerValue, true)
+		o.self.deleteIdx(lowerP, true)
+		o.self.setOwnIdx(upperP, lowerValue, true)
 	}
 }
 
 func (r *Runtime) arrayproto_reverse_generic(o *Object, start int64) {
-	l := toLength(o.self.getStr("length"))
+	l := toLength(o.self.getStr("length", nil))
 	middle := l / 2
 	for lower := start; lower != middle; lower++ {
 		arrayproto_reverse_generic_step(o, lower, l-lower-1)
@@ -825,9 +825,9 @@ func (r *Runtime) arrayproto_reverse_generic(o *Object, start int64) {
 func (r *Runtime) arrayproto_reverse(call FunctionCall) Value {
 	o := call.This.ToObject(r)
 	if a := r.checkStdArrayObj(o); a != nil {
-		l := a.length
+		l := len(a.values)
 		middle := l / 2
-		for lower := int64(0); lower != middle; lower++ {
+		for lower := 0; lower != middle; lower++ {
 			upper := l - lower - 1
 			a.values[lower], a.values[upper] = a.values[upper], a.values[lower]
 		}
@@ -840,24 +840,24 @@ func (r *Runtime) arrayproto_reverse(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_shift(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	length := toLength(o.self.getStr("length"))
+	length := toLength(o.self.getStr("length", nil))
 	if length == 0 {
-		o.self.putStr("length", intToValue(0), true)
+		o.self.setOwnStr("length", intToValue(0), true)
 		return _undefined
 	}
-	first := o.self.get(intToValue(0))
+	first := o.self.getIdx(valueInt(0), nil)
 	for i := int64(1); i < length; i++ {
-		v := o.self.get(intToValue(i))
+		v := o.self.getIdx(valueInt(i), nil)
 		if v != nil {
-			o.self.put(intToValue(i-1), v, true)
+			o.self.setOwnIdx(valueInt(i-1), v, true)
 		} else {
-			o.self.delete(intToValue(i-1), true)
+			o.self.deleteIdx(valueInt(i-1), true)
 		}
 	}
 
-	lv := intToValue(length - 1)
-	o.self.delete(lv, true)
-	o.self.putStr("length", lv, true)
+	lv := valueInt(length - 1)
+	o.self.deleteIdx(lv, true)
+	o.self.setOwnStr("length", lv, true)
 
 	return first
 }
@@ -872,7 +872,7 @@ func (r *Runtime) arrayproto_keys(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_copyWithin(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	l := toLength(o.self.getStr("length"))
+	l := toLength(o.self.getStr("length", nil))
 	var relEnd, dir int64
 	to := relToIdx(call.Argument(0).ToInteger(), l)
 	from := relToIdx(call.Argument(1).ToInteger(), l)
@@ -897,10 +897,10 @@ func (r *Runtime) arrayproto_copyWithin(call FunctionCall) Value {
 		dir = 1
 	}
 	for count > 0 {
-		if p := o.self.get(intToValue(from)); p != nil {
-			o.self.put(intToValue(to), p, true)
+		if o.self.hasPropertyIdx(valueInt(from)) {
+			o.self.setOwnIdx(valueInt(to), o.self.getIdx(valueInt(from), nil), true)
 		} else {
-			o.self.delete(intToValue(to), true)
+			o.self.deleteIdx(valueInt(to), true)
 		}
 		from += dir
 		to += dir
@@ -916,7 +916,7 @@ func (r *Runtime) arrayproto_entries(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_fill(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	l := toLength(o.self.getStr("length"))
+	l := toLength(o.self.getStr("length", nil))
 	k := relToIdx(call.Argument(1).ToInteger(), l)
 	var relEnd int64
 	if endArg := call.Argument(2); endArg != _undefined {
@@ -932,7 +932,7 @@ func (r *Runtime) arrayproto_fill(call FunctionCall) Value {
 		}
 	} else {
 		for ; k < final; k++ {
-			o.self.put(intToValue(k), value, true)
+			o.self.setOwnIdx(valueInt(k), value, true)
 		}
 	}
 	return o
@@ -940,15 +940,15 @@ func (r *Runtime) arrayproto_fill(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_find(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	l := toLength(o.self.getStr("length"))
+	l := toLength(o.self.getStr("length", nil))
 	predicate := r.toCallable(call.Argument(0))
 	fc := FunctionCall{
 		This:      call.Argument(1),
 		Arguments: []Value{nil, nil, o},
 	}
 	for k := int64(0); k < l; k++ {
-		idx := intToValue(k)
-		kValue := o.self.get(idx)
+		idx := valueInt(k)
+		kValue := o.self.getIdx(idx, nil)
 		fc.Arguments[0], fc.Arguments[1] = kValue, idx
 		if predicate(fc).ToBoolean() {
 			return kValue
@@ -960,29 +960,29 @@ func (r *Runtime) arrayproto_find(call FunctionCall) Value {
 
 func (r *Runtime) arrayproto_findIndex(call FunctionCall) Value {
 	o := call.This.ToObject(r)
-	l := toLength(o.self.getStr("length"))
+	l := toLength(o.self.getStr("length", nil))
 	predicate := r.toCallable(call.Argument(0))
 	fc := FunctionCall{
 		This:      call.Argument(1),
 		Arguments: []Value{nil, nil, o},
 	}
 	for k := int64(0); k < l; k++ {
-		idx := intToValue(k)
-		kValue := o.self.get(idx)
+		idx := valueInt(k)
+		kValue := o.self.getIdx(idx, nil)
 		fc.Arguments[0], fc.Arguments[1] = kValue, idx
 		if predicate(fc).ToBoolean() {
 			return idx
 		}
 	}
 
-	return intToValue(-1)
+	return valueInt(-1)
 }
 
 func (r *Runtime) checkStdArrayObj(obj *Object) *arrayObject {
 	if arr, ok := obj.self.(*arrayObject); ok &&
 		arr.propValueCount == 0 &&
-		arr.length == int64(len(arr.values)) &&
-		arr.objCount == arr.length {
+		arr.length == uint32(len(arr.values)) &&
+		uint32(arr.objCount) == arr.length {
 
 		return arr
 	}
@@ -1000,7 +1000,7 @@ func (r *Runtime) checkStdArray(v Value) *arrayObject {
 
 func (r *Runtime) checkStdArrayIter(v Value) *arrayObject {
 	if arr := r.checkStdArray(v); arr != nil &&
-		arr.getSym(symIterator) == r.global.arrayValues {
+		arr.getSym(symIterator, nil) == r.global.arrayValues {
 
 		return arr
 	}
@@ -1030,10 +1030,10 @@ func (r *Runtime) array_from(call FunctionCall) Value {
 		}
 	}
 
-	var ctor func(args []Value) *Object
+	var ctor func(args []Value, newTarget *Object) *Object
 	if call.This != r.global.Array {
 		if o, ok := call.This.(*Object); ok {
-			if c := getConstructor(o); c != nil {
+			if c := o.self.assertConstructor(); c != nil {
 				ctor = c
 			}
 		}
@@ -1041,7 +1041,7 @@ func (r *Runtime) array_from(call FunctionCall) Value {
 	var arr *Object
 	if usingIterator := toMethod(r.getV(items, symIterator)); usingIterator != nil {
 		if ctor != nil {
-			arr = ctor([]Value{})
+			arr = ctor([]Value{}, nil)
 		} else {
 			arr = r.newArrayValues(nil)
 		}
@@ -1061,15 +1061,15 @@ func (r *Runtime) array_from(call FunctionCall) Value {
 			if mapFn != nil {
 				val = mapFn(FunctionCall{This: t, Arguments: []Value{val, intToValue(k)}})
 			}
-			defineDataPropertyOrThrow(arr, intToValue(k), val)
+			createDataPropertyOrThrow(arr, intToValue(k), val)
 			k++
 		})
-		arr.self.putStr("length", intToValue(k), true)
+		arr.self.setOwnStr("length", intToValue(k), true)
 	} else {
 		arrayLike := items.ToObject(r)
-		l := toLength(arrayLike.self.getStr("length"))
+		l := toLength(arrayLike.self.getStr("length", nil))
 		if ctor != nil {
-			arr = ctor([]Value{intToValue(l)})
+			arr = ctor([]Value{intToValue(l)}, nil)
 		} else {
 			arr = r.newArrayValues(nil)
 		}
@@ -1077,23 +1077,23 @@ func (r *Runtime) array_from(call FunctionCall) Value {
 			if a := r.checkStdArrayObj(arr); a != nil {
 				values := make([]Value, l)
 				for k := int64(0); k < l; k++ {
-					values[k] = nilSafe(arrayLike.self.get(intToValue(k)))
+					values[k] = nilSafe(arrayLike.self.getIdx(valueInt(k), nil))
 				}
 				setArrayValues(a, values)
 				return arr
 			}
 		}
 		for k := int64(0); k < l; k++ {
-			idx := intToValue(k)
-			item := arrayLike.self.get(idx)
+			idx := valueInt(k)
+			item := arrayLike.self.getIdx(idx, nil)
 			if mapFn != nil {
 				item = mapFn(FunctionCall{This: t, Arguments: []Value{item, idx}})
 			} else {
 				item = nilSafe(item)
 			}
-			defineDataPropertyOrThrow(arr, idx, item)
+			createDataPropertyOrThrow(arr, idx, item)
 		}
-		arr.self.putStr("length", intToValue(l), true)
+		arr.self.setOwnStr("length", intToValue(l), true)
 	}
 
 	return arr
@@ -1109,10 +1109,10 @@ func (r *Runtime) array_isArray(call FunctionCall) Value {
 }
 
 func (r *Runtime) array_of(call FunctionCall) Value {
-	var ctor func(args []Value) *Object
+	var ctor func(args []Value, newTarget *Object) *Object
 	if call.This != r.global.Array {
 		if o, ok := call.This.(*Object); ok {
-			if c := getConstructor(o); c != nil {
+			if c := o.self.assertConstructor(); c != nil {
 				ctor = c
 			}
 		}
@@ -1123,11 +1123,11 @@ func (r *Runtime) array_of(call FunctionCall) Value {
 		return r.newArrayValues(values)
 	}
 	l := intToValue(int64(len(call.Arguments)))
-	arr := ctor([]Value{l})
+	arr := ctor([]Value{l}, nil)
 	for i, val := range call.Arguments {
-		defineDataPropertyOrThrow(arr, intToValue(int64(i)), val)
+		createDataPropertyOrThrow(arr, intToValue(int64(i)), val)
 	}
-	arr.self.putStr("length", l, true)
+	arr.self.setOwnStr("length", l, true)
 	return arr
 }
 
@@ -1182,17 +1182,17 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl {
 	r.global.arrayValues = valuesFunc
 	o._putProp("values", valuesFunc, true, false, true)
 
-	o.put(symIterator, valueProp(valuesFunc, true, false, true), true)
+	o._putSym(symIterator, valueProp(valuesFunc, true, false, true))
 
 	bl := r.NewObject()
-	bl.self.putStr("copyWithin", valueTrue, true)
-	bl.self.putStr("entries", valueTrue, true)
-	bl.self.putStr("fill", valueTrue, true)
-	bl.self.putStr("find", valueTrue, true)
-	bl.self.putStr("findIndex", valueTrue, true)
-	bl.self.putStr("keys", valueTrue, true)
-	bl.self.putStr("values", valueTrue, true)
-	o.put(symUnscopables, valueProp(bl, false, false, true), true)
+	bl.self.setOwnStr("copyWithin", valueTrue, true)
+	bl.self.setOwnStr("entries", valueTrue, true)
+	bl.self.setOwnStr("fill", valueTrue, true)
+	bl.self.setOwnStr("find", valueTrue, true)
+	bl.self.setOwnStr("findIndex", valueTrue, true)
+	bl.self.setOwnStr("keys", valueTrue, true)
+	bl.self.setOwnStr("values", valueTrue, true)
+	o._putSym(symUnscopables, valueProp(bl, false, false, true))
 
 	return o
 }
@@ -1202,11 +1202,11 @@ func (r *Runtime) createArray(val *Object) objectImpl {
 	o._putProp("from", r.newNativeFunc(r.array_from, nil, "from", nil, 1), true, false, true)
 	o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
 	o._putProp("of", r.newNativeFunc(r.array_of, nil, "of", nil, 0), true, false, true)
-	o.putSym(symSpecies, &valueProperty{
+	o._putSym(symSpecies, &valueProperty{
 		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
 		accessor:     true,
 		configurable: true,
-	}, true)
+	})
 
 	return o
 }
@@ -1215,7 +1215,7 @@ func (r *Runtime) createArrayIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject)
 
 	o._putProp("next", r.newNativeFunc(r.arrayIterProto_next, nil, "next", nil, 0), true, false, true)
-	o.put(symToStringTag, valueProp(asciiString(classArrayIterator), false, false, true), true)
+	o._putSym(symToStringTag, valueProp(asciiString(classArrayIterator), false, false, true))
 
 	return o
 }

+ 15 - 13
builtin_date.go

@@ -26,8 +26,8 @@ func (r *Runtime) makeDate(args []Value, loc *time.Location) (t time.Time, valid
 			return default_, true
 		}
 		value := args[index]
-		if valueInt, ok := value.assertInt(); ok {
-			return valueInt, true
+		if valueInt, ok := value.(valueInt); ok {
+			return int64(valueInt), true
 		}
 		valueFloat := value.ToFloat()
 		if math.IsNaN(valueFloat) || math.IsInf(valueFloat, 0) {
@@ -71,14 +71,15 @@ func (r *Runtime) makeDate(args []Value, loc *time.Location) (t time.Time, valid
 		valid = true
 	default: // one argument
 		pv := toPrimitiveNumber(args[0])
-		if val, ok := pv.assertString(); ok {
+		if val, ok := pv.(valueString); ok {
 			return dateParse(val.String())
 		}
 
 		var n int64
-		if i, ok := pv.assertInt(); ok {
-			n = i
-		} else if f, ok := pv.assertFloat(); ok {
+		if i, ok := pv.(valueInt); ok {
+			n = int64(i)
+		} else if f, ok := pv.(valueFloat); ok {
+			f := float64(f)
 			if math.IsNaN(f) || math.IsInf(f, 0) {
 				return
 			}
@@ -102,13 +103,13 @@ func (r *Runtime) makeDate(args []Value, loc *time.Location) (t time.Time, valid
 	return
 }
 
-func (r *Runtime) newDateTime(args []Value, loc *time.Location) *Object {
+func (r *Runtime) newDateTime(args []Value, loc *time.Location, proto *Object) *Object {
 	t, isSet := r.makeDate(args, loc)
-	return r.newDateObject(t, isSet)
+	return r.newDateObject(t, isSet, proto)
 }
 
-func (r *Runtime) builtin_newDate(args []Value) *Object {
-	return r.newDateTime(args, time.Local)
+func (r *Runtime) builtin_newDate(args []Value, proto *Object) *Object {
+	return r.newDateTime(args, time.Local, proto)
 }
 
 func (r *Runtime) builtin_date(FunctionCall) Value {
@@ -183,15 +184,16 @@ func (r *Runtime) dateproto_toISOString(call FunctionCall) Value {
 func (r *Runtime) dateproto_toJSON(call FunctionCall) Value {
 	obj := r.toObject(call.This)
 	tv := obj.self.toPrimitiveNumber()
-	if f, ok := tv.assertFloat(); ok {
+	if f, ok := tv.(valueFloat); ok {
+		f := float64(f)
 		if math.IsNaN(f) || math.IsInf(f, 0) {
 			return _null
 		}
-	} else if _, ok := tv.assertInt(); !ok {
+	} else if _, ok := tv.(valueInt); !ok {
 		return _null
 	}
 
-	if toISO, ok := obj.self.getStr("toISOString").(*Object); ok {
+	if toISO, ok := obj.self.getStr("toISOString", nil).(*Object); ok {
 		if toISO, ok := toISO.self.assertCallable(); ok {
 			return toISO(FunctionCall{
 				This: obj,

+ 0 - 1
builtin_error.go

@@ -8,7 +8,6 @@ func (r *Runtime) initErrors() {
 	o._putProp("toString", r.newNativeFunc(r.error_toString, nil, "toString", nil, 0), true, false, true)
 
 	r.global.Error = r.newNativeFuncConstruct(r.builtin_Error, "Error", r.global.ErrorPrototype, 1)
-	o = r.global.Error.self
 	r.addToGlobal("Error", r.global.Error)
 
 	r.global.TypeErrorPrototype = r.builtin_new(r.global.Error, []Value{})

+ 42 - 35
builtin_function.go

@@ -18,7 +18,9 @@ func (r *Runtime) builtin_Function(args []Value, proto *Object) *Object {
 	}
 	src += "){" + body + "})"
 
-	return r.toObject(r.eval(src, false, false, _undefined))
+	ret := r.toObject(r.eval(src, false, false, _undefined))
+	ret.self.setProto(proto, true)
+	return ret
 }
 
 func (r *Runtime) functionproto_toString(call FunctionCall) Value {
@@ -34,28 +36,49 @@ repeat:
 	case *lazyObject:
 		obj.self = f.create(obj)
 		goto repeat
+	case *proxyObject:
+		var name string
+	repeat2:
+		switch c := f.target.self.(type) {
+		case *funcObject:
+			name = c.src
+		case *nativeFuncObject:
+			name = c.nameProp.get(call.This).String()
+		case *boundFuncObject:
+			name = c.nameProp.get(call.This).String()
+		case *lazyObject:
+			f.target.self = c.create(obj)
+			goto repeat2
+		default:
+			name = f.target.String()
+		}
+		return newStringValue(fmt.Sprintf("function proxy() { [%s] }", name))
 	}
 
 	r.typeErrorResult(true, "Object is not a function")
 	return nil
 }
 
-func (r *Runtime) toValueArray(a Value) []Value {
-	obj := r.toObject(a)
-	l := toUInt32(obj.self.getStr("length"))
-	ret := make([]Value, l)
-	for i := uint32(0); i < l; i++ {
-		ret[i] = obj.self.get(valueInt(i))
+func (r *Runtime) createListFromArrayLike(a Value) []Value {
+	o := r.toObject(a)
+	if arr := r.checkStdArrayObj(o); arr != nil {
+		return arr.values
 	}
-	return ret
+	l := toLength(o.self.getStr("length", nil))
+	res := make([]Value, 0, l)
+	for k := int64(0); k < l; k++ {
+		res = append(res, o.self.getIdx(valueInt(k), nil))
+	}
+	return res
 }
 
 func (r *Runtime) functionproto_apply(call FunctionCall) Value {
-	f := r.toCallable(call.This)
 	var args []Value
 	if len(call.Arguments) >= 2 {
-		args = r.toValueArray(call.Arguments[1])
+		args = r.createListFromArrayLike(call.Arguments[1])
 	}
+
+	f := r.toCallable(call.This)
 	return f(FunctionCall{
 		This:      call.Argument(0),
 		Arguments: args,
@@ -63,11 +86,12 @@ func (r *Runtime) functionproto_apply(call FunctionCall) Value {
 }
 
 func (r *Runtime) functionproto_call(call FunctionCall) Value {
-	f := r.toCallable(call.This)
 	var args []Value
 	if len(call.Arguments) > 0 {
 		args = call.Arguments[1:]
 	}
+
+	f := r.toCallable(call.This)
 	return f(FunctionCall{
 		This:      call.Argument(0),
 		Arguments: args,
@@ -93,7 +117,7 @@ func (r *Runtime) boundCallable(target func(FunctionCall) Value, boundArgs []Val
 	}
 }
 
-func (r *Runtime) boundConstruct(target func([]Value) *Object, boundArgs []Value) func([]Value) *Object {
+func (r *Runtime) boundConstruct(target func([]Value, *Object) *Object, boundArgs []Value) func([]Value, *Object) *Object {
 	if target == nil {
 		return nil
 	}
@@ -102,37 +126,20 @@ func (r *Runtime) boundConstruct(target func([]Value) *Object, boundArgs []Value
 		args = make([]Value, len(boundArgs)-1)
 		copy(args, boundArgs[1:])
 	}
-	return func(fargs []Value) *Object {
+	return func(fargs []Value, newTarget *Object) *Object {
 		a := append(args, fargs...)
 		copy(a, args)
-		return target(a)
+		return target(a, newTarget)
 	}
 }
 
 func (r *Runtime) functionproto_bind(call FunctionCall) Value {
 	obj := r.toObject(call.This)
-	f := obj.self
-	var fcall func(FunctionCall) Value
-	var construct func([]Value) *Object
-repeat:
-	switch ff := f.(type) {
-	case *funcObject:
-		fcall = ff.Call
-		construct = ff.construct
-	case *nativeFuncObject:
-		fcall = ff.f
-		construct = ff.construct
-	case *boundFuncObject:
-		f = &ff.nativeFuncObject
-		goto repeat
-	case *lazyObject:
-		f = ff.create(obj)
-		goto repeat
-	default:
-		r.typeErrorResult(true, "Value is not callable: %s", obj.toString())
-	}
 
-	l := int(toUInt32(obj.self.getStr("length")))
+	fcall := r.toCallable(call.This)
+	construct := obj.self.assertConstructor()
+
+	l := int(toUInt32(obj.self.getStr("length", nil)))
 	l -= len(call.Arguments) - 1
 	if l < 0 {
 		l = 0

+ 27 - 51
builtin_json.go

@@ -31,7 +31,7 @@ func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
 
 	if reviver != nil {
 		root := r.NewObject()
-		root.self.putStr("", value, false)
+		root.self.setOwnStr("", value, false)
 		return r.builtinJSON_reviveWalk(reviver, root, stringEmpty)
 	}
 
@@ -85,17 +85,7 @@ func (r *Runtime) builtinJSON_decodeObject(d *json.Decoder) (*Object, error) {
 			return nil, err
 		}
 
-		if key == __proto__ {
-			descr := propertyDescr{
-				Value:        value,
-				Writable:     FLAG_TRUE,
-				Enumerable:   FLAG_TRUE,
-				Configurable: FLAG_TRUE,
-			}
-			object.self.defineOwnProperty(string__proto__, descr, false)
-		} else {
-			object.self.putStr(key, value, false)
-		}
+		object.self._putProp(key, value, true, true, true)
 	}
 	return object, nil
 }
@@ -138,40 +128,31 @@ func (r *Runtime) builtinJSON_decodeArray(d *json.Decoder) (*Object, error) {
 	return r.newArrayValues(arrayValue), nil
 }
 
-func isArray(object *Object) bool {
-	switch object.self.className() {
-	case classArray:
-		return true
-	default:
-		return false
-	}
-}
-
 func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holder *Object, name Value) Value {
-	value := holder.self.get(name)
+	value := holder.get(name, nil)
 	if value == nil {
 		value = _undefined
 	}
 
 	if object := value.(*Object); object != nil {
 		if isArray(object) {
-			length := object.self.getStr("length").ToInteger()
+			length := object.self.getStr("length", nil).ToInteger()
 			for index := int64(0); index < length; index++ {
 				name := intToValue(index)
 				value := r.builtinJSON_reviveWalk(reviver, object, name)
 				if value == _undefined {
-					object.self.delete(name, false)
+					object.delete(name, false)
 				} else {
-					object.self.put(name, value, false)
+					object.setOwn(name, value, false)
 				}
 			}
 		} else {
-			for item, f := object.self.enumerate(false, false)(); f != nil; item, f = f() {
+			for _, itemName := range object.self.ownKeys(false, nil) {
 				value := r.builtinJSON_reviveWalk(reviver, object, name)
 				if value == _undefined {
-					object.self.deleteStr(item.name, false)
+					object.self.deleteStr(itemName.String(), false)
 				} else {
-					object.self.putStr(item.name, value, false)
+					object.self.setOwnStr(itemName.String(), value, false)
 				}
 			}
 		}
@@ -199,21 +180,18 @@ func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
 	replacer, _ := call.Argument(1).(*Object)
 	if replacer != nil {
 		if isArray(replacer) {
-			length := replacer.self.getStr("length").ToInteger()
+			length := replacer.self.getStr("length", nil).ToInteger()
 			seen := map[string]bool{}
 			propertyList := make([]Value, length)
 			length = 0
 			for index := range propertyList {
 				var name string
-				value := replacer.self.get(intToValue(int64(index)))
-				if s, ok := value.assertString(); ok {
-					name = s.String()
-				} else if _, ok := value.assertInt(); ok {
-					name = value.String()
-				} else if _, ok := value.assertFloat(); ok {
+				value := replacer.self.getIdx(valueInt(int64(index)), nil)
+				switch v := value.(type) {
+				case valueFloat, valueInt, valueString:
 					name = value.String()
-				} else if o, ok := value.(*Object); ok {
-					switch o.self.className() {
+				case *Object:
+					switch v.self.className() {
 					case classNumber, classString:
 						name = value.String()
 					}
@@ -241,12 +219,12 @@ func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
 		}
 		isNum := false
 		var num int64
-		num, isNum = spaceValue.assertInt()
-		if !isNum {
-			if f, ok := spaceValue.assertFloat(); ok {
-				num = int64(f)
-				isNum = true
-			}
+		if i, ok := spaceValue.(valueInt); ok {
+			num = int64(i)
+			isNum = true
+		} else if f, ok := spaceValue.(valueFloat); ok {
+			num = int64(f)
+			isNum = true
 		}
 		if isNum {
 			if num > 0 {
@@ -256,7 +234,7 @@ func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
 				ctx.gap = strings.Repeat(" ", int(num))
 			}
 		} else {
-			if s, ok := spaceValue.assertString(); ok {
+			if s, ok := spaceValue.(valueString); ok {
 				str := s.String()
 				if len(str) > 10 {
 					ctx.gap = str[:10]
@@ -275,18 +253,18 @@ func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
 
 func (ctx *_builtinJSON_stringifyContext) do(v Value) bool {
 	holder := ctx.r.NewObject()
-	holder.self.putStr("", v, false)
+	holder.self.setOwnStr("", v, false)
 	return ctx.str(stringEmpty, holder)
 }
 
 func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool {
-	value := holder.self.get(key)
+	value := holder.get(key, nil)
 	if value == nil {
 		value = _undefined
 	}
 
 	if object, ok := value.(*Object); ok {
-		if toJSON, ok := object.self.getStr("toJSON").(*Object); ok {
+		if toJSON, ok := object.self.getStr("toJSON", nil).(*Object); ok {
 			if c, ok := toJSON.self.assertCallable(); ok {
 				value = c(FunctionCall{
 					This:      value,
@@ -384,7 +362,7 @@ func (ctx *_builtinJSON_stringifyContext) ja(array *Object) {
 		stepback = ctx.indent
 		ctx.indent += ctx.gap
 	}
-	length := array.self.getStr("length").ToInteger()
+	length := array.self.getStr("length", nil).ToInteger()
 	if length == 0 {
 		ctx.buf.WriteString("[]")
 		return
@@ -436,9 +414,7 @@ func (ctx *_builtinJSON_stringifyContext) jo(object *Object) {
 
 	var props []Value
 	if ctx.propertyList == nil {
-		for item, f := object.self.enumerate(false, false)(); f != nil; item, f = f() {
-			props = append(props, newStringValue(item.name))
-		}
+		props = append(props, object.self.ownKeys(false, nil)...)
 	} else {
 		props = ctx.propertyList
 	}

+ 15 - 15
builtin_map.go

@@ -138,7 +138,7 @@ func (r *Runtime) mapProto_getSize(call FunctionCall) Value {
 	return intToValue(int64(mo.m.size))
 }
 
-func (r *Runtime) builtin_newMap(args []Value) *Object {
+func (r *Runtime) builtin_newMap(args []Value, proto *Object) *Object {
 	o := &Object{runtime: r}
 
 	mo := &mapObject{}
@@ -146,19 +146,19 @@ func (r *Runtime) builtin_newMap(args []Value) *Object {
 	mo.val = o
 	mo.extensible = true
 	o.self = mo
-	mo.prototype = r.global.MapPrototype
+	mo.prototype = proto
 	mo.init()
 	if len(args) > 0 {
 		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
-			adder := mo.getStr("set")
+			adder := mo.getStr("set", nil)
 			iter := r.getIterator(arg, nil)
-			i0 := intToValue(0)
-			i1 := intToValue(1)
+			i0 := valueInt(0)
+			i1 := valueInt(1)
 			if adder == r.global.mapAdder {
 				r.iterate(iter, func(item Value) {
 					itemObj := r.toObject(item)
-					k := nilSafe(itemObj.self.get(i0))
-					v := nilSafe(itemObj.self.get(i1))
+					k := nilSafe(itemObj.self.getIdx(i0, nil))
+					v := nilSafe(itemObj.self.getIdx(i1, nil))
 					mo.m.set(k, v)
 				})
 			} else {
@@ -168,8 +168,8 @@ func (r *Runtime) builtin_newMap(args []Value) *Object {
 				}
 				r.iterate(iter, func(item Value) {
 					itemObj := r.toObject(item)
-					k := itemObj.self.get(i0)
-					v := itemObj.self.get(i1)
+					k := itemObj.self.getIdx(i0, nil)
+					v := itemObj.self.getIdx(i1, nil)
 					adderFn(FunctionCall{This: o, Arguments: []Value{k, v}})
 				})
 			}
@@ -220,7 +220,7 @@ func (r *Runtime) createMapProto(val *Object) objectImpl {
 	o._putProp("forEach", r.newNativeFunc(r.mapProto_forEach, nil, "forEach", nil, 1), true, false, true)
 	o._putProp("has", r.newNativeFunc(r.mapProto_has, nil, "has", nil, 1), true, false, true)
 	o._putProp("get", r.newNativeFunc(r.mapProto_get, nil, "get", nil, 1), true, false, true)
-	o.putStr("size", &valueProperty{
+	o.setOwnStr("size", &valueProperty{
 		getterFunc:   r.newNativeFunc(r.mapProto_getSize, nil, "get size", nil, 0),
 		accessor:     true,
 		writable:     true,
@@ -231,19 +231,19 @@ func (r *Runtime) createMapProto(val *Object) objectImpl {
 
 	entriesFunc := r.newNativeFunc(r.mapProto_entries, nil, "entries", nil, 0)
 	o._putProp("entries", entriesFunc, true, false, true)
-	o.put(symIterator, valueProp(entriesFunc, true, false, true), true)
-	o.put(symToStringTag, valueProp(asciiString(classMap), false, false, true), true)
+	o._putSym(symIterator, valueProp(entriesFunc, true, false, true))
+	o._putSym(symToStringTag, valueProp(asciiString(classMap), false, false, true))
 
 	return o
 }
 
 func (r *Runtime) createMap(val *Object) objectImpl {
 	o := r.newNativeFuncObj(val, r.constructorThrower("Map"), r.builtin_newMap, "Map", r.global.MapPrototype, 0)
-	o.putSym(symSpecies, &valueProperty{
+	o._putSym(symSpecies, &valueProperty{
 		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
 		accessor:     true,
 		configurable: true,
-	}, true)
+	})
 
 	return o
 }
@@ -252,7 +252,7 @@ func (r *Runtime) createMapIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject)
 
 	o._putProp("next", r.newNativeFunc(r.mapIterProto_next, nil, "next", nil, 0), true, false, true)
-	o.put(symToStringTag, valueProp(asciiString(classMapIterator), false, false, true), true)
+	o._putSym(symToStringTag, valueProp(asciiString(classMapIterator), false, false, true))
 
 	return o
 }

+ 3 - 3
builtin_math.go

@@ -88,15 +88,15 @@ func (r *Runtime) math_min(call FunctionCall) Value {
 func (r *Runtime) math_pow(call FunctionCall) Value {
 	x := call.Argument(0)
 	y := call.Argument(1)
-	if x, ok := x.assertInt(); ok {
-		if y, ok := y.assertInt(); ok && y >= 0 && y < 64 {
+	if x, ok := x.(valueInt); ok {
+		if y, ok := y.(valueInt); ok && y >= 0 && y < 64 {
 			if y == 0 {
 				return intToValue(1)
 			}
 			if x == 0 {
 				return intToValue(0)
 			}
-			ip := ipow(x, y)
+			ip := ipow(int64(x), int64(y))
 			if ip != 0 {
 				return intToValue(ip)
 			}

+ 5 - 11
builtin_number.go

@@ -10,22 +10,16 @@ func (r *Runtime) numberproto_valueOf(call FunctionCall) Value {
 	if !isNumber(this) {
 		r.typeErrorResult(true, "Value is not a number")
 	}
-	if _, ok := this.assertInt(); ok {
+	switch t := this.(type) {
+	case valueInt, valueFloat:
 		return this
-	}
-
-	if _, ok := this.assertFloat(); ok {
-		return this
-	}
-
-	if obj, ok := this.(*Object); ok {
-		if v, ok := obj.self.(*primitiveValueObject); ok {
+	case *Object:
+		if v, ok := t.self.(*primitiveValueObject); ok {
 			return v.pValue
 		}
 	}
 
-	r.typeErrorResult(true, "Number.prototype.valueOf is not generic")
-	return nil
+	panic(r.NewTypeError("Number.prototype.valueOf is not generic"))
 }
 
 func isNumber(v Value) bool {

+ 151 - 145
builtin_object.go

@@ -23,10 +23,7 @@ func (r *Runtime) object_getPrototypeOf(call FunctionCall) Value {
 	return p
 }
 
-func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value {
-	obj := call.Argument(0).ToObject(r)
-	propName := toPropertyKey(call.Argument(1))
-	desc := obj.self.getOwnProp(propName)
+func (r *Runtime) valuePropToDescriptorObject(desc Value) Value {
 	if desc == nil {
 		return _undefined
 	}
@@ -49,63 +46,114 @@ func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value {
 	}
 
 	ret := r.NewObject()
-	o := ret.self
+	obj := ret.self
 	if !accessor {
-		o.putStr("value", value, false)
-		o.putStr("writable", r.toBoolean(writable), false)
+		obj.setOwnStr("value", value, false)
+		obj.setOwnStr("writable", r.toBoolean(writable), false)
 	} else {
 		if get != nil {
-			o.putStr("get", get, false)
+			obj.setOwnStr("get", get, false)
 		} else {
-			o.putStr("get", _undefined, false)
+			obj.setOwnStr("get", _undefined, false)
 		}
 		if set != nil {
-			o.putStr("set", set, false)
+			obj.setOwnStr("set", set, false)
 		} else {
-			o.putStr("set", _undefined, false)
+			obj.setOwnStr("set", _undefined, false)
 		}
 	}
-	o.putStr("enumerable", r.toBoolean(enumerable), false)
-	o.putStr("configurable", r.toBoolean(configurable), false)
+	obj.setOwnStr("enumerable", r.toBoolean(enumerable), false)
+	obj.setOwnStr("configurable", r.toBoolean(configurable), false)
 
 	return ret
 }
 
+func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value {
+	o := call.Argument(0).ToObject(r)
+	propName := toPropertyKey(call.Argument(1))
+	return r.valuePropToDescriptorObject(o.getOwnProp(propName))
+}
+
 func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value {
-	// ES6
 	obj := call.Argument(0).ToObject(r)
-	// obj := r.toObject(call.Argument(0))
 
-	var values []Value
-	for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-		values = append(values, newStringValue(item.name))
-	}
-	return r.newArrayValues(values)
+	return r.newArrayValues(obj.self.ownKeys(true, nil))
 }
 
 func (r *Runtime) object_getOwnPropertySymbols(call FunctionCall) Value {
 	obj := call.Argument(0).ToObject(r)
-	return r.newArrayValues(obj.self.getOwnSymbols())
+	return r.newArrayValues(obj.self.ownSymbols())
+}
+
+func (r *Runtime) toValueProp(v Value) *valueProperty {
+	if v == nil || v == _undefined {
+		return nil
+	}
+	obj := r.toObject(v)
+	getter := obj.self.getStr("get", nil)
+	setter := obj.self.getStr("set", nil)
+	writable := obj.self.getStr("writable", nil)
+	value := obj.self.getStr("value", nil)
+	if (getter != nil || setter != nil) && (value != nil || writable != nil) {
+		r.typeErrorResult(true, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute")
+	}
+
+	ret := &valueProperty{}
+	if writable != nil && writable.ToBoolean() {
+		ret.writable = true
+	}
+	if e := obj.self.getStr("enumerable", nil); e != nil && e.ToBoolean() {
+		ret.enumerable = true
+	}
+	if c := obj.self.getStr("configurable", nil); c != nil && c.ToBoolean() {
+		ret.configurable = true
+	}
+	ret.value = value
+
+	if getter != nil && getter != _undefined {
+		o := r.toObject(getter)
+		if _, ok := o.self.assertCallable(); !ok {
+			r.typeErrorResult(true, "getter must be a function")
+		}
+		ret.getterFunc = o
+	}
+
+	if setter != nil && setter != _undefined {
+		o := r.toObject(v)
+		if _, ok := o.self.assertCallable(); !ok {
+			r.typeErrorResult(true, "setter must be a function")
+		}
+		ret.setterFunc = o
+	}
+
+	if ret.getterFunc != nil || ret.setterFunc != nil {
+		ret.accessor = true
+	}
+
+	return ret
 }
 
-func (r *Runtime) toPropertyDescr(v Value) (ret propertyDescr) {
+func (r *Runtime) toPropertyDescriptor(v Value) (ret PropertyDescriptor) {
 	if o, ok := v.(*Object); ok {
 		descr := o.self
 
-		ret.Value = descr.getStr("value")
+		// Save the original descriptor for reference
+		ret.jsDescriptor = o
+
+		ret.Value = descr.getStr("value", nil)
 
-		if p := descr.getStr("writable"); p != nil {
+		if p := descr.getStr("writable", nil); p != nil {
 			ret.Writable = ToFlag(p.ToBoolean())
 		}
-		if p := descr.getStr("enumerable"); p != nil {
+		if p := descr.getStr("enumerable", nil); p != nil {
 			ret.Enumerable = ToFlag(p.ToBoolean())
 		}
-		if p := descr.getStr("configurable"); p != nil {
+		if p := descr.getStr("configurable", nil); p != nil {
 			ret.Configurable = ToFlag(p.ToBoolean())
 		}
 
-		ret.Getter = descr.getStr("get")
-		ret.Setter = descr.getStr("set")
+		ret.Getter = descr.getStr("get", nil)
+		ret.Setter = descr.getStr("set", nil)
 
 		if ret.Getter != nil && ret.Getter != _undefined {
 			if _, ok := r.toObject(ret.Getter).self.assertCallable(); !ok {
@@ -121,7 +169,6 @@ func (r *Runtime) toPropertyDescr(v Value) (ret propertyDescr) {
 
 		if (ret.Getter != nil || ret.Setter != nil) && (ret.Value != nil || ret.Writable != FLAG_NOT_SET) {
 			r.typeErrorResult(true, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute")
-			return
 		}
 	} else {
 		r.typeErrorResult(true, "Property description must be an object: %s", v.String())
@@ -133,18 +180,20 @@ func (r *Runtime) toPropertyDescr(v Value) (ret propertyDescr) {
 func (r *Runtime) _defineProperties(o *Object, p Value) {
 	type propItem struct {
 		name string
-		prop propertyDescr
+		prop PropertyDescriptor
 	}
 	props := p.ToObject(r)
-	var list []propItem
-	for item, f := props.self.enumerate(false, false)(); f != nil; item, f = f() {
+	names := props.self.ownKeys(false, nil)
+	list := make([]propItem, 0, len(names))
+	for _, itemName := range names {
+		itemNameStr := itemName.String()
 		list = append(list, propItem{
-			name: item.name,
-			prop: r.toPropertyDescr(props.self.getStr(item.name)),
+			name: itemNameStr,
+			prop: r.toPropertyDescriptor(props.self.getStr(itemNameStr, nil)),
 		})
 	}
 	for _, prop := range list {
-		o.self.defineOwnProperty(newStringValue(prop.name), prop.prop, true)
+		o.self.defineOwnPropertyStr(prop.name, prop.prop, true)
 	}
 }
 
@@ -168,8 +217,8 @@ func (r *Runtime) object_create(call FunctionCall) Value {
 
 func (r *Runtime) object_defineProperty(call FunctionCall) (ret Value) {
 	if obj, ok := call.Argument(0).(*Object); ok {
-		descr := r.toPropertyDescr(call.Argument(2))
-		obj.self.defineOwnProperty(call.Argument(1), descr, true)
+		descr := r.toPropertyDescriptor(call.Argument(2))
+		obj.defineOwnProperty(toPropertyKey(call.Argument(1)), descr, true)
 		ret = call.Argument(0)
 	} else {
 		r.typeErrorResult(true, "Object.defineProperty called on non-object")
@@ -187,26 +236,13 @@ func (r *Runtime) object_seal(call FunctionCall) Value {
 	// ES6
 	arg := call.Argument(0)
 	if obj, ok := arg.(*Object); ok {
-		descr := propertyDescr{
+		descr := PropertyDescriptor{
 			Writable:     FLAG_TRUE,
 			Enumerable:   FLAG_TRUE,
 			Configurable: FLAG_FALSE,
 		}
-		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-			v := obj.self.getOwnPropStr(item.name)
-			if prop, ok := v.(*valueProperty); ok {
-				if !prop.configurable {
-					continue
-				}
-				prop.configurable = false
-			} else {
-				descr.Value = v
-				obj.self.defineOwnProperty(newStringValue(item.name), descr, true)
-				//obj.self._putProp(item.name, v, true, true, false)
-			}
-		}
-		for _, sym := range obj.self.getOwnSymbols() {
-			v := obj.self.getOwnProp(sym)
+		for _, key := range obj.self.ownPropertyKeys(true, nil) {
+			v := obj.getOwnProp(key)
 			if prop, ok := v.(*valueProperty); ok {
 				if !prop.configurable {
 					continue
@@ -214,10 +250,10 @@ func (r *Runtime) object_seal(call FunctionCall) Value {
 				prop.configurable = false
 			} else {
 				descr.Value = v
-				obj.self.defineOwnProperty(sym, descr, true)
+				obj.defineOwnProperty(key, descr, true)
 			}
 		}
-		obj.self.preventExtensions()
+		obj.self.preventExtensions(false)
 		return obj
 	}
 	return arg
@@ -226,13 +262,13 @@ func (r *Runtime) object_seal(call FunctionCall) Value {
 func (r *Runtime) object_freeze(call FunctionCall) Value {
 	arg := call.Argument(0)
 	if obj, ok := arg.(*Object); ok {
-		descr := propertyDescr{
+		descr := PropertyDescriptor{
 			Writable:     FLAG_FALSE,
 			Enumerable:   FLAG_TRUE,
 			Configurable: FLAG_FALSE,
 		}
-		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-			v := obj.self.getOwnPropStr(item.name)
+		for _, key := range obj.self.ownPropertyKeys(true, nil) {
+			v := obj.getOwnProp(key)
 			if prop, ok := v.(*valueProperty); ok {
 				prop.configurable = false
 				if prop.value != nil {
@@ -240,22 +276,10 @@ func (r *Runtime) object_freeze(call FunctionCall) Value {
 				}
 			} else {
 				descr.Value = v
-				obj.self.defineOwnProperty(newStringValue(item.name), descr, true)
+				obj.defineOwnProperty(key, descr, true)
 			}
 		}
-		for _, sym := range obj.self.getOwnSymbols() {
-			v := obj.self.getOwnProp(sym)
-			if prop, ok := v.(*valueProperty); ok {
-				prop.configurable = false
-				if prop.value != nil {
-					prop.writable = false
-				}
-			} else {
-				descr.Value = v
-				obj.self.defineOwnProperty(sym, descr, true)
-			}
-		}
-		obj.self.preventExtensions()
+		obj.self.preventExtensions(false)
 		return obj
 	} else {
 		// ES6 behavior
@@ -266,7 +290,7 @@ func (r *Runtime) object_freeze(call FunctionCall) Value {
 func (r *Runtime) object_preventExtensions(call FunctionCall) (ret Value) {
 	arg := call.Argument(0)
 	if obj, ok := arg.(*Object); ok {
-		obj.self.preventExtensions()
+		obj.self.preventExtensions(false)
 		return obj
 	}
 	// ES6
@@ -280,8 +304,8 @@ func (r *Runtime) object_isSealed(call FunctionCall) Value {
 		if obj.self.isExtensible() {
 			return valueFalse
 		}
-		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-			prop := obj.self.getOwnPropStr(item.name)
+		for _, key := range obj.self.ownPropertyKeys(true, nil) {
+			prop := obj.getOwnProp(key)
 			if prop, ok := prop.(*valueProperty); ok {
 				if prop.configurable {
 					return valueFalse
@@ -290,20 +314,6 @@ func (r *Runtime) object_isSealed(call FunctionCall) Value {
 				return valueFalse
 			}
 		}
-		for _, sym := range obj.self.getOwnSymbols() {
-			prop := obj.self.getOwnProp(sym)
-			if prop, ok := prop.(*valueProperty); ok {
-				if prop.configurable {
-					return valueFalse
-				}
-			} else {
-				return valueFalse
-			}
-		}
-	} else {
-		// ES6
-		//r.typeErrorResult(true, "Object.isSealed called on non-object")
-		return valueTrue
 	}
 	return valueTrue
 }
@@ -313,8 +323,8 @@ func (r *Runtime) object_isFrozen(call FunctionCall) Value {
 		if obj.self.isExtensible() {
 			return valueFalse
 		}
-		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
-			prop := obj.self.getOwnPropStr(item.name)
+		for _, key := range obj.self.ownPropertyKeys(true, nil) {
+			prop := obj.getOwnProp(key)
 			if prop, ok := prop.(*valueProperty); ok {
 				if prop.configurable || prop.value != nil && prop.writable {
 					return valueFalse
@@ -323,20 +333,6 @@ func (r *Runtime) object_isFrozen(call FunctionCall) Value {
 				return valueFalse
 			}
 		}
-		for _, sym := range obj.self.getOwnSymbols() {
-			prop := obj.self.getOwnProp(sym)
-			if prop, ok := prop.(*valueProperty); ok {
-				if prop.configurable || prop.value != nil && prop.writable {
-					return valueFalse
-				}
-			} else {
-				return valueFalse
-			}
-		}
-	} else {
-		// ES6
-		//r.typeErrorResult(true, "Object.isFrozen called on non-object")
-		return valueTrue
 	}
 	return valueTrue
 }
@@ -355,24 +351,15 @@ func (r *Runtime) object_isExtensible(call FunctionCall) Value {
 }
 
 func (r *Runtime) object_keys(call FunctionCall) Value {
-	// ES6
 	obj := call.Argument(0).ToObject(r)
-	//if obj, ok := call.Argument(0).(*valueObject); ok {
-	var keys []Value
-	for item, f := obj.self.enumerate(false, false)(); f != nil; item, f = f() {
-		keys = append(keys, newStringValue(item.name))
-	}
-	return r.newArrayValues(keys)
-	//} else {
-	//	r.typeErrorResult(true, "Object.keys called on non-object")
-	//}
-	//return nil
+
+	return r.newArrayValues(obj.self.ownKeys(false, nil))
 }
 
 func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value {
 	p := toPropertyKey(call.Argument(0))
 	o := call.This.ToObject(r)
-	if o.self.hasOwnProperty(p) {
+	if o.hasOwnProperty(p) {
 		return valueTrue
 	} else {
 		return valueFalse
@@ -398,7 +385,7 @@ func (r *Runtime) objectproto_isPrototypeOf(call FunctionCall) Value {
 func (r *Runtime) objectproto_propertyIsEnumerable(call FunctionCall) Value {
 	p := toPropertyKey(call.Argument(0))
 	o := call.This.ToObject(r)
-	pv := o.self.getOwnProp(p)
+	pv := o.getOwnProp(p)
 	if pv == nil {
 		return valueFalse
 	}
@@ -419,14 +406,16 @@ func (r *Runtime) objectproto_toString(call FunctionCall) Value {
 	default:
 		obj := o.ToObject(r)
 		var clsName string
-		if tag := obj.self.get(symToStringTag); tag != nil {
-			if str, ok := tag.assertString(); ok {
+		if isArray(obj) {
+			clsName = classArray
+		} else {
+			clsName = obj.self.className()
+		}
+		if tag := obj.self.getSym(symToStringTag, nil); tag != nil {
+			if str, ok := tag.(valueString); ok {
 				clsName = str.String()
 			}
 		}
-		if clsName == "" {
-			clsName = obj.self.className()
-		}
 		return newStringValue(fmt.Sprintf("[object %s]", clsName))
 	}
 }
@@ -436,6 +425,25 @@ func (r *Runtime) objectproto_toLocaleString(call FunctionCall) Value {
 	return toString(FunctionCall{This: call.This})
 }
 
+func (r *Runtime) objectproto_getProto(call FunctionCall) Value {
+	proto := call.This.ToObject(r).self.proto()
+	if proto != nil {
+		return proto
+	}
+	return _null
+}
+
+func (r *Runtime) objectproto_setProto(call FunctionCall) Value {
+	o := call.This
+	r.checkObjectCoercible(o)
+	proto := r.toProto(call.Argument(0))
+	if o, ok := o.(*Object); ok {
+		o.self.setProto(proto, true)
+	}
+
+	return _undefined
+}
+
 func (r *Runtime) objectproto_valueOf(call FunctionCall) Value {
 	return call.This.ToObject(r)
 }
@@ -446,23 +454,15 @@ func (r *Runtime) object_assign(call FunctionCall) Value {
 		for _, arg := range call.Arguments[1:] {
 			if arg != _undefined && arg != _null {
 				source := arg.ToObject(r)
-				for item, f := source.self.enumerate(false, false)(); f != nil; item, f = f() {
-					p := source.self.getOwnPropStr(item.name)
-					if v, ok := p.(*valueProperty); ok {
-						p = v.get(source)
+				for _, key := range source.self.ownPropertyKeys(false, nil) {
+					p := source.getOwnProp(key)
+					if p == nil {
+						continue
 					}
-					to.self.putStr(item.name, p, true)
-				}
-
-				for _, sym := range source.self.getOwnSymbols() {
-					p := source.self.getOwnProp(sym)
 					if v, ok := p.(*valueProperty); ok {
-						if !v.enumerable {
-							continue
-						}
 						p = v.get(source)
 					}
-					to.self.put(sym, p, true)
+					to.setOwn(key, p, true)
 				}
 			}
 		}
@@ -475,22 +475,23 @@ func (r *Runtime) object_is(call FunctionCall) Value {
 	return r.toBoolean(call.Argument(0).SameAs(call.Argument(1)))
 }
 
-func (r *Runtime) object_setPrototypeOf(call FunctionCall) Value {
-	o := call.Argument(0)
-	r.checkObjectCoercible(o)
-	proto := call.Argument(1)
-	var protoObj *Object
+func (r *Runtime) toProto(proto Value) *Object {
 	if proto != _null {
 		if obj, ok := proto.(*Object); ok {
-			protoObj = obj
+			return obj
 		} else {
 			panic(r.NewTypeError("Object prototype may only be an Object or null: %s", proto))
 		}
 	}
+	return nil
+}
+
+func (r *Runtime) object_setPrototypeOf(call FunctionCall) Value {
+	o := call.Argument(0)
+	r.checkObjectCoercible(o)
+	proto := r.toProto(call.Argument(1))
 	if o, ok := o.(*Object); ok {
-		if res := o.self.setProto(protoObj); res != nil {
-			panic(res)
-		}
+		o.self.setProto(proto, true)
 	}
 
 	return o
@@ -504,6 +505,11 @@ func (r *Runtime) initObject() {
 	o._putProp("hasOwnProperty", r.newNativeFunc(r.objectproto_hasOwnProperty, nil, "hasOwnProperty", nil, 1), true, false, true)
 	o._putProp("isPrototypeOf", r.newNativeFunc(r.objectproto_isPrototypeOf, nil, "isPrototypeOf", nil, 1), true, false, true)
 	o._putProp("propertyIsEnumerable", r.newNativeFunc(r.objectproto_propertyIsEnumerable, nil, "propertyIsEnumerable", nil, 1), true, false, true)
+	o.defineOwnPropertyStr(__proto__, PropertyDescriptor{
+		Getter:       r.newNativeFunc(r.objectproto_getProto, nil, "get __proto__", nil, 0),
+		Setter:       r.newNativeFunc(r.objectproto_setProto, nil, "set __proto__", nil, 1),
+		Configurable: FLAG_TRUE,
+	}, true)
 
 	r.global.Object = r.newNativeFuncConstruct(r.builtin_Object, classObject, r.global.ObjectPrototype, 1)
 	o = r.global.Object.self

+ 281 - 0
builtin_proxy.go

@@ -0,0 +1,281 @@
+package goja
+
+import "fmt"
+
+func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) *Object {
+	handler := r.NewObject()
+	r.proxyproto_nativehandler_gen_obj_obj(proxy_trap_getPrototypeOf, nativeHandler.GetPrototypeOf, handler)
+	r.proxyproto_nativehandler_setPrototypeOf(nativeHandler.SetPrototypeOf, handler)
+	r.proxyproto_nativehandler_gen_obj_bool(proxy_trap_isExtensible, nativeHandler.IsExtensible, handler)
+	r.proxyproto_nativehandler_gen_obj_bool(proxy_trap_preventExtensions, nativeHandler.PreventExtensions, handler)
+	r.proxyproto_nativehandler_getOwnPropertyDescriptor(nativeHandler.GetOwnPropertyDescriptor, handler)
+	r.proxyproto_nativehandler_defineProperty(nativeHandler.DefineProperty, handler)
+	r.proxyproto_nativehandler_gen_obj_string_bool(proxy_trap_has, nativeHandler.Has, handler)
+	r.proxyproto_nativehandler_get(nativeHandler.Get, handler)
+	r.proxyproto_nativehandler_set(nativeHandler.Set, handler)
+	r.proxyproto_nativehandler_gen_obj_string_bool(proxy_trap_deleteProperty, nativeHandler.DeleteProperty, handler)
+	r.proxyproto_nativehandler_gen_obj_obj(proxy_trap_ownKeys, nativeHandler.OwnKeys, handler)
+	r.proxyproto_nativehandler_apply(nativeHandler.Apply, handler)
+	r.proxyproto_nativehandler_construct(nativeHandler.Construct, handler)
+	return handler
+}
+
+func (r *Runtime) proxyproto_nativehandler_gen_obj_obj(name proxyTrap, native func(*Object) *Object, handler *Object) {
+	if native != nil {
+		handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 1 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					return native(t)
+				}
+			}
+			panic(r.NewTypeError("%s needs to be called with target as Object", name))
+		}, nil, fmt.Sprintf("[native %s]", name), nil, 1), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_setPrototypeOf(native func(*Object, *Object) bool, handler *Object) {
+	if native != nil {
+		handler.self._putProp("setPrototypeOf", r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 2 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					if p, ok := call.Argument(1).(*Object); ok {
+						s := native(t, p)
+						return r.ToValue(s)
+					}
+				}
+			}
+			panic(r.NewTypeError("setPrototypeOf needs to be called with target and prototype as Object"))
+		}, nil, "[native setPrototypeOf]", nil, 2), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_gen_obj_bool(name proxyTrap, native func(*Object) bool, handler *Object) {
+	if native != nil {
+		handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 1 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					s := native(t)
+					return r.ToValue(s)
+				}
+			}
+			panic(r.NewTypeError("%s needs to be called with target as Object", name))
+		}, nil, fmt.Sprintf("[native %s]", name), nil, 1), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_getOwnPropertyDescriptor(native func(*Object, string) PropertyDescriptor, handler *Object) {
+	if native != nil {
+		handler.self._putProp("getOwnPropertyDescriptor", r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 2 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					if p, ok := call.Argument(1).(valueString); ok {
+						desc := native(t, p.String())
+						return desc.toValue(r)
+					}
+				}
+			}
+			panic(r.NewTypeError("getOwnPropertyDescriptor needs to be called with target as Object and prop as string"))
+		}, nil, "[native getOwnPropertyDescriptor]", nil, 2), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_defineProperty(native func(*Object, string, PropertyDescriptor) bool, handler *Object) {
+	if native != nil {
+		handler.self._putProp("defineProperty", r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 3 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					if k, ok := call.Argument(1).(valueString); ok {
+						propertyDescriptor := r.toPropertyDescriptor(call.Argument(2))
+						s := native(t, k.String(), propertyDescriptor)
+						return r.ToValue(s)
+					}
+				}
+			}
+			panic(r.NewTypeError("defineProperty needs to be called with target as Object and propertyDescriptor as string and key as string"))
+		}, nil, "[native defineProperty]", nil, 3), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_gen_obj_string_bool(name proxyTrap, native func(*Object, string) bool, handler *Object) {
+	if native != nil {
+		handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 2 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					if p, ok := call.Argument(1).(valueString); ok {
+						o := native(t, p.String())
+						return r.ToValue(o)
+					}
+				}
+			}
+			panic(r.NewTypeError("%s needs to be called with target as Object and property as string", name))
+		}, nil, fmt.Sprintf("[native %s]", name), nil, 2), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_get(native func(*Object, string, *Object) Value, handler *Object) {
+	if native != nil {
+		handler.self._putProp("get", r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 3 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					if p, ok := call.Argument(1).(valueString); ok {
+						if r, ok := call.Argument(2).(*Object); ok {
+							return native(t, p.String(), r)
+						}
+					}
+				}
+			}
+			panic(r.NewTypeError("get needs to be called with target and receiver as Object and property as string"))
+		}, nil, "[native get]", nil, 3), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_set(native func(*Object, string, Value, *Object) bool, handler *Object) {
+	if native != nil {
+		handler.self._putProp("set", r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 4 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					if p, ok := call.Argument(1).(valueString); ok {
+						v := call.Argument(2)
+						if re, ok := call.Argument(3).(*Object); ok {
+							s := native(t, p.String(), v, re)
+							return r.ToValue(s)
+						}
+					}
+				}
+			}
+			panic(r.NewTypeError("set needs to be called with target and receiver as Object, property as string and value as a legal javascript value"))
+		}, nil, "[native set]", nil, 4), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_apply(native func(*Object, *Object, []Value) Value, handler *Object) {
+	if native != nil {
+		handler.self._putProp("apply", r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 3 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					if this, ok := call.Argument(1).(*Object); ok {
+						if v, ok := call.Argument(2).(*Object); ok {
+							if a, ok := v.self.(*arrayObject); ok {
+								v := native(t, this, a.values)
+								return r.ToValue(v)
+							}
+						}
+					}
+				}
+			}
+			panic(r.NewTypeError("apply needs to be called with target and this as Object and argumentsList as an array of legal javascript values"))
+		}, nil, "[native apply]", nil, 3), true, true, true)
+	}
+}
+
+func (r *Runtime) proxyproto_nativehandler_construct(native func(*Object, []Value, *Object) *Object, handler *Object) {
+	if native != nil {
+		handler.self._putProp("construct", r.newNativeFunc(func(call FunctionCall) Value {
+			if len(call.Arguments) >= 3 {
+				if t, ok := call.Argument(0).(*Object); ok {
+					if v, ok := call.Argument(1).(*Object); ok {
+						if newTarget, ok := call.Argument(2).(*Object); ok {
+							if a, ok := v.self.(*arrayObject); ok {
+								return native(t, a.values, newTarget)
+							}
+						}
+					}
+				}
+			}
+			panic(r.NewTypeError("construct needs to be called with target and newTarget as Object and argumentsList as an array of legal javascript values"))
+		}, nil, "[native construct]", nil, 3), true, true, true)
+	}
+}
+
+type ProxyTrapConfig struct {
+	// A trap for Object.getPrototypeOf, Reflect.getPrototypeOf, __proto__, Object.prototype.isPrototypeOf, instanceof
+	GetPrototypeOf func(target *Object) (prototype *Object)
+
+	// A trap for Object.setPrototypeOf, Reflect.setPrototypeOf
+	SetPrototypeOf func(target *Object, prototype *Object) (success bool)
+
+	// A trap for Object.isExtensible, Reflect.isExtensible
+	IsExtensible func(target *Object) (success bool)
+
+	// A trap for Object.preventExtensions, Reflect.preventExtensions
+	PreventExtensions func(target *Object) (success bool)
+
+	// A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor
+	GetOwnPropertyDescriptor func(target *Object, prop string) (propertyDescriptor PropertyDescriptor)
+
+	// A trap for Object.defineProperty, Reflect.defineProperty
+	DefineProperty func(target *Object, key string, propertyDescriptor PropertyDescriptor) (success bool)
+
+	// A trap for the in operator, with operator, Reflect.has
+	Has func(target *Object, property string) (available bool)
+
+	// A trap for getting property values, Reflect.get
+	Get func(target *Object, property string, receiver *Object) (value Value)
+
+	// A trap for setting property values, Reflect.set
+	Set func(target *Object, property string, value Value, receiver *Object) (success bool)
+
+	// A trap for the delete operator, Reflect.deleteProperty
+	DeleteProperty func(target *Object, property string) (success bool)
+
+	// A trap for Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.keys, Reflect.ownKeys
+	OwnKeys func(target *Object) (object *Object)
+
+	// A trap for a function call, Function.prototype.apply, Function.prototype.call, Reflect.apply
+	Apply func(target *Object, this *Object, argumentsList []Value) (value Value)
+
+	// A trap for the new operator, Reflect.construct
+	Construct func(target *Object, argumentsList []Value, newTarget *Object) (value *Object)
+}
+
+func (r *Runtime) newProxy(args []Value, proto *Object) *Object {
+	if len(args) >= 2 {
+		if target, ok := args[0].(*Object); ok {
+			if proxyHandler, ok := args[1].(*Object); ok {
+				return r.newProxyObject(target, proxyHandler, proto).val
+			}
+		}
+	}
+	panic(r.NewTypeError("Cannot create proxy with a non-object as target or handler"))
+}
+
+func (r *Runtime) builtin_newProxy(args []Value, proto *Object) *Object {
+	return r.newProxy(args, proto)
+}
+
+func (r *Runtime) NewProxy(target *Object, nativeHandler *ProxyTrapConfig) *Proxy {
+	handler := r.newNativeProxyHandler(nativeHandler)
+	proxy := r.newProxyObject(target, handler, r.global.Proxy)
+	return &Proxy{proxy: proxy}
+}
+
+func (r *Runtime) builtin_proxy_revocable(call FunctionCall) Value {
+	if len(call.Arguments) >= 2 {
+		if target, ok := call.Argument(0).(*Object); ok {
+			if proxyHandler, ok := call.Argument(1).(*Object); ok {
+				proxy := r.newProxyObject(target, proxyHandler, nil)
+				revoke := r.newNativeFunc(func(FunctionCall) Value {
+					proxy.revoke()
+					return _undefined
+				}, nil, "", nil, 0)
+				ret := r.NewObject()
+				ret.self._putProp("proxy", proxy.val, true, true, true)
+				ret.self._putProp("revoke", revoke, true, true, true)
+				return ret
+			}
+		}
+	}
+	panic(r.NewTypeError("Cannot create proxy with a non-object as target or handler"))
+}
+
+func (r *Runtime) createProxy(val *Object) objectImpl {
+	o := r.newNativeFuncObj(val, r.constructorThrower("Proxy"), r.builtin_newProxy, "Proxy", nil, 2)
+
+	o._putProp("revocable", r.newNativeFunc(r.builtin_proxy_revocable, nil, "revocable", nil, 2), true, false, true)
+	return o
+}
+
+func (r *Runtime) initProxy() {
+	r.global.Proxy = r.newLazyObject(r.createProxy)
+	r.addToGlobal("Proxy", r.global.Proxy)
+}

+ 828 - 0
builtin_proxy_test.go

@@ -0,0 +1,828 @@
+package goja
+
+import (
+	"testing"
+)
+
+func TestProxy_Object_target_getPrototypeOf(t *testing.T) {
+	const SCRIPT = `
+    var proto = {};
+	var obj = Object.create(proto);
+	var proxy = new Proxy(obj, {});
+	var p = Object.getPrototypeOf(proxy);
+	assert.sameValue(proto, p);
+	`
+
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestProxy_Object_proxy_getPrototypeOf(t *testing.T) {
+	const SCRIPT = `
+    var proto = {};
+	var proto2 = {};
+	var obj = Object.create(proto);
+	var proxy = new Proxy(obj, {
+		getPrototypeOf: function(target) {
+			return proto2;
+		}
+	});
+	var p = Object.getPrototypeOf(proxy);
+	assert.sameValue(proto2, p);
+	`
+
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestProxy_Object_native_proxy_getPrototypeOf(t *testing.T) {
+	const SCRIPT = `
+	var p = Object.getPrototypeOf(proxy);
+	assert.sameValue(proto, p);
+	`
+
+	runtime := New()
+
+	prototype := runtime.NewObject()
+	runtime.Set("proto", prototype)
+
+	target := runtime.NewObject()
+	proxy := runtime.NewProxy(target, &ProxyTrapConfig{
+		GetPrototypeOf: func(target *Object) *Object {
+			return prototype
+		},
+	})
+	runtime.Set("proxy", proxy)
+
+	_, err := runtime.RunString(TESTLIB + SCRIPT)
+	if err != nil {
+		panic(err)
+	}
+}
+
+/*func TestProxy_Object_target_setPrototypeOf(t *testing.T) {
+	const SCRIPT = `
+    var proto = {};
+	var obj = {};
+	Object.setPrototypeOf(obj, proto);
+	var proxy = new Proxy(obj, {});
+	var p = Object.getPrototypeOf(proxy);
+	assert.sameValue(proto, p);
+	`
+
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestProxy_Object_proxy_setPrototypeOf(t *testing.T) {
+	const SCRIPT = `
+    var proto = {};
+	var proto2 = {};
+	var obj = {};
+	Object.setPrototypeOf(obj, proto);
+	var proxy = new Proxy(obj, {
+		setPrototypeOf: function(target, prototype) {
+			return Object.setPrototypeOf(target, proto2);
+		}
+	});
+	var p = Object.getPrototypeOf(proxy);
+	assert.sameValue(proto2, p);
+	`
+
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}*/
+
+func TestProxy_Object_target_isExtensible(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	Object.seal(obj);
+	var proxy = new Proxy(obj, {});
+	Object.isExtensible(proxy);
+	`
+
+	testScript1(SCRIPT, valueFalse, t)
+}
+
+func TestProxy_proxy_isExtensible(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	Object.seal(obj);
+	var proxy = new Proxy(obj, {
+		isExtensible: function(target) {
+			return false;
+		}
+	});
+	Object.isExtensible(proxy);
+	`
+
+	testScript1(SCRIPT, valueFalse, t)
+}
+
+func TestProxy_native_proxy_isExtensible(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+		Object.preventExtensions(target);
+		return Object.isExtensible(proxy);
+	})();
+	`
+
+	runtime := New()
+
+	target := runtime.NewObject()
+	runtime.Set("target", target)
+
+	proxy := runtime.NewProxy(target, &ProxyTrapConfig{
+		IsExtensible: func(target *Object) (success bool) {
+			return false
+		},
+	})
+	runtime.Set("proxy", proxy)
+
+	val, err := runtime.RunString(SCRIPT)
+	if err != nil {
+		panic(err)
+	}
+	if val.ToBoolean() {
+		t.Fatal()
+	}
+}
+
+func TestProxy_Object_target_preventExtensions(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		canEvolve: true
+	};
+	var proxy = new Proxy(obj, {});
+	Object.preventExtensions(proxy);
+	proxy.canEvolve
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestProxy_proxy_preventExtensions(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		canEvolve: true
+	};
+	var proxy = new Proxy(obj, {
+		preventExtensions: function(target) {
+			target.canEvolve = false;
+			return false;
+		}
+	});
+	Object.preventExtensions(proxy);
+	proxy.canEvolve;
+	`
+
+	testScript1(SCRIPT, valueFalse, t)
+}
+
+func TestProxy_native_proxy_preventExtensions(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+		Object.preventExtensions(proxy);
+		return proxy.canEvolve;
+	})();
+	`
+
+	runtime := New()
+
+	target := runtime.NewObject()
+	target.Set("canEvolve", true)
+	runtime.Set("target", target)
+
+	proxy := runtime.NewProxy(target, &ProxyTrapConfig{
+		PreventExtensions: func(target *Object) (success bool) {
+			target.Set("canEvolve", false)
+			return false
+		},
+	})
+	runtime.Set("proxy", proxy)
+
+	val, err := runtime.RunString(SCRIPT)
+	if err != nil {
+		panic(err)
+	}
+	if val.ToBoolean() {
+		t.Fatal()
+	}
+}
+
+func TestProxy_Object_target_getOwnPropertyDescriptor(t *testing.T) {
+	const SCRIPT = `
+	var desc = {
+		configurable: false,
+		enumerable: false,
+		value: 42,
+		writable: false 
+	};
+
+	var obj = {};
+	Object.defineProperty(obj, "foo", desc);
+
+	var proxy = new Proxy(obj, {});
+
+	var desc2 = Object.getOwnPropertyDescriptor(proxy, "foo");
+	desc2.value
+	`
+
+	testScript1(SCRIPT, valueInt(42), t)
+}
+
+func TestProxy_proxy_getOwnPropertyDescriptor(t *testing.T) {
+	const SCRIPT = `
+	var desc = {
+		configurable: false,
+		enumerable: false,
+		value: 42,
+		writable: false 
+	};
+	var proxy_desc = {
+		configurable: false,
+		enumerable: false,
+		value: 24,
+		writable: false 
+	};
+
+	var obj = {};
+	Object.defineProperty(obj, "foo", desc);
+
+	var proxy = new Proxy(obj, {
+		getOwnPropertyDescriptor: function(target, property) {
+			return proxy_desc;
+		}
+	});
+
+	assert.throws(TypeError, function() {
+		Object.getOwnPropertyDescriptor(proxy, "foo");
+	});
+	undefined;
+	`
+
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestProxy_native_proxy_getOwnPropertyDescriptor(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+		var desc = {
+			configurable: true,
+			enumerable: false,
+			value: 42,
+			writable: false 
+		};
+		var proxy_desc = {
+			configurable: true,
+			enumerable: false,
+			value: 24,
+			writable: false 
+		};
+		
+		var obj = {};
+		Object.defineProperty(obj, "foo", desc);
+
+		return function(constructor) {
+			var proxy = constructor(obj, proxy_desc);
+
+			var desc2 = Object.getOwnPropertyDescriptor(proxy, "foo");
+			return desc2.value
+		}
+	})();
+	`
+
+	runtime := New()
+
+	constructor := func(call FunctionCall) Value {
+		target := call.Argument(0).(*Object)
+		proxyDesc := call.Argument(1).(*Object)
+
+		return runtime.NewProxy(target, &ProxyTrapConfig{
+			GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor {
+				return runtime.toPropertyDescriptor(proxyDesc)
+			},
+		}).proxy.val
+	}
+
+	val, err := runtime.RunString(SCRIPT)
+	if err != nil {
+		panic(err)
+	}
+
+	if c, ok := val.(*Object).self.assertCallable(); ok {
+		val := c(FunctionCall{
+			This:      val,
+			Arguments: []Value{runtime.ToValue(constructor)},
+		})
+		if i := val.ToInteger(); i != 24 {
+			t.Fatalf("val: %d", i)
+		}
+	} else {
+		t.Fatal("not a function")
+	}
+}
+
+func TestProxy_Object_target_defineProperty(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	var proxy = new Proxy(obj, {});
+	Object.defineProperty(proxy, "foo", {
+		value: "test123"
+	});
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("test123"), t)
+}
+
+func TestProxy_proxy_defineProperty(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	var proxy = new Proxy(obj, {
+		defineProperty: function(target, prop, descriptor) {
+			target.foo = "321tset";
+			return true;
+		}
+	});
+	Object.defineProperty(proxy, "foo", {
+		value: "test123"
+	});
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("321tset"), t)
+}
+
+func TestProxy_native_proxy_defineProperty(t *testing.T) {
+	const SCRIPT = `
+	Object.defineProperty(proxy, "foo", {
+		value: "test123"
+	});
+	proxy.foo;
+	`
+
+	runtime := New()
+
+	target := runtime.NewObject()
+
+	proxy := runtime.NewProxy(target, &ProxyTrapConfig{
+		DefineProperty: func(target *Object, key string, propertyDescriptor PropertyDescriptor) (success bool) {
+			target.Set("foo", "321tset")
+			return true
+		},
+	})
+	runtime.Set("proxy", proxy)
+
+	val, err := runtime.RunString(SCRIPT)
+	if err != nil {
+		panic(err)
+	}
+	if s := val.String(); s != "321tset" {
+		t.Fatalf("val: %s", s)
+	}
+}
+
+func TestProxy_target_has_in(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		secret: true
+	};
+	var proxy = new Proxy(obj, {});
+	
+	"secret" in proxy
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestProxy_proxy_has_in(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		secret: true
+	};
+	var proxy = new Proxy(obj, {
+		has: function(target, key) {
+			return key !== "secret";
+		}
+	});
+	
+	"secret" in proxy
+	`
+
+	testScript1(SCRIPT, valueFalse, t)
+}
+
+func TestProxy_target_has_with(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		secret: true
+	};
+	var proxy = new Proxy(obj, {});
+	
+	with(proxy) {
+		(secret);
+	}
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestProxy_proxy_has_with(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		secret: true
+	};
+	var proxy = new Proxy(obj, {
+		has: function(target, key) {
+			return key !== "secret";
+		}
+	});
+	
+	var thrown = false;
+	try {
+		with(proxy) {
+			(secret);
+		}
+	} catch (e) {
+		if (e instanceof ReferenceError) {
+			thrown = true;
+		} else {
+			throw e;
+		}
+	}
+	thrown;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestProxy_target_get(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	var proxy = new Proxy(obj, {});
+	Object.defineProperty(proxy, "foo", {
+		value: "test123"
+	});
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("test123"), t)
+}
+
+func TestProxy_proxy_get(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	var proxy = new Proxy(obj, {
+		get: function(target, prop, receiver) {
+			return "321tset"
+		}
+	});
+	Object.defineProperty(proxy, "foo", {
+		value: "test123",
+		configurable: true,
+	});
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("321tset"), t)
+}
+
+func TestProxy_target_set_prop(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	var proxy = new Proxy(obj, {});
+	proxy.foo = "test123";
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("test123"), t)
+}
+
+func TestProxy_proxy_set_prop(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	var proxy = new Proxy(obj, {
+		set: function(target, prop, receiver) {
+			target.foo = "321tset";
+			return true;
+		}
+	});
+	proxy.foo = "test123";
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("321tset"), t)
+}
+func TestProxy_target_set_associative(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	var proxy = new Proxy(obj, {});
+	proxy["foo"] = "test123";
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("test123"), t)
+}
+
+func TestProxy_proxy_set_associative(t *testing.T) {
+	const SCRIPT = `
+	var obj = {};
+	var proxy = new Proxy(obj, {
+		set: function(target, property, value, receiver) {
+			target["foo"] = "321tset";
+			return true;
+		}
+	});
+	proxy["foo"] = "test123";
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("321tset"), t)
+}
+
+func TestProxy_target_delete(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		foo: "test"
+	};
+	var proxy = new Proxy(obj, {});
+	delete proxy.foo;
+
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestProxy_proxy_delete(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		foo: "test"
+	};
+	var proxy = new Proxy(obj, {
+		deleteProperty: function(target, prop) {
+			return true;
+		}
+	});
+	delete proxy.foo;
+
+	proxy.foo;
+	`
+
+	testScript1(SCRIPT, asciiString("test"), t)
+}
+
+func TestProxy_target_keys(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		foo: "test"
+	};
+	var proxy = new Proxy(obj, {});
+
+	var keys = Object.keys(proxy);
+	if (keys.length != 1) {
+		throw new Error("assertion error");
+	}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestProxy_proxy_keys(t *testing.T) {
+	const SCRIPT = `
+	var obj = {
+		foo: "test"
+	};
+	var proxy = new Proxy(obj, {
+		ownKeys: function(target) {
+			return ["foo", "bar"];
+		}
+	});
+
+	var keys = Object.keys(proxy);
+	if (keys.length !== 1) {
+		throw new Error("length is "+keys.length);
+	}
+	if (keys[0] !== "foo") {
+		throw new Error("keys[0] is "+keys[0]);
+	}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestProxy_target_call(t *testing.T) {
+	const SCRIPT = `
+	var obj = function() {
+		return "test"
+	}
+	
+	var proxy = new Proxy(obj, {});
+
+	proxy();
+	`
+
+	testScript1(SCRIPT, asciiString("test"), t)
+}
+
+func TestProxy_proxy_call(t *testing.T) {
+	const SCRIPT = `
+	var obj = function() {
+		return "test"
+	}
+	
+	var proxy = new Proxy(obj, {
+		apply: function(target, thisArg, args) {
+			return "tset"
+		}
+	});
+
+	proxy();
+	`
+
+	testScript1(SCRIPT, asciiString("tset"), t)
+}
+
+func TestProxy_target_func_apply(t *testing.T) {
+	const SCRIPT = `
+	var obj = function() {
+		return "test"
+	}
+	
+	var proxy = new Proxy(obj, {});
+
+	proxy.apply();
+	`
+
+	testScript1(SCRIPT, asciiString("test"), t)
+}
+
+func TestProxy_proxy_func_apply(t *testing.T) {
+	const SCRIPT = `
+	var obj = function() {
+		return "test"
+	}
+	
+	var proxy = new Proxy(obj, {
+		apply: function(target, thisArg, args) {
+			return "tset"
+		}
+	});
+
+	proxy.apply();
+	`
+
+	testScript1(SCRIPT, asciiString("tset"), t)
+}
+
+func TestProxy_target_func_call(t *testing.T) {
+	const SCRIPT = `
+	var obj = function() {
+		return "test"
+	}
+	
+	var proxy = new Proxy(obj, {});
+
+	proxy.call();
+	`
+
+	testScript1(SCRIPT, asciiString("test"), t)
+}
+
+func TestProxy_proxy_func_call(t *testing.T) {
+	const SCRIPT = `
+	var obj = function() {
+		return "test"
+	}
+	
+	var proxy = new Proxy(obj, {
+		apply: function(target, thisArg, args) {
+			return "tset"
+		}
+	});
+
+	proxy.call();
+	`
+
+	testScript1(SCRIPT, asciiString("tset"), t)
+}
+
+func TestProxy_target_new(t *testing.T) {
+	const SCRIPT = `
+	var obj = function(word) {
+		this.foo = function() {
+			return word;
+		}
+	}
+	
+	var proxy = new Proxy(obj, {});
+
+	var instance = new proxy("test");
+	instance.foo();
+	`
+
+	testScript1(SCRIPT, asciiString("test"), t)
+}
+
+func TestProxy_proxy_new(t *testing.T) {
+	const SCRIPT = `
+	var obj = function(word) {
+		this.foo = function() {
+			return word;
+		}
+	}
+	
+	var proxy = new Proxy(obj, {
+		construct: function(target, args, newTarget) {
+			var word = args[0]; 
+			return {
+				foo: function() {
+					return "caught-" + word
+				}
+			}
+		}
+	});
+
+	var instance = new proxy("test");
+	instance.foo();
+	`
+
+	testScript1(SCRIPT, asciiString("caught-test"), t)
+}
+
+func TestProxy_Object_native_proxy_ownKeys(t *testing.T) {
+	headers := map[string][]string{
+		"k0": {},
+	}
+	vm := New()
+	proxy := vm.NewProxy(vm.NewObject(), &ProxyTrapConfig{
+		OwnKeys: func(target *Object) (object *Object) {
+			keys := make([]interface{}, 0, len(headers))
+			for k := range headers {
+				keys = append(keys, k)
+			}
+			return vm.ToValue(keys).ToObject(vm)
+		},
+		GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor {
+			v, exists := headers[prop]
+			if exists {
+				return PropertyDescriptor{
+					Value:        vm.ToValue(v),
+					Enumerable:   FLAG_TRUE,
+					Configurable: FLAG_TRUE,
+				}
+			}
+			return PropertyDescriptor{}
+		},
+	})
+	vm.Set("headers", proxy)
+	v, err := vm.RunString(`
+		var keys = Object.keys(headers);
+		keys.length === 1 && keys[0] === "k0";
+		`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if v != valueTrue {
+		t.Fatal("not true", v)
+	}
+}
+
+func TestProxy_proxy_forIn(t *testing.T) {
+	const SCRIPT = `
+	var proto = {
+		a: 2,
+		protoProp: 1
+	}
+	Object.defineProperty(proto, "protoNonEnum", {
+		value: 2,
+		writable: true,
+		configurable: true
+	});
+	var target = Object.create(proto);
+	var proxy = new Proxy(target, {
+		ownKeys: function() {
+			return ["a", "b"];
+		},
+		getOwnPropertyDescriptor: function(target, p) {
+			switch (p) {
+			case "a":
+			case "b":
+				return {
+					value: 42,
+					enumerable: true,
+					configurable: true
+				}
+			}
+		},
+	});
+
+	var forInResult = [];
+	for (var key in proxy) {
+		if (forInResult.indexOf(key) !== -1) {
+			throw new Error("Duplicate property "+key);
+		}
+		forInResult.push(key);
+	}
+	forInResult.length === 3 && forInResult[0] === "a" && forInResult[1] === "b" && forInResult[2] === "protoProp";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}

+ 132 - 0
builtin_reflect.go

@@ -0,0 +1,132 @@
+package goja
+
+func (r *Runtime) builtin_reflect_apply(call FunctionCall) Value {
+	return r.toCallable(call.Argument(0))(FunctionCall{
+		This:      call.Argument(1),
+		Arguments: r.createListFromArrayLike(call.Argument(2))})
+}
+
+func (r *Runtime) toConstructor(v Value) func(args []Value, newTarget *Object) *Object {
+	if ctor := r.toObject(v).self.assertConstructor(); ctor != nil {
+		return ctor
+	}
+	panic(r.NewTypeError("Value is not a constructor"))
+}
+
+func (r *Runtime) builtin_reflect_construct(call FunctionCall) Value {
+	target := call.Argument(0)
+	ctor := r.toConstructor(target)
+	var newTarget Value
+	if len(call.Arguments) > 2 {
+		newTarget = call.Argument(2)
+		r.toConstructor(newTarget)
+	} else {
+		newTarget = target
+	}
+	return ctor(r.createListFromArrayLike(call.Argument(1)), r.toObject(newTarget))
+}
+
+func (r *Runtime) builtin_reflect_defineProperty(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	key := toPropertyKey(call.Argument(1))
+	desc := r.toPropertyDescriptor(call.Argument(2))
+
+	return r.toBoolean(target.defineOwnProperty(key, desc, false))
+}
+
+func (r *Runtime) builtin_reflect_deleteProperty(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	key := toPropertyKey(call.Argument(1))
+
+	return r.toBoolean(target.delete(key, false))
+}
+
+func (r *Runtime) builtin_reflect_get(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	key := toPropertyKey(call.Argument(1))
+	var receiver Value
+	if len(call.Arguments) > 2 {
+		receiver = call.Arguments[2]
+	}
+	return target.get(key, receiver)
+}
+
+func (r *Runtime) builtin_reflect_getOwnPropertyDescriptor(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	key := toPropertyKey(call.Argument(1))
+	return r.valuePropToDescriptorObject(target.getOwnProp(key))
+}
+
+func (r *Runtime) builtin_reflect_getPrototypeOf(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	if proto := target.self.proto(); proto != nil {
+		return proto
+	}
+
+	return _null
+}
+
+func (r *Runtime) builtin_reflect_has(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	key := toPropertyKey(call.Argument(1))
+	return r.toBoolean(target.hasProperty(key))
+}
+
+func (r *Runtime) builtin_reflect_isExtensible(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	return r.toBoolean(target.self.isExtensible())
+}
+
+func (r *Runtime) builtin_reflect_ownKeys(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	return r.newArrayValues(target.self.ownPropertyKeys(true, nil))
+}
+
+func (r *Runtime) builtin_reflect_preventExtensions(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	return r.toBoolean(target.self.preventExtensions(false))
+}
+
+func (r *Runtime) builtin_reflect_set(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	var receiver Value
+	if len(call.Arguments) >= 4 {
+		receiver = call.Argument(3)
+	} else {
+		receiver = target
+	}
+	return r.toBoolean(target.set(call.Argument(1), call.Argument(2), receiver, false))
+}
+
+func (r *Runtime) builtin_reflect_setPrototypeOf(call FunctionCall) Value {
+	target := r.toObject(call.Argument(0))
+	var proto *Object
+	if arg := call.Argument(1); arg != _null {
+		proto = r.toObject(arg)
+	}
+	return r.toBoolean(target.self.setProto(proto, false))
+}
+
+func (r *Runtime) createReflect(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
+
+	o._putProp("apply", r.newNativeFunc(r.builtin_reflect_apply, nil, "apply", nil, 3), true, false, true)
+	o._putProp("construct", r.newNativeFunc(r.builtin_reflect_construct, nil, "construct", nil, 2), true, false, true)
+	o._putProp("defineProperty", r.newNativeFunc(r.builtin_reflect_defineProperty, nil, "defineProperty", nil, 3), true, false, true)
+	o._putProp("deleteProperty", r.newNativeFunc(r.builtin_reflect_deleteProperty, nil, "deleteProperty", nil, 2), true, false, true)
+	o._putProp("get", r.newNativeFunc(r.builtin_reflect_get, nil, "get", nil, 2), true, false, true)
+	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.builtin_reflect_getOwnPropertyDescriptor, nil, "getOwnPropertyDescriptor", nil, 2), true, false, true)
+	o._putProp("getPrototypeOf", r.newNativeFunc(r.builtin_reflect_getPrototypeOf, nil, "getPrototypeOf", nil, 1), true, false, true)
+	o._putProp("has", r.newNativeFunc(r.builtin_reflect_has, nil, "has", nil, 2), true, false, true)
+	o._putProp("isExtensible", r.newNativeFunc(r.builtin_reflect_isExtensible, nil, "isExtensible", nil, 1), true, false, true)
+	o._putProp("ownKeys", r.newNativeFunc(r.builtin_reflect_ownKeys, nil, "ownKeys", nil, 1), true, false, true)
+	o._putProp("preventExtensions", r.newNativeFunc(r.builtin_reflect_preventExtensions, nil, "preventExtensions", nil, 1), true, false, true)
+	o._putProp("set", r.newNativeFunc(r.builtin_reflect_set, nil, "set", nil, 3), true, false, true)
+	o._putProp("setPrototypeOf", r.newNativeFunc(r.builtin_reflect_setPrototypeOf, nil, "setPrototypeOf", nil, 2), true, false, true)
+
+	return o
+}
+
+func (r *Runtime) initReflect() {
+	r.addToGlobal("Reflect", r.newLazyObject(r.createReflect))
+}

+ 48 - 48
builtin_regexp.go

@@ -119,16 +119,16 @@ func (r *Runtime) newRegExp(patternStr valueString, flags string, proto *Object)
 	return r.newRegExpp(pattern, patternStr, global, ignoreCase, multiline, sticky, proto)
 }
 
-func (r *Runtime) builtin_newRegExp(args []Value) *Object {
+func (r *Runtime) builtin_newRegExp(args []Value, proto *Object) *Object {
 	var pattern valueString
 	var flags string
 	if len(args) > 0 {
 		if obj, ok := args[0].(*Object); ok {
-			if regexp, ok := obj.self.(*regexpObject); ok {
+			if rx, ok := obj.self.(*regexpObject); ok {
 				if len(args) < 2 || args[1] == _undefined {
-					return regexp.clone()
+					return rx.clone()
 				} else {
-					return r.newRegExp(regexp.source, args[1].String(), r.global.RegExpPrototype)
+					return r.newRegExp(rx.source, args[1].String(), proto)
 				}
 			}
 		}
@@ -144,7 +144,7 @@ func (r *Runtime) builtin_newRegExp(args []Value) *Object {
 	if pattern == nil {
 		pattern = stringEmpty
 	}
-	return r.newRegExp(pattern, flags, r.global.RegExpPrototype)
+	return r.newRegExp(pattern, flags, proto)
 }
 
 func (r *Runtime) builtin_RegExp(call FunctionCall) Value {
@@ -156,7 +156,7 @@ func (r *Runtime) builtin_RegExp(call FunctionCall) Value {
 			}
 		}
 	}
-	return r.builtin_newRegExp(call.Arguments)
+	return r.builtin_newRegExp(call.Arguments, r.global.RegExpPrototype)
 }
 
 func (r *Runtime) regexpproto_exec(call FunctionCall) Value {
@@ -271,16 +271,16 @@ func (r *Runtime) regexpproto_getFlags(call FunctionCall) Value {
 	if this, ok := thisObj.self.(*regexpObject); ok {
 		global, ignoreCase, multiline, sticky = this.global, this.ignoreCase, this.multiline, this.sticky
 	} else {
-		if v := thisObj.self.getStr("global"); v != nil {
+		if v := thisObj.self.getStr("global", nil); v != nil {
 			global = v.ToBoolean()
 		}
-		if v := thisObj.self.getStr("ignoreCase"); v != nil {
+		if v := thisObj.self.getStr("ignoreCase", nil); v != nil {
 			ignoreCase = v.ToBoolean()
 		}
-		if v := thisObj.self.getStr("multiline"); v != nil {
+		if v := thisObj.self.getStr("multiline", nil); v != nil {
 			multiline = v.ToBoolean()
 		}
-		if v := thisObj.self.getStr("sticky"); v != nil {
+		if v := thisObj.self.getStr("sticky", nil); v != nil {
 			sticky = v.ToBoolean()
 		}
 	}
@@ -319,10 +319,10 @@ func (r *Runtime) regExpExec(execFn func(FunctionCall) Value, rxObj *Object, arg
 
 func (r *Runtime) regexpproto_stdMatcherGeneric(rxObj *Object, arg Value) Value {
 	rx := rxObj.self
-	global := rx.getStr("global")
+	global := rx.getStr("global", nil)
 	if global != nil && global.ToBoolean() {
-		rx.putStr("lastIndex", intToValue(0), true)
-		execFn, ok := r.toObject(rx.getStr("exec")).self.assertCallable()
+		rx.setOwnStr("lastIndex", intToValue(0), true)
+		execFn, ok := r.toObject(rx.getStr("exec", nil)).self.assertCallable()
 		if !ok {
 			panic(r.NewTypeError("exec is not a function"))
 		}
@@ -332,11 +332,11 @@ func (r *Runtime) regexpproto_stdMatcherGeneric(rxObj *Object, arg Value) Value
 			if res == _null {
 				break
 			}
-			matchStr := nilSafe(r.toObject(res).self.get(intToValue(0))).toString()
+			matchStr := nilSafe(r.toObject(res).self.getIdx(valueInt(0), nil)).toString()
 			a = append(a, matchStr)
 			if matchStr.length() == 0 {
-				thisIndex := rx.getStr("lastIndex").ToInteger()
-				rx.putStr("lastIndex", intToValue(thisIndex+1), true) // TODO fullUnicode
+				thisIndex := rx.getStr("lastIndex", nil).ToInteger()
+				rx.setOwnStr("lastIndex", intToValue(thisIndex+1), true) // TODO fullUnicode
 			}
 		}
 		if len(a) == 0 {
@@ -345,7 +345,7 @@ func (r *Runtime) regexpproto_stdMatcherGeneric(rxObj *Object, arg Value) Value
 		return r.newArrayValues(a)
 	}
 
-	execFn, ok := r.toObject(rx.getStr("exec")).self.assertCallable()
+	execFn, ok := r.toObject(rx.getStr("exec", nil)).self.assertCallable()
 	if !ok {
 		panic(r.NewTypeError("exec is not a function"))
 	}
@@ -358,7 +358,7 @@ func (r *Runtime) checkStdRegexp(rxObj *Object) *regexpObject {
 	if !ok {
 		return nil
 	}
-	execFn := rx.getPropStr("exec")
+	execFn := rx.getStr("exec", nil)
 	if execFn != nil && execFn != r.global.regexpProtoExec {
 		return nil
 	}
@@ -374,7 +374,7 @@ func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value {
 		return r.regexpproto_stdMatcherGeneric(thisObj, s)
 	}
 	if rx.global {
-		rx.putStr("lastIndex", intToValue(0), true)
+		rx.setOwnStr("lastIndex", intToValue(0), true)
 		var a []Value
 		var previousLastIndex int64
 		for {
@@ -382,10 +382,10 @@ func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value {
 			if !match {
 				break
 			}
-			thisIndex := rx.getStr("lastIndex").ToInteger()
+			thisIndex := rx.getStr("lastIndex", nil).ToInteger()
 			if thisIndex == previousLastIndex {
 				previousLastIndex++
-				rx.putStr("lastIndex", intToValue(previousLastIndex), true)
+				rx.setOwnStr("lastIndex", intToValue(previousLastIndex), true)
 			} else {
 				previousLastIndex = thisIndex
 			}
@@ -402,21 +402,21 @@ func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value {
 
 func (r *Runtime) regexpproto_stdSearchGeneric(rxObj *Object, arg valueString) Value {
 	rx := rxObj.self
-	previousLastIndex := rx.getStr("lastIndex")
-	rx.putStr("lastIndex", intToValue(0), true)
-	execFn, ok := r.toObject(rx.getStr("exec")).self.assertCallable()
+	previousLastIndex := rx.getStr("lastIndex", nil)
+	rx.setOwnStr("lastIndex", intToValue(0), true)
+	execFn, ok := r.toObject(rx.getStr("exec", nil)).self.assertCallable()
 	if !ok {
 		panic(r.NewTypeError("exec is not a function"))
 	}
 
 	result := r.regExpExec(execFn, rxObj, arg)
-	rx.putStr("lastIndex", previousLastIndex, true)
+	rx.setOwnStr("lastIndex", previousLastIndex, true)
 
 	if result == _null {
 		return intToValue(-1)
 	}
 
-	return r.toObject(result).self.getStr("index")
+	return r.toObject(result).self.getStr("index", nil)
 }
 
 func (r *Runtime) regexpproto_stdSearch(call FunctionCall) Value {
@@ -427,11 +427,11 @@ func (r *Runtime) regexpproto_stdSearch(call FunctionCall) Value {
 		return r.regexpproto_stdSearchGeneric(thisObj, s)
 	}
 
-	previousLastIndex := rx.getStr("lastIndex")
-	rx.putStr("lastIndex", intToValue(0), true)
+	previousLastIndex := rx.getStr("lastIndex", nil)
+	rx.setOwnStr("lastIndex", intToValue(0), true)
 
 	match, result := rx.execRegexp(s)
-	rx.putStr("lastIndex", previousLastIndex, true)
+	rx.setOwnStr("lastIndex", previousLastIndex, true)
 
 	if !match {
 		return intToValue(-1)
@@ -452,7 +452,7 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 	if lim == 0 {
 		return r.newArrayValues(a)
 	}
-	execFn := toMethod(splitter.ToObject(r).self.getStr("exec")) // must be non-nil
+	execFn := toMethod(splitter.ToObject(r).self.getStr("exec", nil)) // must be non-nil
 
 	if size == 0 {
 		if r.regExpExec(execFn, splitter, s) == _null {
@@ -463,13 +463,13 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 
 	q := p
 	for q < size {
-		splitter.self.putStr("lastIndex", intToValue(q), true)
+		splitter.self.setOwnStr("lastIndex", intToValue(q), true)
 		z := r.regExpExec(execFn, splitter, s)
 		if z == _null {
 			q++
 		} else {
 			z := r.toObject(z)
-			e := toLength(splitter.self.getStr("lastIndex"))
+			e := toLength(splitter.self.getStr("lastIndex", nil))
 			if e == p {
 				q++
 			} else {
@@ -478,9 +478,9 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 					return r.newArrayValues(a)
 				}
 				p = e
-				numberOfCaptures := max(toLength(z.self.getStr("length"))-1, 0)
+				numberOfCaptures := max(toLength(z.self.getStr("length", nil))-1, 0)
 				for i := int64(1); i <= numberOfCaptures; i++ {
-					a = append(a, z.self.get(intToValue(i)))
+					a = append(a, z.self.getIdx(valueInt(i), nil))
 					if int64(len(a)) == lim {
 						return r.newArrayValues(a)
 					}
@@ -496,13 +496,13 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString
 func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value {
 	rxObj := r.toObject(call.This)
 	c := r.speciesConstructor(rxObj, r.global.RegExp)
-	flags := nilSafe(rxObj.self.getStr("flags")).toString()
+	flags := nilSafe(rxObj.self.getStr("flags", nil)).toString()
 
 	// Add 'y' flag if missing
 	if flagsStr := flags.String(); !strings.Contains(flagsStr, "y") {
 		flags = newStringValue(flagsStr + "y")
 	}
-	splitter := c([]Value{rxObj, flags})
+	splitter := c([]Value{rxObj, flags}, nil)
 
 	s := call.Argument(0).toString()
 	limitValue := call.Argument(1)
@@ -582,50 +582,50 @@ func (r *Runtime) initRegExp() {
 	r.global.RegExpPrototype = r.NewObject()
 	o := r.global.RegExpPrototype.self
 	r.global.regexpProtoExec = valueProp(r.newNativeFunc(r.regexpproto_exec, nil, "exec", nil, 1), true, false, true)
-	o.putStr("exec", r.global.regexpProtoExec, true)
+	o.setOwnStr("exec", r.global.regexpProtoExec, true)
 	o._putProp("test", r.newNativeFunc(r.regexpproto_test, nil, "test", nil, 1), true, false, true)
 	o._putProp("toString", r.newNativeFunc(r.regexpproto_toString, nil, "toString", nil, 0), true, false, true)
-	o.putStr("source", &valueProperty{
+	o.setOwnStr("source", &valueProperty{
 		configurable: true,
 		getterFunc:   r.newNativeFunc(r.regexpproto_getSource, nil, "get source", nil, 0),
 		accessor:     true,
 	}, false)
-	o.putStr("global", &valueProperty{
+	o.setOwnStr("global", &valueProperty{
 		configurable: true,
 		getterFunc:   r.newNativeFunc(r.regexpproto_getGlobal, nil, "get global", nil, 0),
 		accessor:     true,
 	}, false)
-	o.putStr("multiline", &valueProperty{
+	o.setOwnStr("multiline", &valueProperty{
 		configurable: true,
 		getterFunc:   r.newNativeFunc(r.regexpproto_getMultiline, nil, "get multiline", nil, 0),
 		accessor:     true,
 	}, false)
-	o.putStr("ignoreCase", &valueProperty{
+	o.setOwnStr("ignoreCase", &valueProperty{
 		configurable: true,
 		getterFunc:   r.newNativeFunc(r.regexpproto_getIgnoreCase, nil, "get ignoreCase", nil, 0),
 		accessor:     true,
 	}, false)
-	o.putStr("sticky", &valueProperty{
+	o.setOwnStr("sticky", &valueProperty{
 		configurable: true,
 		getterFunc:   r.newNativeFunc(r.regexpproto_getSticky, nil, "get sticky", nil, 0),
 		accessor:     true,
 	}, false)
-	o.putStr("flags", &valueProperty{
+	o.setOwnStr("flags", &valueProperty{
 		configurable: true,
 		getterFunc:   r.newNativeFunc(r.regexpproto_getFlags, nil, "get flags", nil, 0),
 		accessor:     true,
 	}, false)
 
-	o.put(symMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, nil, "[Symbol.match]", nil, 1), true, false, true), true)
-	o.put(symSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, nil, "[Symbol.search]", nil, 1), true, false, true), true)
-	o.put(symSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, nil, "[Symbol.split]", nil, 2), true, false, true), true)
+	o._putSym(symMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, nil, "[Symbol.match]", nil, 1), true, false, true))
+	o._putSym(symSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, nil, "[Symbol.search]", nil, 1), true, false, true))
+	o._putSym(symSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, nil, "[Symbol.split]", nil, 2), true, false, true))
 
 	r.global.RegExp = r.newNativeFunc(r.builtin_RegExp, r.builtin_newRegExp, "RegExp", r.global.RegExpPrototype, 2)
 	o = r.global.RegExp.self
-	o.put(symSpecies, &valueProperty{
+	o._putSym(symSpecies, &valueProperty{
 		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
 		accessor:     true,
 		configurable: true,
-	}, true)
+	})
 	r.addToGlobal("RegExp", r.global.RegExp)
 }

+ 9 - 9
builtin_set.go

@@ -121,7 +121,7 @@ func (r *Runtime) setProto_values(call FunctionCall) Value {
 	return r.createSetIterator(call.This, iterationKindValue)
 }
 
-func (r *Runtime) builtin_newSet(args []Value) *Object {
+func (r *Runtime) builtin_newSet(args []Value, proto *Object) *Object {
 	o := &Object{runtime: r}
 
 	so := &setObject{}
@@ -129,11 +129,11 @@ func (r *Runtime) builtin_newSet(args []Value) *Object {
 	so.val = o
 	so.extensible = true
 	o.self = so
-	so.prototype = r.global.SetPrototype
+	so.prototype = proto
 	so.init()
 	if len(args) > 0 {
 		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
-			adder := so.getStr("add")
+			adder := so.getStr("add", nil)
 			iter := r.getIterator(arg, nil)
 			if adder == r.global.setAdder {
 				r.iterate(iter, func(item Value) {
@@ -195,7 +195,7 @@ func (r *Runtime) createSetProto(val *Object) objectImpl {
 	o._putProp("delete", r.newNativeFunc(r.setProto_delete, nil, "delete", nil, 1), true, false, true)
 	o._putProp("forEach", r.newNativeFunc(r.setProto_forEach, nil, "forEach", nil, 1), true, false, true)
 	o._putProp("has", r.newNativeFunc(r.setProto_has, nil, "has", nil, 1), true, false, true)
-	o.putStr("size", &valueProperty{
+	o.setOwnStr("size", &valueProperty{
 		getterFunc:   r.newNativeFunc(r.setProto_getSize, nil, "get size", nil, 0),
 		accessor:     true,
 		writable:     true,
@@ -206,19 +206,19 @@ func (r *Runtime) createSetProto(val *Object) objectImpl {
 	o._putProp("values", valuesFunc, true, false, true)
 	o._putProp("keys", valuesFunc, true, false, true)
 	o._putProp("entries", r.newNativeFunc(r.setProto_entries, nil, "entries", nil, 0), true, false, true)
-	o.put(symIterator, valueProp(valuesFunc, true, false, true), true)
-	o.put(symToStringTag, valueProp(asciiString(classSet), false, false, true), true)
+	o._putSym(symIterator, valueProp(valuesFunc, true, false, true))
+	o._putSym(symToStringTag, valueProp(asciiString(classSet), false, false, true))
 
 	return o
 }
 
 func (r *Runtime) createSet(val *Object) objectImpl {
 	o := r.newNativeFuncObj(val, r.constructorThrower("Set"), r.builtin_newSet, "Set", r.global.SetPrototype, 0)
-	o.putSym(symSpecies, &valueProperty{
+	o._putSym(symSpecies, &valueProperty{
 		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
 		accessor:     true,
 		configurable: true,
-	}, true)
+	})
 
 	return o
 }
@@ -227,7 +227,7 @@ func (r *Runtime) createSetIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject)
 
 	o._putProp("next", r.newNativeFunc(r.setIterProto_next, nil, "next", nil, 0), true, false, true)
-	o.put(symToStringTag, valueProp(asciiString(classSetIterator), false, false, true), true)
+	o._putSym(symToStringTag, valueProp(asciiString(classSetIterator), false, false, true))
 
 	return o
 }

+ 12 - 13
builtin_string.go

@@ -21,7 +21,7 @@ func (r *Runtime) collator() *collate.Collator {
 }
 
 func toString(arg Value) valueString {
-	if s, ok := arg.assertString(); ok {
+	if s, ok := arg.(valueString); ok {
 		return s
 	}
 	if s, ok := arg.(*valueSymbol); ok {
@@ -38,7 +38,7 @@ func (r *Runtime) builtin_String(call FunctionCall) Value {
 	}
 }
 
-func (r *Runtime) _newString(s valueString) *Object {
+func (r *Runtime) _newString(s valueString, proto *Object) *Object {
 	v := &Object{runtime: r}
 
 	o := &stringObject{}
@@ -46,7 +46,7 @@ func (r *Runtime) _newString(s valueString) *Object {
 	o.val = v
 	o.extensible = true
 	v.self = o
-	o.prototype = r.global.StringPrototype
+	o.prototype = proto
 	if s != nil {
 		o.value = s
 	}
@@ -54,14 +54,14 @@ func (r *Runtime) _newString(s valueString) *Object {
 	return v
 }
 
-func (r *Runtime) builtin_newString(args []Value) *Object {
+func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object {
 	var s valueString
 	if len(args) > 0 {
 		s = toString(args[0])
 	} else {
 		s = stringEmpty
 	}
-	return r._newString(s)
+	return r._newString(s, proto)
 }
 
 func searchSubstringUTF8(str, search string) (ret [][]int) {
@@ -79,7 +79,7 @@ func searchSubstringUTF8(str, search string) (ret [][]int) {
 }
 
 func (r *Runtime) stringproto_toStringValueOf(this Value, funcName string) Value {
-	if str, ok := this.assertString(); ok {
+	if str, ok := this.(valueString); ok {
 		return str
 	}
 	if obj, ok := this.(*Object); ok {
@@ -206,7 +206,7 @@ func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value {
 	numPos := call.Argument(1).ToNumber()
 
 	var pos int64
-	if f, ok := numPos.assertFloat(); ok && math.IsNaN(f) {
+	if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) {
 		pos = value.length()
 	} else {
 		pos = numPos.ToInteger()
@@ -248,10 +248,10 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value {
 	}
 
 	if rx == nil {
-		rx = r.builtin_newRegExp([]Value{regexp}).self.(*regexpObject)
+		rx = r.builtin_newRegExp([]Value{regexp}, r.global.RegExpPrototype).self.(*regexpObject)
 	}
 
-	if matcher, ok := r.toObject(rx.getSym(symMatch)).self.assertCallable(); ok {
+	if matcher, ok := r.toObject(rx.getSym(symMatch, nil)).self.assertCallable(); ok {
 		return matcher(FunctionCall{
 			This:      rx.val,
 			Arguments: []Value{call.This.toString()},
@@ -431,10 +431,10 @@ func (r *Runtime) stringproto_search(call FunctionCall) Value {
 	}
 
 	if rx == nil {
-		rx = r.builtin_newRegExp([]Value{regexp}).self.(*regexpObject)
+		rx = r.builtin_newRegExp([]Value{regexp}, r.global.RegExpPrototype).self.(*regexpObject)
 	}
 
-	if searcher, ok := r.toObject(rx.getSym(symSearch)).self.assertCallable(); ok {
+	if searcher, ok := r.toObject(rx.getSym(symSearch, nil)).self.assertCallable(); ok {
 		return searcher(FunctionCall{
 			This:      rx.val,
 			Arguments: []Value{call.This.toString()},
@@ -615,10 +615,9 @@ func (r *Runtime) stringproto_substr(call FunctionCall) Value {
 }
 
 func (r *Runtime) initString() {
-	r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty})
+	r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty}, r.global.ObjectPrototype)
 
 	o := r.global.StringPrototype.self
-	o.(*stringObject).prototype = r.global.ObjectPrototype
 	o._putProp("toString", r.newNativeFunc(r.stringproto_toString, nil, "toString", nil, 0), true, false, true)
 	o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
 	o._putProp("charAt", r.newNativeFunc(r.stringproto_charAt, nil, "charAt", nil, 1), true, false, true)

+ 2 - 2
builtin_symbol.go

@@ -99,8 +99,8 @@ func (r *Runtime) createSymbolProto(val *Object) objectImpl {
 	o._putProp("constructor", r.global.Symbol, true, false, true)
 	o._putProp("toString", r.newNativeFunc(r.symbolproto_tostring, nil, "toString", nil, 0), true, false, true)
 	o._putProp("valueOf", r.newNativeFunc(r.symbolproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
-	o.putSym(symToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, nil, "[Symbol.toPrimitive]", nil, 1), false, false, true), true)
-	o.putSym(symToStringTag, valueProp(newStringValue("Symbol"), false, false, true), true)
+	o._putSym(symToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, nil, "[Symbol.toPrimitive]", nil, 1), false, false, true))
+	o._putSym(symToStringTag, valueProp(newStringValue("Symbol"), false, false, true))
 
 	return o
 }

+ 10 - 10
builtin_weakmap.go

@@ -133,7 +133,7 @@ func (r *Runtime) weakMapProto_set(call FunctionCall) Value {
 	return call.This
 }
 
-func (r *Runtime) builtin_newWeakMap(args []Value) *Object {
+func (r *Runtime) builtin_newWeakMap(args []Value, proto *Object) *Object {
 	o := &Object{runtime: r}
 
 	wmo := &weakMapObject{}
@@ -141,19 +141,19 @@ func (r *Runtime) builtin_newWeakMap(args []Value) *Object {
 	wmo.val = o
 	wmo.extensible = true
 	o.self = wmo
-	wmo.prototype = r.global.WeakMapPrototype
+	wmo.prototype = proto
 	wmo.init()
 	if len(args) > 0 {
 		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
-			adder := wmo.getStr("set")
+			adder := wmo.getStr("set", nil)
 			iter := r.getIterator(arg, nil)
-			i0 := intToValue(0)
-			i1 := intToValue(1)
+			i0 := valueInt(0)
+			i1 := valueInt(1)
 			if adder == r.global.weakMapAdder {
 				r.iterate(iter, func(item Value) {
 					itemObj := r.toObject(item)
-					k := itemObj.self.get(i0)
-					v := nilSafe(itemObj.self.get(i1))
+					k := itemObj.self.getIdx(i0, nil)
+					v := nilSafe(itemObj.self.getIdx(i1, nil))
 					wmo.m.set(r.toObject(k), v)
 				})
 			} else {
@@ -163,8 +163,8 @@ func (r *Runtime) builtin_newWeakMap(args []Value) *Object {
 				}
 				r.iterate(iter, func(item Value) {
 					itemObj := r.toObject(item)
-					k := itemObj.self.get(i0)
-					v := itemObj.self.get(i1)
+					k := itemObj.self.getIdx(i0, nil)
+					v := itemObj.self.getIdx(i1, nil)
 					adderFn(FunctionCall{This: o, Arguments: []Value{k, v}})
 				})
 			}
@@ -183,7 +183,7 @@ func (r *Runtime) createWeakMapProto(val *Object) objectImpl {
 	o._putProp("has", r.newNativeFunc(r.weakMapProto_has, nil, "has", nil, 1), true, false, true)
 	o._putProp("get", r.newNativeFunc(r.weakMapProto_get, nil, "get", nil, 1), true, false, true)
 
-	o.put(symToStringTag, valueProp(asciiString(classWeakMap), false, false, true), true)
+	o._putSym(symToStringTag, valueProp(asciiString(classWeakMap), false, false, true))
 
 	return o
 }

+ 10 - 10
builtin_weakset.go

@@ -11,7 +11,7 @@ type weakSet struct {
 
 type weakSetObject struct {
 	baseObject
-	set *weakSet
+	s *weakSet
 }
 
 func newWeakSet() *weakSet {
@@ -22,7 +22,7 @@ func newWeakSet() *weakSet {
 
 func (ws *weakSetObject) init() {
 	ws.baseObject.init()
-	ws.set = newWeakSet()
+	ws.s = newWeakSet()
 }
 
 func (ws *weakSet) removePtr(ptr uintptr) {
@@ -72,7 +72,7 @@ func (r *Runtime) weakSetProto_add(call FunctionCall) Value {
 	if !ok {
 		panic(r.NewTypeError("Method WeakSet.prototype.add called on incompatible receiver %s", thisObj.String()))
 	}
-	wso.set.add(r.toObject(call.Argument(0)))
+	wso.s.add(r.toObject(call.Argument(0)))
 	return call.This
 }
 
@@ -83,7 +83,7 @@ func (r *Runtime) weakSetProto_delete(call FunctionCall) Value {
 		panic(r.NewTypeError("Method WeakSet.prototype.delete called on incompatible receiver %s", thisObj.String()))
 	}
 	obj, ok := call.Argument(0).(*Object)
-	if ok && wso.set.remove(obj) {
+	if ok && wso.s.remove(obj) {
 		return valueTrue
 	}
 	return valueFalse
@@ -96,7 +96,7 @@ func (r *Runtime) weakSetProto_has(call FunctionCall) Value {
 		panic(r.NewTypeError("Method WeakSet.prototype.has called on incompatible receiver %s", thisObj.String()))
 	}
 	obj, ok := call.Argument(0).(*Object)
-	if ok && wso.set.has(obj) {
+	if ok && wso.s.has(obj) {
 		return valueTrue
 	}
 	return valueFalse
@@ -113,7 +113,7 @@ func (r *Runtime) populateWeakSetGeneric(s *Object, adderValue Value, iterable V
 	})
 }
 
-func (r *Runtime) builtin_newWeakSet(args []Value) *Object {
+func (r *Runtime) builtin_newWeakSet(args []Value, proto *Object) *Object {
 	o := &Object{runtime: r}
 
 	wso := &weakSetObject{}
@@ -121,15 +121,15 @@ func (r *Runtime) builtin_newWeakSet(args []Value) *Object {
 	wso.val = o
 	wso.extensible = true
 	o.self = wso
-	wso.prototype = r.global.WeakSetPrototype
+	wso.prototype = proto
 	wso.init()
 	if len(args) > 0 {
 		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
-			adder := wso.getStr("add")
+			adder := wso.getStr("add", nil)
 			if adder == r.global.weakSetAdder {
 				if arr := r.checkStdArrayIter(arg); arr != nil {
 					for _, v := range arr.values {
-						wso.set.add(r.toObject(v))
+						wso.s.add(r.toObject(v))
 					}
 					return o
 				}
@@ -149,7 +149,7 @@ func (r *Runtime) createWeakSetProto(val *Object) objectImpl {
 	o._putProp("delete", r.newNativeFunc(r.weakSetProto_delete, nil, "delete", nil, 1), true, false, true)
 	o._putProp("has", r.newNativeFunc(r.weakSetProto_has, nil, "has", nil, 1), true, false, true)
 
-	o.put(symToStringTag, valueProp(asciiString(classWeakSet), false, false, true), true)
+	o._putSym(symToStringTag, valueProp(asciiString(classWeakSet), false, false, true))
 
 	return o
 }

+ 3 - 3
builtin_weakset_test.go

@@ -37,9 +37,9 @@ func TestWeakSetExpiry(t *testing.T) {
 	}
 	runtime.GC()
 	wso := vm.Get("s").ToObject(vm).self.(*weakSetObject)
-	wso.set.Lock()
-	l := len(wso.set.data)
-	wso.set.Unlock()
+	wso.s.Lock()
+	l := len(wso.s.data)
+	wso.s.Unlock()
 	if l > 0 {
 		t.Fatal("Object has not been removed")
 	}

+ 30 - 7
compiler_expr.go

@@ -115,6 +115,10 @@ type compiledNewExpr struct {
 	args   []compiledExpr
 }
 
+type compiledNewTarget struct {
+	baseCompiledExpr
+}
+
 type compiledSequenceExpr struct {
 	baseCompiledExpr
 	sequence []compiledExpr
@@ -232,6 +236,8 @@ func (c *compiler) compileExpression(v ast.Expression) compiledExpr {
 		return c.compileSequenceExpression(v)
 	case *ast.NewExpression:
 		return c.compileNewExpression(v)
+	case *ast.MetaProperty:
+		return c.compileMetaProperty(v)
 	default:
 		panic(fmt.Errorf("Unknown expression type: %T", v))
 	}
@@ -246,7 +252,7 @@ func (e *baseCompiledExpr) init(c *compiler, idx file.Idx) {
 	e.offset = int(idx) - 1
 }
 
-func (e *baseCompiledExpr) emitSetter(valueExpr compiledExpr) {
+func (e *baseCompiledExpr) emitSetter(compiledExpr) {
 	e.c.throwSyntaxError(e.offset, "Not a valid left-value expression")
 }
 
@@ -258,7 +264,7 @@ func (e *baseCompiledExpr) deleteExpr() compiledExpr {
 	return r
 }
 
-func (e *baseCompiledExpr) emitUnary(prepare, body func(), postfix bool, putOnStack bool) {
+func (e *baseCompiledExpr) emitUnary(func(), func(), bool, bool) {
 	e.c.throwSyntaxError(e.offset, "Not a valid left-value expression")
 }
 
@@ -923,6 +929,23 @@ func (c *compiler) compileNewExpression(v *ast.NewExpression) compiledExpr {
 	return r
 }
 
+func (e *compiledNewTarget) emitGetter(putOnStack bool) {
+	if putOnStack {
+		e.addSrcMap()
+		e.c.emit(loadNewTarget)
+	}
+}
+
+func (c *compiler) compileMetaProperty(v *ast.MetaProperty) compiledExpr {
+	if v.Meta.Name == "new" || v.Property.Name != "target" {
+		r := &compiledNewTarget{}
+		r.init(c, v.Idx0())
+		return r
+	}
+	c.throwSyntaxError(int(v.Idx)-1, "Unsupported meta property: %s.%s", v.Meta.Name, v.Property.Name)
+	return nil
+}
+
 func (e *compiledSequenceExpr) emitGetter(putOnStack bool) {
 	if len(e.sequence) > 0 {
 		for i := 0; i < len(e.sequence)-1; i++ {
@@ -950,11 +973,11 @@ func (c *compiler) compileSequenceExpression(v *ast.SequenceExpression) compiled
 
 func (c *compiler) emitThrow(v Value) {
 	if o, ok := v.(*Object); ok {
-		t := o.self.getStr("name").String()
+		t := o.self.getStr("name", nil).String()
 		switch t {
 		case "TypeError":
 			c.emit(getVar1(t))
-			msg := o.self.getStr("message")
+			msg := o.self.getStr("message", nil)
 			if msg != nil {
 				c.emit(loadVal(c.p.defineLiteralValue(msg)))
 				c.emit(_new(1))
@@ -1365,7 +1388,7 @@ func (c *compiler) compileObjectLiteral(v *ast.ObjectLiteral) compiledExpr {
 
 func (e *compiledArrayLiteral) emitGetter(putOnStack bool) {
 	e.addSrcMap()
-	objCount := uint32(0)
+	objCount := 0
 	for _, v := range e.expr.Value {
 		if v != nil {
 			e.c.compileExpression(v).emitGetter(true)
@@ -1374,11 +1397,11 @@ func (e *compiledArrayLiteral) emitGetter(putOnStack bool) {
 			e.c.emit(loadNil)
 		}
 	}
-	if objCount == uint32(len(e.expr.Value)) {
+	if objCount == len(e.expr.Value) {
 		e.c.emit(newArray(objCount))
 	} else {
 		e.c.emit(&newArraySparse{
-			l:        uint32(len(e.expr.Value)),
+			l:        len(e.expr.Value),
 			objCount: objCount,
 		})
 	}

+ 1 - 1
compiler_test.go

@@ -27,7 +27,7 @@ func testScript(script string, expectedResult Value, t *testing.T) {
 	t.Logf("stack size: %d", len(vm.stack))
 	t.Logf("stashAllocs: %d", vm.stashAllocs)
 
-	v := vm.r.globalObject.self.getStr("rv")
+	v := vm.r.globalObject.self.getStr("rv", nil)
 	if v == nil {
 		v = _undefined
 	}

+ 2 - 2
date.go

@@ -68,13 +68,13 @@ func dateParse(date string) (time.Time, bool) {
 	return t, err == nil && unix >= -8640000000000000 && unix <= 8640000000000000
 }
 
-func (r *Runtime) newDateObject(t time.Time, isSet bool) *Object {
+func (r *Runtime) newDateObject(t time.Time, isSet bool, proto *Object) *Object {
 	v := &Object{runtime: r}
 	d := &dateObject{}
 	v.self = d
 	d.val = v
 	d.class = classDate
-	d.prototype = r.global.DatePrototype
+	d.prototype = proto
 	d.extensible = true
 	d.init()
 	d.time = t.In(time.Local)

+ 58 - 56
func.go

@@ -20,7 +20,7 @@ type nativeFuncObject struct {
 	baseFuncObject
 
 	f         func(FunctionCall) Value
-	construct func(args []Value) *Object
+	construct func(args []Value, newTarget *Object) *Object
 }
 
 type boundFuncObject struct {
@@ -45,25 +45,25 @@ func (f *funcObject) _addProto(n string) Value {
 	return nil
 }
 
-func (f *funcObject) getPropStr(name string) Value {
+func (f *funcObject) getStr(p string, receiver Value) Value {
+	return f.getStrWithOwnProp(f.getOwnPropStr(p), p, receiver)
+}
+
+func (f *funcObject) getOwnPropStr(name string) Value {
 	if v := f._addProto(name); v != nil {
 		return v
 	}
 
-	return f.baseObject.getPropStr(name)
+	return f.baseObject.getOwnPropStr(name)
 }
 
-func (f *funcObject) putStr(name string, val Value, throw bool) {
+func (f *funcObject) setOwnStr(name string, val Value, throw bool) bool {
 	f._addProto(name)
-	f.baseObject.putStr(name, val, throw)
+	return f.baseObject.setOwnStr(name, val, throw)
 }
 
-func (f *funcObject) put(n Value, val Value, throw bool) {
-	if s, ok := n.(*valueSymbol); ok {
-		f.putSym(s, val, throw)
-	} else {
-		f.putStr(n.String(), val, throw)
-	}
+func (f *funcObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return f._setForeignStr(name, f.getOwnPropStr(name), val, receiver, throw)
 }
 
 func (f *funcObject) deleteStr(name string, throw bool) bool {
@@ -71,55 +71,49 @@ func (f *funcObject) deleteStr(name string, throw bool) bool {
 	return f.baseObject.deleteStr(name, throw)
 }
 
-func (f *funcObject) delete(n Value, throw bool) bool {
-	if s, ok := n.(*valueSymbol); ok {
-		return f.deleteSym(s, throw)
-	}
-	return f.deleteStr(n.String(), throw)
-}
-
 func (f *funcObject) addPrototype() Value {
 	proto := f.val.runtime.NewObject()
 	proto.self._putProp("constructor", f.val, true, false, true)
 	return f._putProp("prototype", proto, true, false, false)
 }
 
-func (f *funcObject) hasOwnProperty(n Value) bool {
-	if r := f.baseObject.hasOwnProperty(n); r {
+func (f *funcObject) hasOwnPropertyStr(name string) bool {
+	if r := f.baseObject.hasOwnPropertyStr(name); r {
 		return true
 	}
 
-	name := n.String()
 	if name == "prototype" {
 		return true
 	}
 	return false
 }
 
-func (f *funcObject) hasOwnPropertyStr(name string) bool {
-	if r := f.baseObject.hasOwnPropertyStr(name); r {
-		return true
-	}
-
-	if name == "prototype" {
-		return true
+func (f *funcObject) ownKeys(all bool, accum []Value) []Value {
+	if all {
+		if _, exists := f.values["prototype"]; !exists {
+			accum = append(accum, asciiString("prototype"))
+		}
 	}
-	return false
+	return f.baseFuncObject.ownKeys(all, accum)
 }
 
-func (f *funcObject) construct(args []Value) *Object {
-	proto := f.getStr("prototype")
+func (f *funcObject) construct(args []Value, newTarget *Object) *Object {
+	if newTarget == nil {
+		newTarget = f.val
+	}
+	proto := newTarget.self.getStr("prototype", nil)
 	var protoObj *Object
 	if p, ok := proto.(*Object); ok {
 		protoObj = p
 	} else {
 		protoObj = f.val.runtime.global.ObjectPrototype
 	}
+
 	obj := f.val.runtime.newBaseObject(protoObj, classObject).val
-	ret := f.Call(FunctionCall{
+	ret := f.call(FunctionCall{
 		This:      obj,
 		Arguments: args,
-	})
+	}, newTarget)
 
 	if ret, ok := ret.(*Object); ok {
 		return ret
@@ -128,6 +122,10 @@ func (f *funcObject) construct(args []Value) *Object {
 }
 
 func (f *funcObject) Call(call FunctionCall) Value {
+	return f.call(call, nil)
+}
+
+func (f *funcObject) call(call FunctionCall, newTarget Value) Value {
 	vm := f.val.runtime.vm
 	pc := vm.pc
 
@@ -154,6 +152,7 @@ func (f *funcObject) Call(call FunctionCall) Value {
 	vm.args = len(call.Arguments)
 	vm.prg = f.prg
 	vm.stash = f.stash
+	vm.newTarget = newTarget
 	vm.pc = 0
 	vm.run()
 	vm.pc = pc
@@ -173,12 +172,18 @@ func (f *funcObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return f.Call, true
 }
 
+func (f *funcObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
+	return f.construct
+}
+
 func (f *baseFuncObject) init(name string, length int) {
 	f.baseObject.init()
 
-	f.nameProp.configurable = true
-	f.nameProp.value = newStringValue(name)
-	f._put("name", &f.nameProp)
+	if name != "" {
+		f.nameProp.configurable = true
+		f.nameProp.value = newStringValue(name)
+		f._put("name", &f.nameProp)
+	}
 
 	f.lenProp.configurable = true
 	f.lenProp.value = valueInt(length)
@@ -187,7 +192,7 @@ func (f *baseFuncObject) init(name string, length int) {
 
 func (f *baseFuncObject) hasInstance(v Value) bool {
 	if v, ok := v.(*Object); ok {
-		o := f.val.self.getStr("prototype")
+		o := f.val.self.getStr("prototype", nil)
 		if o1, ok := o.(*Object); ok {
 			for {
 				v = v.self.proto()
@@ -207,7 +212,7 @@ func (f *baseFuncObject) hasInstance(v Value) bool {
 }
 
 func (f *nativeFuncObject) defaultConstruct(ccall func(ConstructorCall) *Object, args []Value) *Object {
-	proto := f.getStr("prototype")
+	proto := f.getStr("prototype", nil)
 	var protoObj *Object
 	if p, ok := proto.(*Object); ok {
 		protoObj = p
@@ -233,19 +238,20 @@ func (f *nativeFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return nil, false
 }
 
-func (f *boundFuncObject) getPropStr(name string) Value {
+func (f *nativeFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
+	return f.construct
+}
+
+func (f *boundFuncObject) getStr(p string, receiver Value) Value {
+	return f.getStrWithOwnProp(f.getOwnPropStr(p), p, receiver)
+}
+
+func (f *boundFuncObject) getOwnPropStr(name string) Value {
 	if name == "caller" || name == "arguments" {
-		//f.runtime.typeErrorResult(true, "'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.")
 		return f.val.runtime.global.throwerProperty
 	}
-	return f.nativeFuncObject.getPropStr(name)
-}
 
-func (f *boundFuncObject) delete(n Value, throw bool) bool {
-	if s, ok := n.(*valueSymbol); ok {
-		return f.deleteSym(s, throw)
-	}
-	return f.deleteStr(n.String(), throw)
+	return f.nativeFuncObject.getOwnPropStr(name)
 }
 
 func (f *boundFuncObject) deleteStr(name string, throw bool) bool {
@@ -255,19 +261,15 @@ func (f *boundFuncObject) deleteStr(name string, throw bool) bool {
 	return f.nativeFuncObject.deleteStr(name, throw)
 }
 
-func (f *boundFuncObject) putStr(name string, val Value, throw bool) {
+func (f *boundFuncObject) setOwnStr(name string, val Value, throw bool) bool {
 	if name == "caller" || name == "arguments" {
-		f.val.runtime.typeErrorResult(true, "'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.")
+		panic(f.val.runtime.NewTypeError("'caller' and 'arguments' are restricted function properties and cannot be accessed in this context."))
 	}
-	f.nativeFuncObject.putStr(name, val, throw)
+	return f.nativeFuncObject.setOwnStr(name, val, throw)
 }
 
-func (f *boundFuncObject) put(n Value, val Value, throw bool) {
-	if s, ok := n.(*valueSymbol); ok {
-		f.putSym(s, val, throw)
-		return
-	}
-	f.putStr(n.String(), val, throw)
+func (f *boundFuncObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return f._setForeignStr(name, f.getOwnPropStr(name), val, receiver, throw)
 }
 
 func (f *boundFuncObject) hasInstance(v Value) bool {

File diff suppressed because it is too large
+ 501 - 232
object.go


+ 31 - 48
object_args.go

@@ -10,15 +10,16 @@ type mappedProperty struct {
 	v *Value
 }
 
-func (a *argumentsObject) getPropStr(name string) Value {
-	if prop, ok := a.values[name].(*mappedProperty); ok {
-		return *prop.v
-	}
-	return a.baseObject.getPropStr(name)
+func (a *argumentsObject) getStr(name string, receiver Value) Value {
+	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 }
 
-func (a *argumentsObject) getProp(n Value) Value {
-	return a.getPropStr(n.String())
+func (a *argumentsObject) getOwnPropStr(name string) Value {
+	if mapped, ok := a.values[name].(*mappedProperty); ok {
+		return *mapped.v
+	}
+
+	return a.baseObject.getOwnPropStr(name)
 }
 
 func (a *argumentsObject) init() {
@@ -26,15 +27,23 @@ func (a *argumentsObject) init() {
 	a._putProp("length", intToValue(int64(a.length)), true, false, true)
 }
 
-func (a *argumentsObject) put(n Value, val Value, throw bool) {
-	if s, ok := n.(*valueSymbol); ok {
-		a.putSym(s, val, throw)
-		return
+func (a *argumentsObject) setOwnStr(name string, val Value, throw bool) bool {
+	if prop, ok := a.values[name].(*mappedProperty); ok {
+		if !prop.writable {
+			a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name)
+			return false
+		}
+		*prop.v = val
+		return true
 	}
-	a.putStr(n.String(), val, throw)
+	return a.baseObject.setOwnStr(name, val, throw)
+}
+
+func (a *argumentsObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 }
 
-func (a *argumentsObject) putStr(name string, val Value, throw bool) {
+/*func (a *argumentsObject) putStr(name string, val Value, throw bool) {
 	if prop, ok := a.values[name].(*mappedProperty); ok {
 		if !prop.writable {
 			a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name)
@@ -44,7 +53,7 @@ func (a *argumentsObject) putStr(name string, val Value, throw bool) {
 		return
 	}
 	a.baseObject.putStr(name, val, throw)
-}
+}*/
 
 func (a *argumentsObject) deleteStr(name string, throw bool) bool {
 	if prop, ok := a.values[name].(*mappedProperty); ok {
@@ -58,13 +67,6 @@ func (a *argumentsObject) deleteStr(name string, throw bool) bool {
 	return a.baseObject.deleteStr(name, throw)
 }
 
-func (a *argumentsObject) delete(n Value, throw bool) bool {
-	if s, ok := n.(*valueSymbol); ok {
-		return a.deleteSym(s, throw)
-	}
-	return a.deleteStr(n.String(), throw)
-}
-
 type argumentsPropIter struct {
 	wrapped iterNextFunc
 }
@@ -81,24 +83,13 @@ func (i *argumentsPropIter) next() (propIterItem, iterNextFunc) {
 	return item, i.next
 }
 
-func (a *argumentsObject) _enumerate(recursive bool) iterNextFunc {
-	return (&argumentsPropIter{
-		wrapped: a.baseObject._enumerate(recursive),
-	}).next
-
-}
-
-func (a *argumentsObject) enumerate(all, recursive bool) iterNextFunc {
-	return (&argumentsPropIter{
-		wrapped: a.baseObject.enumerate(all, recursive),
-	}).next
+func (a *argumentsObject) enumerateUnfiltered() iterNextFunc {
+	return a.recursiveIter((&argumentsPropIter{
+		wrapped: a.ownIter(),
+	}).next)
 }
 
-func (a *argumentsObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	if _, ok := n.(*valueSymbol); ok {
-		return a.baseObject.defineOwnProperty(n, descr, throw)
-	}
-	name := n.String()
+func (a *argumentsObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
 	if mapped, ok := a.values[name].(*mappedProperty); ok {
 		existing := &valueProperty{
 			configurable: mapped.configurable,
@@ -107,7 +98,7 @@ func (a *argumentsObject) defineOwnProperty(n Value, descr propertyDescr, throw
 			value:        mapped.get(a.val),
 		}
 
-		val, ok := a.baseObject._defineOwnProperty(n, existing, descr, throw)
+		val, ok := a.baseObject._defineOwnProperty(name, existing, descr, throw)
 		if !ok {
 			return false
 		}
@@ -131,21 +122,13 @@ func (a *argumentsObject) defineOwnProperty(n Value, descr propertyDescr, throw
 		return true
 	}
 
-	return a.baseObject.defineOwnProperty(n, descr, throw)
-}
-
-func (a *argumentsObject) getOwnPropStr(name string) Value {
-	if mapped, ok := a.values[name].(*mappedProperty); ok {
-		return *mapped.v
-	}
-
-	return a.baseObject.getOwnPropStr(name)
+	return a.baseObject.defineOwnPropertyStr(name, descr, throw)
 }
 
 func (a *argumentsObject) export() interface{} {
 	arr := make([]interface{}, a.length)
 	for i := range arr {
-		v := a.get(intToValue(int64(i)))
+		v := a.getIdx(valueInt(int64(i)), nil)
 		if v != nil {
 			arr[i] = v.Export()
 		}

+ 50 - 106
object_gomap.go

@@ -2,7 +2,6 @@ package goja
 
 import (
 	"reflect"
-	"strconv"
 )
 
 type objectGoMapSimple struct {
@@ -17,10 +16,6 @@ func (o *objectGoMapSimple) init() {
 	o.extensible = true
 }
 
-func (o *objectGoMapSimple) _get(n Value) Value {
-	return o._getStr(n.String())
-}
-
 func (o *objectGoMapSimple) _getStr(name string) Value {
 	v, exists := o.data[name]
 	if !exists {
@@ -29,93 +24,73 @@ func (o *objectGoMapSimple) _getStr(name string) Value {
 	return o.val.runtime.ToValue(v)
 }
 
-func (o *objectGoMapSimple) get(n Value) Value {
-	return o.getStr(n.String())
-}
-
-func (o *objectGoMapSimple) getProp(n Value) Value {
-	return o.getPropStr(n.String())
-}
-
-func (o *objectGoMapSimple) getPropStr(name string) Value {
+func (o *objectGoMapSimple) getStr(name string, receiver Value) Value {
 	if v := o._getStr(name); v != nil {
 		return v
 	}
-	return o.baseObject.getPropStr(name)
-}
-
-func (o *objectGoMapSimple) getStr(name string) Value {
-	if v := o._getStr(name); v != nil {
-		return v
-	}
-	return o.baseObject._getStr(name)
+	return o.baseObject.getStr(name, receiver)
 }
 
 func (o *objectGoMapSimple) getOwnPropStr(name string) Value {
 	if v := o._getStr(name); v != nil {
 		return v
 	}
-	return o.baseObject.getOwnPropStr(name)
-}
-
-func (o *objectGoMapSimple) put(n Value, val Value, throw bool) {
-	if _, ok := n.(*valueSymbol); ok {
-		o.val.runtime.typeErrorResult(throw, "Cannot set Symbol properties on Go maps")
-		return
-	}
-	o.putStr(n.String(), val, throw)
+	return nil
 }
 
-func (o *objectGoMapSimple) _hasStr(name string) bool {
-	_, exists := o.data[name]
-	return exists
-}
-
-func (o *objectGoMapSimple) _has(n Value) bool {
-	return o._hasStr(n.String())
-}
-
-func (o *objectGoMapSimple) putStr(name string, val Value, throw bool) {
-	if o.extensible || o._hasStr(name) {
+func (o *objectGoMapSimple) setOwnStr(name string, val Value, throw bool) bool {
+	if _, exists := o.data[name]; exists {
 		o.data[name] = val.Export()
+		return true
+	}
+	if proto := o.prototype; proto != nil {
+		// we know it's foreign because prototype loops are not allowed
+		if res, ok := proto.self.setForeignStr(name, val, o.val, throw); ok {
+			return res
+		}
+	}
+	// new property
+	if !o.extensible {
+		o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name)
+		return false
 	} else {
-		o.val.runtime.typeErrorResult(throw, "Host object is not extensible")
+		o.data[name] = val.Export()
 	}
+	return true
 }
 
-func (o *objectGoMapSimple) hasProperty(n Value) bool {
-	if o._has(n) {
-		return true
+func trueValIfPresent(present bool) Value {
+	if present {
+		return valueTrue
 	}
-	return o.baseObject.hasProperty(n)
+	return nil
 }
 
-func (o *objectGoMapSimple) hasPropertyStr(name string) bool {
-	if o._hasStr(name) {
-		return true
-	}
-	return o.baseObject.hasOwnPropertyStr(name)
+func (o *objectGoMapSimple) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignStr(name, trueValIfPresent(o._hasStr(name)), val, receiver, throw)
 }
 
-func (o *objectGoMapSimple) hasOwnProperty(n Value) bool {
-	return o._has(n)
+func (o *objectGoMapSimple) _hasStr(name string) bool {
+	_, exists := o.data[name]
+	return exists
 }
 
 func (o *objectGoMapSimple) hasOwnPropertyStr(name string) bool {
 	return o._hasStr(name)
 }
 
-func (o *objectGoMapSimple) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
-	o.putStr(name, value, false)
-	return value
-}
-
-func (o *objectGoMapSimple) defineOwnProperty(name Value, descr propertyDescr, throw bool) bool {
+func (o *objectGoMapSimple) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
 	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 	}
-	o.put(name, descr.Value, throw)
-	return true
+
+	if o.extensible || o._hasStr(name) {
+		o.data[name] = descr.Value.Export()
+		return true
+	}
+
+	o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", name)
+	return false
 }
 
 /*
@@ -136,23 +111,14 @@ func (o *objectGoMapSimple) assertCallable() (call func(FunctionCall) Value, ok
 }
 */
 
-func (o *objectGoMapSimple) deleteStr(name string, throw bool) bool {
+func (o *objectGoMapSimple) deleteStr(name string, _ bool) bool {
 	delete(o.data, name)
 	return true
 }
 
-func (o *objectGoMapSimple) delete(name Value, throw bool) bool {
-	if _, ok := name.(*valueSymbol); ok {
-		return true
-	}
-
-	return o.deleteStr(name.String(), throw)
-}
-
 type gomapPropIter struct {
 	o         *objectGoMapSimple
 	propNames []string
-	recursive bool
 	idx       int
 }
 
@@ -165,33 +131,29 @@ func (i *gomapPropIter) next() (propIterItem, iterNextFunc) {
 		}
 	}
 
-	if i.recursive {
-		return i.o.prototype.self._enumerate(true)()
-	}
-
 	return propIterItem{}, nil
 }
 
-func (o *objectGoMapSimple) enumerate(all, recursive bool) iterNextFunc {
-	return (&propFilterIter{
-		wrapped: o._enumerate(recursive),
-		all:     all,
-		seen:    make(map[string]bool),
-	}).next
-}
-
-func (o *objectGoMapSimple) _enumerate(recursive bool) iterNextFunc {
+func (o *objectGoMapSimple) enumerateUnfiltered() iterNextFunc {
 	propNames := make([]string, len(o.data))
 	i := 0
 	for key := range o.data {
 		propNames[i] = key
 		i++
 	}
-	return (&gomapPropIter{
+
+	return o.recursiveIter((&gomapPropIter{
 		o:         o,
 		propNames: propNames,
-		recursive: recursive,
-	}).next
+	}).next)
+}
+
+func (o *objectGoMapSimple) ownKeys(_ bool, accum []Value) []Value {
+	// all own keys are enumerable
+	for key := range o.data {
+		accum = append(accum, newStringValue(key))
+	}
+	return accum
 }
 
 func (o *objectGoMapSimple) export() interface{} {
@@ -208,21 +170,3 @@ func (o *objectGoMapSimple) equal(other objectImpl) bool {
 	}
 	return false
 }
-
-func (o *objectGoMapSimple) sortLen() int64 {
-	return int64(len(o.data))
-}
-
-func (o *objectGoMapSimple) sortGet(i int64) Value {
-	return o.getStr(strconv.FormatInt(i, 10))
-}
-
-func (o *objectGoMapSimple) swap(i, j int64) {
-	ii := strconv.FormatInt(i, 10)
-	jj := strconv.FormatInt(j, 10)
-	x := o.getStr(ii)
-	y := o.getStr(jj)
-
-	o.putStr(ii, y, false)
-	o.putStr(jj, x, false)
-}

+ 115 - 80
object_gomap_reflect.go

@@ -15,10 +15,6 @@ func (o *objectGoMapReflect) init() {
 }
 
 func (o *objectGoMapReflect) toKey(n Value, throw bool) reflect.Value {
-	if _, ok := n.(*valueSymbol); ok {
-		o.val.runtime.typeErrorResult(throw, "Cannot set Symbol properties on Go maps")
-		return reflect.Value{}
-	}
 	key, err := o.val.runtime.toReflectValue(n, o.keyType)
 	if err != nil {
 		o.val.runtime.typeErrorResult(throw, "map key conversion error: %v", err)
@@ -58,26 +54,18 @@ func (o *objectGoMapReflect) _getStr(name string) Value {
 	return nil
 }
 
-func (o *objectGoMapReflect) get(n Value) Value {
-	if v := o._get(n); v != nil {
+func (o *objectGoMapReflect) getStr(name string, receiver Value) Value {
+	if v := o._getStr(name); v != nil {
 		return v
 	}
-	return o.objectGoReflect.get(n)
+	return o.objectGoReflect.getStr(name, receiver)
 }
 
-func (o *objectGoMapReflect) getStr(name string) Value {
-	if v := o._getStr(name); v != nil {
+func (o *objectGoMapReflect) getIdx(idx valueInt, receiver Value) Value {
+	if v := o._get(idx); v != nil {
 		return v
 	}
-	return o.objectGoReflect.getStr(name)
-}
-
-func (o *objectGoMapReflect) getProp(n Value) Value {
-	return o.get(n)
-}
-
-func (o *objectGoMapReflect) getPropStr(name string) Value {
-	return o.getStr(name)
+	return o.objectGoReflect.getIdx(idx, receiver)
 }
 
 func (o *objectGoMapReflect) getOwnPropStr(name string) Value {
@@ -91,6 +79,17 @@ func (o *objectGoMapReflect) getOwnPropStr(name string) Value {
 	return o.objectGoReflect.getOwnPropStr(name)
 }
 
+func (o *objectGoMapReflect) getOwnPropIdx(idx valueInt) Value {
+	if v := o._get(idx); v != nil {
+		return &valueProperty{
+			value:      v,
+			writable:   true,
+			enumerable: true,
+		}
+	}
+	return o.objectGoReflect.getOwnPropStr(idx.String())
+}
+
 func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) {
 	v, err := o.val.runtime.toReflectValue(val, o.valueType)
 	if err != nil {
@@ -101,74 +100,113 @@ func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool
 	return v, true
 }
 
-func (o *objectGoMapReflect) put(key, val Value, throw bool) {
-	k := o.toKey(key, throw)
-	v, ok := o.toValue(val, throw)
-	if !ok {
-		return
+func (o *objectGoMapReflect) _put(key reflect.Value, val Value, throw bool) bool {
+	if key.IsValid() {
+		if o.extensible || o.value.MapIndex(key).IsValid() {
+			v, ok := o.toValue(val, throw)
+			if !ok {
+				return false
+			}
+			o.value.SetMapIndex(key, v)
+		} else {
+			o.val.runtime.typeErrorResult(throw, "Cannot set property %s, object is not extensible", key.String())
+			return false
+		}
+		return true
 	}
-	o.value.SetMapIndex(k, v)
+	return false
 }
 
-func (o *objectGoMapReflect) putStr(name string, val Value, throw bool) {
-	k := o.strToKey(name, throw)
-	if !k.IsValid() {
-		return
-	}
-	v, ok := o.toValue(val, throw)
-	if !ok {
-		return
+func (o *objectGoMapReflect) setOwnStr(name string, val Value, throw bool) bool {
+	key := o.strToKey(name, false)
+	if !key.IsValid() || !o.value.MapIndex(key).IsValid() {
+		if proto := o.prototype; proto != nil {
+			// we know it's foreign because prototype loops are not allowed
+			if res, ok := proto.self.setForeignStr(name, val, o.val, throw); ok {
+				return res
+			}
+		}
+		// new property
+		if !o.extensible {
+			o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name)
+			return false
+		} else {
+			if throw && !key.IsValid() {
+				o.strToKey(name, true)
+				return false
+			}
+		}
 	}
-	o.value.SetMapIndex(k, v)
+	o._put(key, val, throw)
+	return true
 }
 
-func (o *objectGoMapReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
-	o.putStr(name, value, true)
-	return value
+func (o *objectGoMapReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool {
+	key := o.toKey(idx, false)
+	if !key.IsValid() || !o.value.MapIndex(key).IsValid() {
+		if proto := o.prototype; proto != nil {
+			// we know it's foreign because prototype loops are not allowed
+			if res, ok := proto.self.setForeignIdx(idx, val, o.val, throw); ok {
+				return res
+			}
+		}
+		// new property
+		if !o.extensible {
+			o.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
+			return false
+		} else {
+			if throw && !key.IsValid() {
+				o.toKey(idx, true)
+				return false
+			}
+		}
+	}
+	o._put(key, val, throw)
+	return true
 }
 
-func (o *objectGoMapReflect) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	if !o.val.runtime.checkHostObjectPropertyDescr(n, descr, throw) {
-		return false
-	}
+func (o *objectGoMapReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
+}
 
-	o.put(n, descr.Value, throw)
-	return true
+func (o *objectGoMapReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw)
 }
 
-func (o *objectGoMapReflect) hasOwnPropertyStr(name string) bool {
-	key := o.strToKey(name, false)
-	if !key.IsValid() {
+func (o *objectGoMapReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 		return false
 	}
-	return o.value.MapIndex(key).IsValid()
+
+	return o._put(o.strToKey(name, throw), descr.Value, throw)
 }
 
-func (o *objectGoMapReflect) hasOwnProperty(n Value) bool {
-	key := o.toKey(n, false)
-	if !key.IsValid() {
+func (o *objectGoMapReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
+	if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) {
 		return false
 	}
 
-	return o.value.MapIndex(key).IsValid()
+	return o._put(o.toKey(idx, throw), descr.Value, throw)
 }
 
-func (o *objectGoMapReflect) hasProperty(n Value) bool {
-	if o.hasOwnProperty(n) {
+func (o *objectGoMapReflect) hasOwnPropertyStr(name string) bool {
+	key := o.strToKey(name, false)
+	if key.IsValid() && o.value.MapIndex(key).IsValid() {
 		return true
 	}
-	return o.objectGoReflect.hasProperty(n)
+	return false
 }
 
-func (o *objectGoMapReflect) hasPropertyStr(name string) bool {
-	if o.hasOwnPropertyStr(name) {
+func (o *objectGoMapReflect) hasOwnPropertyIdx(idx valueInt) bool {
+	key := o.toKey(idx, false)
+	if key.IsValid() && o.value.MapIndex(key).IsValid() {
 		return true
 	}
-	return o.objectGoReflect.hasPropertyStr(name)
+	return false
 }
 
-func (o *objectGoMapReflect) delete(n Value, throw bool) bool {
-	key := o.toKey(n, throw)
+func (o *objectGoMapReflect) deleteStr(name string, throw bool) bool {
+	key := o.strToKey(name, throw)
 	if !key.IsValid() {
 		return false
 	}
@@ -176,8 +214,8 @@ func (o *objectGoMapReflect) delete(n Value, throw bool) bool {
 	return true
 }
 
-func (o *objectGoMapReflect) deleteStr(name string, throw bool) bool {
-	key := o.strToKey(name, throw)
+func (o *objectGoMapReflect) deleteIdx(idx valueInt, throw bool) bool {
+	key := o.toKey(idx, throw)
 	if !key.IsValid() {
 		return false
 	}
@@ -186,10 +224,9 @@ func (o *objectGoMapReflect) deleteStr(name string, throw bool) bool {
 }
 
 type gomapReflectPropIter struct {
-	o         *objectGoMapReflect
-	keys      []reflect.Value
-	idx       int
-	recursive bool
+	o    *objectGoMapReflect
+	keys []reflect.Value
+	idx  int
 }
 
 func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) {
@@ -202,28 +239,26 @@ func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) {
 		}
 	}
 
-	if i.recursive {
-		return i.o.objectGoReflect._enumerate(true)()
+	if i.o.prototype != nil {
+		return i.o.prototype.self.enumerateUnfiltered()()
 	}
-
 	return propIterItem{}, nil
 }
 
-func (o *objectGoMapReflect) _enumerate(recusrive bool) iterNextFunc {
-	r := &gomapReflectPropIter{
-		o:         o,
-		keys:      o.value.MapKeys(),
-		recursive: recusrive,
-	}
-	return r.next
+func (o *objectGoMapReflect) enumerateUnfiltered() iterNextFunc {
+	return (&gomapReflectPropIter{
+		o:    o,
+		keys: o.value.MapKeys(),
+	}).next
 }
 
-func (o *objectGoMapReflect) enumerate(all, recursive bool) iterNextFunc {
-	return (&propFilterIter{
-		wrapped: o._enumerate(recursive),
-		all:     all,
-		seen:    make(map[string]bool),
-	}).next
+func (o *objectGoMapReflect) ownKeys(_ bool, accum []Value) []Value {
+	// all own keys are enumerable
+	for _, key := range o.value.MapKeys() {
+		accum = append(accum, newStringValue(key.String()))
+	}
+
+	return accum
 }
 
 func (o *objectGoMapReflect) equal(other objectImpl) bool {

+ 86 - 1
object_gomap_reflect_test.go

@@ -1,6 +1,8 @@
 package goja
 
-import "testing"
+import (
+	"testing"
+)
 
 func TestGoMapReflectGetSet(t *testing.T) {
 	const SCRIPT = `
@@ -164,3 +166,86 @@ func TestGoMapReflectWithMethods(t *testing.T) {
 	}
 
 }
+
+func TestGoMapReflectWithProto(t *testing.T) {
+	vm := New()
+	m := map[string]string{
+		"t": "42",
+	}
+	vm.Set("m", m)
+	_, err := vm.RunString(TESTLIB + `
+	(function() {
+	'use strict';
+	var proto = {};
+	var getterAllowed = false;
+	var setterAllowed = false;
+	var tHolder = "proto t";
+	Object.defineProperty(proto, "t", {
+		get: function() {
+			if (!getterAllowed) throw new Error("getter is called");
+			return tHolder;
+		},
+		set: function(v) {
+			if (!setterAllowed) throw new Error("setter is called");
+			tHolder = v;
+		}
+	});
+	var t1Holder;
+	Object.defineProperty(proto, "t1", {
+		get: function() {
+			return t1Holder;
+		},
+		set: function(v) {
+			t1Holder = v;
+		}
+	});
+	Object.setPrototypeOf(m, proto);
+	assert.sameValue(m.t, "42");
+	m.t = 43;
+	assert.sameValue(m.t, "43");
+	t1Holder = "test";
+	assert.sameValue(m.t1, "test");
+	m.t1 = "test1";
+	assert.sameValue(m.t1, "test1");
+	delete m.t;
+	getterAllowed = true;
+	assert.sameValue(m.t, "proto t", "after delete");
+	setterAllowed = true;
+	m.t = true;
+	assert.sameValue(m.t, true, "m.t === true");
+	assert.sameValue(tHolder, true, "tHolder === true");
+	Object.preventExtensions(m);
+	assert.throws(TypeError, function() {
+		m.t2 = 1;
+	});
+	m.t1 = "test2";
+	assert.sameValue(m.t1, "test2");
+	})();
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoMapReflectProtoProp(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+	"use strict";
+	var proto = {};
+	Object.defineProperty(proto, "ro", {value: 42});
+	Object.setPrototypeOf(m, proto);
+	assert.throws(TypeError, function() {
+		m.ro = 43;
+	});
+	Object.defineProperty(m, "ro", {value: 43});
+	assert.sameValue(m.ro, "43");
+	})();
+	`
+
+	r := New()
+	r.Set("m", map[string]string{})
+	_, err := r.RunString(TESTLIB + SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 124 - 0
object_gomap_test.go

@@ -182,3 +182,127 @@ func TestGoMapExtensibility(t *testing.T) {
 	}
 
 }
+
+func TestGoMapWithProto(t *testing.T) {
+	vm := New()
+	m := map[string]interface{}{
+		"t": "42",
+	}
+	vm.Set("m", m)
+	_, err := vm.RunString(TESTLIB + `
+	(function() {
+	'use strict';
+	var proto = {};
+	var getterAllowed = false;
+	var setterAllowed = false;
+	var tHolder = "proto t";
+	Object.defineProperty(proto, "t", {
+		get: function() {
+			if (!getterAllowed) throw new Error("getter is called");
+			return tHolder;
+		},
+		set: function(v) {
+			if (!setterAllowed) throw new Error("setter is called");
+			tHolder = v;
+		}
+	});
+	var t1Holder;
+	Object.defineProperty(proto, "t1", {
+		get: function() {
+			return t1Holder;
+		},
+		set: function(v) {
+			t1Holder = v;
+		}
+	});
+	Object.setPrototypeOf(m, proto);
+	assert.sameValue(m.t, "42");
+	m.t = 43;
+	assert.sameValue(m.t, 43);
+	t1Holder = "test";
+	assert.sameValue(m.t1, "test");
+	m.t1 = "test1";
+	assert.sameValue(m.t1, "test1");
+	delete m.t;
+	getterAllowed = true;
+	assert.sameValue(m.t, "proto t", "after delete");
+	setterAllowed = true;
+	m.t = true;
+	assert.sameValue(m.t, true);
+	assert.sameValue(tHolder, true);
+	Object.preventExtensions(m);
+	assert.throws(TypeError, function() {
+		m.t2 = 1;
+	});
+	m.t1 = "test2";
+	assert.sameValue(m.t1, "test2");
+	})();
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoMapProtoProp(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+	"use strict";
+	var proto = {};
+	Object.defineProperty(proto, "ro", {value: 42});
+	Object.setPrototypeOf(m, proto);
+	assert.throws(TypeError, function() {
+		m.ro = 43;
+	});
+	Object.defineProperty(m, "ro", {value: 43});
+	assert.sameValue(m.ro, 43);
+	})();
+	`
+
+	r := New()
+	r.Set("m", map[string]interface{}{})
+	_, err := r.RunString(TESTLIB + SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoMapProtoPropChain(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+	"use strict";
+	var p1 = Object.create(null);
+	m.__proto__ = p1;
+	
+	Object.defineProperty(p1, "test", {
+		value: 42
+	});
+	
+	Object.defineProperty(m, "test", {
+		value: 43,
+		writable: true,
+	});
+	var o = Object.create(m);
+	o.test = 44;
+	assert.sameValue(o.test, 44);
+
+	var sym = Symbol(true);
+	Object.defineProperty(p1, sym, {
+		value: 42
+	});
+	
+	Object.defineProperty(m, sym, {
+		value: 43,
+		writable: true,
+	});
+	o[sym] = 44;
+	assert.sameValue(o[sym], 44);
+	})();
+	`
+
+	r := New()
+	r.Set("m", map[string]interface{}{})
+	_, err := r.RunString(TESTLIB + SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 68 - 110
object_goreflect.go

@@ -62,6 +62,7 @@ func (o *objectGoReflect) init() {
 		o.class = classObject
 		o.prototype = o.val.runtime.global.ObjectPrototype
 	}
+	o.extensible = true
 
 	o.baseObject._putProp("toString", o.val.runtime.newNativeFunc(o.toStringFunc, nil, "toString", nil, 0), true, false, true)
 	o.baseObject._putProp("valueOf", o.val.runtime.newNativeFunc(o.valueOfFunc, nil, "valueOf", nil, 0), true, false, true)
@@ -74,16 +75,19 @@ func (o *objectGoReflect) init() {
 	}
 }
 
-func (o *objectGoReflect) toStringFunc(call FunctionCall) Value {
+func (o *objectGoReflect) toStringFunc(FunctionCall) Value {
 	return o.toPrimitiveString()
 }
 
-func (o *objectGoReflect) valueOfFunc(call FunctionCall) Value {
+func (o *objectGoReflect) valueOfFunc(FunctionCall) Value {
 	return o.toPrimitive()
 }
 
-func (o *objectGoReflect) get(n Value) Value {
-	return o.getStr(n.String())
+func (o *objectGoReflect) getStr(name string, receiver Value) Value {
+	if v := o._get(name); v != nil {
+		return v
+	}
+	return o.baseObject.getStr(name, receiver)
 }
 
 func (o *objectGoReflect) _getField(jsName string) reflect.Value {
@@ -103,10 +107,17 @@ func (o *objectGoReflect) _getMethod(jsName string) reflect.Value {
 	return reflect.Value{}
 }
 
+func (o *objectGoReflect) getAddr(v reflect.Value) reflect.Value {
+	if (v.Kind() == reflect.Struct || v.Kind() == reflect.Slice) && v.CanAddr() {
+		return v.Addr()
+	}
+	return v
+}
+
 func (o *objectGoReflect) _get(name string) Value {
 	if o.value.Kind() == reflect.Struct {
 		if v := o._getField(name); v.IsValid() {
-			return o.val.runtime.ToValue(v.Interface())
+			return o.val.runtime.ToValue(o.getAddr(v).Interface())
 		}
 	}
 
@@ -117,30 +128,12 @@ func (o *objectGoReflect) _get(name string) Value {
 	return nil
 }
 
-func (o *objectGoReflect) getStr(name string) Value {
-	if v := o._get(name); v != nil {
-		return v
-	}
-	return o.baseObject._getStr(name)
-}
-
-func (o *objectGoReflect) getPropStr(name string) Value {
-	if v := o.getOwnPropStr(name); v != nil {
-		return v
-	}
-	return o.baseObject.getPropStr(name)
-}
-
 func (o *objectGoReflect) getOwnPropStr(name string) Value {
 	if o.value.Kind() == reflect.Struct {
 		if v := o._getField(name); v.IsValid() {
-			canSet := v.CanSet()
-			if (v.Kind() == reflect.Struct || v.Kind() == reflect.Slice) && v.CanAddr() {
-				v = v.Addr()
-			}
 			return &valueProperty{
-				value:      o.val.runtime.ToValue(v.Interface()),
-				writable:   canSet,
+				value:      o.val.runtime.ToValue(o.getAddr(v).Interface()),
+				writable:   v.CanSet(),
 				enumerable: true,
 			}
 		}
@@ -156,90 +149,75 @@ func (o *objectGoReflect) getOwnPropStr(name string) Value {
 	return nil
 }
 
-func (o *objectGoReflect) put(n Value, val Value, throw bool) {
-	if _, ok := n.(*valueSymbol); ok {
-		o.val.runtime.typeErrorResult(throw, "Cannot assign to Symbol property %s of a host object", n.String())
-		return
+func (o *objectGoReflect) setOwnStr(name string, val Value, throw bool) bool {
+	has, ok := o._put(name, val, throw)
+	if !has {
+		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
+			o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name)
+			return false
+		} else {
+			return res
+		}
 	}
-	o.putStr(n.String(), val, throw)
+	return ok
 }
 
-func (o *objectGoReflect) putStr(name string, val Value, throw bool) {
-	if !o._put(name, val, throw) {
-		o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name)
-	}
+func (o *objectGoReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignStr(name, trueValIfPresent(o._has(name)), val, receiver, throw)
 }
 
-func (o *objectGoReflect) _put(name string, val Value, throw bool) bool {
+func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool) {
 	if o.value.Kind() == reflect.Struct {
 		if v := o._getField(name); v.IsValid() {
 			if !v.CanSet() {
 				o.val.runtime.typeErrorResult(throw, "Cannot assign to a non-addressable or read-only property %s of a host object", name)
-				return false
+				return true, false
 			}
 			vv, err := o.val.runtime.toReflectValue(val, v.Type())
 			if err != nil {
 				o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
-				return false
+				return true, false
 			}
 			v.Set(vv)
-			return true
+			return true, true
 		}
 	}
-	return false
+	return false, false
 }
 
 func (o *objectGoReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
-	if o._put(name, value, false) {
+	if _, ok := o._put(name, value, false); ok {
 		return value
 	}
 	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
 }
 
-func (r *Runtime) checkHostObjectPropertyDescr(n Value, descr propertyDescr, throw bool) bool {
-	if _, ok := n.(*valueSymbol); ok {
-		r.typeErrorResult(throw, "Host objects do not support symbol properties")
-		return false
-	}
+func (r *Runtime) checkHostObjectPropertyDescr(name string, descr PropertyDescriptor, throw bool) bool {
 	if descr.Getter != nil || descr.Setter != nil {
 		r.typeErrorResult(throw, "Host objects do not support accessor properties")
 		return false
 	}
 	if descr.Writable == FLAG_FALSE {
-		r.typeErrorResult(throw, "Host object field %s cannot be made read-only", n.String())
+		r.typeErrorResult(throw, "Host object field %s cannot be made read-only", name)
 		return false
 	}
 	if descr.Configurable == FLAG_TRUE {
-		r.typeErrorResult(throw, "Host object field %s cannot be made configurable", n.String())
+		r.typeErrorResult(throw, "Host object field %s cannot be made configurable", name)
 		return false
 	}
 	return true
 }
 
-func (o *objectGoReflect) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	if _, ok := n.(*valueSymbol); !ok {
-		if o.value.Kind() == reflect.Struct {
-			name := n.String()
-			if v := o._getField(name); v.IsValid() {
-				if !o.val.runtime.checkHostObjectPropertyDescr(n, descr, throw) {
-					return false
-				}
-				val := descr.Value
-				if val == nil {
-					val = _undefined
-				}
-				vv, err := o.val.runtime.toReflectValue(val, v.Type())
-				if err != nil {
-					o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
-					return false
-				}
-				v.Set(vv)
-				return true
-			}
+func (o *objectGoReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+	if o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
+		if has, ok := o._put(name, descr.Value, throw); !has {
+			o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", name)
+			return false
+		} else {
+			return ok
 		}
 	}
-
-	return o.baseObject.defineOwnProperty(n, descr, throw)
+	return false
 }
 
 func (o *objectGoReflect) _has(name string) bool {
@@ -254,25 +232,6 @@ func (o *objectGoReflect) _has(name string) bool {
 	return false
 }
 
-func (o *objectGoReflect) hasProperty(n Value) bool {
-	name := n.String()
-	if o._has(name) {
-		return true
-	}
-	return o.baseObject.hasProperty(n)
-}
-
-func (o *objectGoReflect) hasPropertyStr(name string) bool {
-	if o._has(name) {
-		return true
-	}
-	return o.baseObject.hasPropertyStr(name)
-}
-
-func (o *objectGoReflect) hasOwnProperty(n Value) bool {
-	return o._has(n.String())
-}
-
 func (o *objectGoReflect) hasOwnPropertyStr(name string) bool {
 	return o._has(name)
 }
@@ -342,14 +301,9 @@ func (o *objectGoReflect) deleteStr(name string, throw bool) bool {
 	return o.baseObject.deleteStr(name, throw)
 }
 
-func (o *objectGoReflect) delete(name Value, throw bool) bool {
-	return o.deleteStr(name.String(), throw)
-}
-
 type goreflectPropIter struct {
-	o         *objectGoReflect
-	idx       int
-	recursive bool
+	o   *objectGoReflect
+	idx int
 }
 
 func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) {
@@ -372,30 +326,34 @@ func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
 		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextMethod
 	}
 
-	if i.recursive {
-		return i.o.baseObject._enumerate(true)()
-	}
-
 	return propIterItem{}, nil
 }
 
-func (o *objectGoReflect) _enumerate(recursive bool) iterNextFunc {
+func (o *objectGoReflect) enumerateUnfiltered() iterNextFunc {
 	r := &goreflectPropIter{
-		o:         o,
-		recursive: recursive,
+		o: o,
 	}
+	var next iterNextFunc
 	if o.value.Kind() == reflect.Struct {
-		return r.nextField
+		next = r.nextField
+	} else {
+		next = r.nextMethod
 	}
-	return r.nextMethod
+
+	return o.recursiveIter(next)
 }
 
-func (o *objectGoReflect) enumerate(all, recursive bool) iterNextFunc {
-	return (&propFilterIter{
-		wrapped: o._enumerate(recursive),
-		all:     all,
-		seen:    make(map[string]bool),
-	}).next
+func (o *objectGoReflect) ownKeys(_ bool, accum []Value) []Value {
+	// all own keys are enumerable
+	for _, name := range o.valueTypeInfo.FieldNames {
+		accum = append(accum, newStringValue(name))
+	}
+
+	for _, name := range o.valueTypeInfo.MethodNames {
+		accum = append(accum, newStringValue(name))
+	}
+
+	return accum
 }
 
 func (o *objectGoReflect) export() interface{} {

+ 107 - 10
object_goreflect_test.go

@@ -23,7 +23,7 @@ func TestGoReflectGet(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if s, ok := v.assertString(); ok {
+	if s, ok := v.(valueString); ok {
 		if s.String() != "42" {
 			t.Fatalf("Unexpected string: %s", s)
 		}
@@ -487,14 +487,14 @@ func TestGoReflectEmbeddedStruct(t *testing.T) {
 
 type jsonTagNamer struct{}
 
-func (jsonTagNamer) FieldName(t reflect.Type, field reflect.StructField) string {
+func (jsonTagNamer) FieldName(_ reflect.Type, field reflect.StructField) string {
 	if jsonTag := field.Tag.Get("json"); jsonTag != "" {
 		return jsonTag
 	}
 	return field.Name
 }
 
-func (jsonTagNamer) MethodName(t reflect.Type, method reflect.Method) string {
+func (jsonTagNamer) MethodName(_ reflect.Type, method reflect.Method) string {
 	return method.Name
 }
 
@@ -588,11 +588,11 @@ func TestGoReflectCustomObjNaming(t *testing.T) {
 
 type fieldNameMapper1 struct{}
 
-func (fieldNameMapper1) FieldName(t reflect.Type, f reflect.StructField) string {
+func (fieldNameMapper1) FieldName(_ reflect.Type, f reflect.StructField) string {
 	return strings.ToLower(f.Name)
 }
 
-func (fieldNameMapper1) MethodName(t reflect.Type, m reflect.Method) string {
+func (fieldNameMapper1) MethodName(_ reflect.Type, m reflect.Method) string {
 	return m.Name
 }
 
@@ -672,7 +672,7 @@ func TestStructNonAddressable(t *testing.T) {
 type testFieldMapper struct {
 }
 
-func (testFieldMapper) FieldName(t reflect.Type, f reflect.StructField) string {
+func (testFieldMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
 	if tag := f.Tag.Get("js"); tag != "" {
 		if tag == "-" {
 			return ""
@@ -683,7 +683,7 @@ func (testFieldMapper) FieldName(t reflect.Type, f reflect.StructField) string {
 	return f.Name
 }
 
-func (testFieldMapper) MethodName(t reflect.Type, m reflect.Method) string {
+func (testFieldMapper) MethodName(_ reflect.Type, m reflect.Method) string {
 	return m.Name
 }
 
@@ -792,7 +792,7 @@ func TestDefinePropertyUnexportedJsName(t *testing.T) {
 		throw new Error("Unexpected value: " + f.field);
 	}
 	if (f.hasOwnProperty("unexported")) {
-		throw new Error("hasOwnProporty('unexported') is true");
+		throw new Error("hasOwnProperty('unexported') is true");
 	}
 	var thrown;
 	try {
@@ -811,11 +811,11 @@ func TestDefinePropertyUnexportedJsName(t *testing.T) {
 
 type fieldNameMapperToLower struct{}
 
-func (fieldNameMapperToLower) FieldName(t reflect.Type, f reflect.StructField) string {
+func (fieldNameMapperToLower) FieldName(_ reflect.Type, f reflect.StructField) string {
 	return strings.ToLower(f.Name)
 }
 
-func (fieldNameMapperToLower) MethodName(t reflect.Type, m reflect.Method) string {
+func (fieldNameMapperToLower) MethodName(_ reflect.Type, m reflect.Method) string {
 	return strings.ToLower(m.Name)
 }
 
@@ -950,3 +950,100 @@ func TestStructNonAddressableAnonStruct(t *testing.T) {
 	}
 
 }
+
+func TestGoReflectWithProto(t *testing.T) {
+	type S struct {
+		Field int
+	}
+	var s S
+	vm := New()
+	vm.Set("s", &s)
+	_, err := vm.RunString(TESTLIB + `
+	(function() {
+	'use strict';
+	var proto = {
+		Field: "protoField",
+		test: 42
+	};
+	var test1Holder;
+	Object.defineProperty(proto, "test1", {
+		set: function(v) {
+			test1Holder = v;
+		},
+		get: function() {
+			return test1Holder;
+		}
+	});
+	Object.setPrototypeOf(s, proto);
+	assert.sameValue(s.Field, 0, "s.Field");
+	s.Field = 2;
+	assert.sameValue(s.Field, 2, "s.Field");
+	assert.sameValue(s.test, 42, "s.test");
+	assert.throws(TypeError, function() {
+		Object.defineProperty(s, "test", {value: 43});
+	});
+	test1Holder = 1;
+	assert.sameValue(s.test1, 1, "s.test1");
+	s.test1 = 2;
+	assert.sameValue(test1Holder, 2, "test1Holder");
+	})();
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoReflectSymbols(t *testing.T) {
+	type S struct {
+		Field int
+	}
+	var s S
+	vm := New()
+	vm.Set("s", &s)
+	_, err := vm.RunString(`
+	'use strict';
+	var sym = Symbol(66);
+	s[sym] = "Test";
+	if (s[sym] !== "Test") {
+		throw new Error("s[sym]=" + s[sym]);
+	}
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoObj__Proto__(t *testing.T) {
+	type S struct {
+		Field int
+	}
+	vm := New()
+	vm.Set("s", S{})
+	vm.Set("m", map[string]interface{}{})
+	vm.Set("mr", map[int]string{})
+	vm.Set("a", []interface{}{})
+	vm.Set("ar", []string{})
+	_, err := vm.RunString(`
+	function f(s, expectedCtor, prefix) {
+		if (s.__proto__ !== expectedCtor.prototype) {
+			throw new Error(prefix + ": __proto__: " + s.__proto__);
+		}
+		s.__proto__ = null;
+		if (s.__proto__ !== undefined) { // as there is no longer a prototype, there is no longer the __proto__ property
+			throw new Error(prefix + ": __proto__ is not undefined: " + s.__proto__);
+		}
+		var proto = Object.getPrototypeOf(s);
+		if (proto !== null) {
+			throw new Error(prefix + ": proto is not null: " + proto);
+		}
+	}
+	f(s, Object, "struct");
+	f(m, Object, "simple map");
+	f(mr, Object, "reflect map");
+	f(a, Array, "slice");
+	f(ar, Array, "reflect slice");
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 182 - 123
object_goslice.go

@@ -1,6 +1,8 @@
 package goja
 
 import (
+	"math"
+	"math/bits"
 	"reflect"
 	"strconv"
 )
@@ -17,76 +19,73 @@ func (o *objectGoSlice) init() {
 	o.class = classArray
 	o.prototype = o.val.runtime.global.ArrayPrototype
 	o.lengthProp.writable = o.sliceExtensible
-	o._setLen()
+	o.extensible = true
+	o.updateLen()
 	o.baseObject._put("length", &o.lengthProp)
 }
 
-func (o *objectGoSlice) _setLen() {
+func (o *objectGoSlice) updateLen() {
 	o.lengthProp.value = intToValue(int64(len(*o.data)))
 }
 
-func (o *objectGoSlice) getIdx(idx int64) Value {
-	if idx < int64(len(*o.data)) {
-		return o.val.runtime.ToValue((*o.data)[idx])
+func (o *objectGoSlice) getStr(name string, receiver Value) Value {
+	var ownProp Value
+	if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) {
+		v := (*o.data)[idx]
+		ownProp = o.val.runtime.ToValue(v)
+	} else if name == "length" {
+		ownProp = &o.lengthProp
 	}
-	return nil
-}
 
-func (o *objectGoSlice) _get(n Value) Value {
-	if idx := toIdx(n); idx >= 0 {
-		return o.getIdx(idx)
-	}
-	return nil
+	return o.getStrWithOwnProp(ownProp, name, receiver)
 }
 
-func (o *objectGoSlice) _getStr(name string) Value {
-	if idx := strToIdx(name); idx >= 0 {
-		return o.getIdx(idx)
+func (o *objectGoSlice) getIdx(idx valueInt, receiver Value) Value {
+	if idx := int64(idx); idx >= 0 && idx < int64(len(*o.data)) {
+		v := (*o.data)[idx]
+		return o.val.runtime.ToValue(v)
 	}
-	return nil
-}
-
-func (o *objectGoSlice) get(n Value) Value {
-	if v := o._get(n); v != nil {
-		return v
-	}
-	return o.baseObject._getStr(n.String())
-}
-
-func (o *objectGoSlice) getStr(name string) Value {
-	if v := o._getStr(name); v != nil {
-		return v
+	if o.prototype != nil {
+		if receiver == nil {
+			return o.prototype.self.getIdx(idx, o.val)
+		}
+		return o.prototype.self.getIdx(idx, receiver)
 	}
-	return o.baseObject._getStr(name)
+	return nil
 }
 
-func (o *objectGoSlice) getProp(n Value) Value {
-	if v := o._get(n); v != nil {
-		return v
+func (o *objectGoSlice) getOwnPropStr(name string) Value {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if idx < len(*o.data) {
+			v := o.val.runtime.ToValue((*o.data)[idx])
+			return &valueProperty{
+				value:      v,
+				writable:   true,
+				enumerable: true,
+			}
+		}
+		return nil
 	}
-	return o.baseObject.getPropStr(n.String())
-}
-
-func (o *objectGoSlice) getPropStr(name string) Value {
-	if v := o._getStr(name); v != nil {
-		return v
+	if name == "length" {
+		return &o.lengthProp
 	}
-	return o.baseObject.getPropStr(name)
+	return nil
 }
 
-func (o *objectGoSlice) getOwnPropStr(name string) Value {
-	if v := o._getStr(name); v != nil {
+func (o *objectGoSlice) getOwnPropIdx(idx valueInt) Value {
+	if idx := int64(idx); idx >= 0 && idx < int64(len(*o.data)) {
+		v := o.val.runtime.ToValue((*o.data)[idx])
 		return &valueProperty{
 			value:      v,
 			writable:   true,
 			enumerable: true,
 		}
 	}
-	return o.baseObject.getOwnPropStr(name)
+	return nil
 }
 
-func (o *objectGoSlice) grow(size int64) {
-	newcap := int64(cap(*o.data))
+func (o *objectGoSlice) grow(size int) {
+	newcap := cap(*o.data)
 	if newcap < size {
 		// Use the same algorithm as in runtime.growSlice
 		doublecap := newcap + newcap
@@ -106,13 +105,26 @@ func (o *objectGoSlice) grow(size int64) {
 		copy(n, *o.data)
 		*o.data = n
 	} else {
+		tail := (*o.data)[len(*o.data):size]
+		for k := range tail {
+			tail[k] = nil
+		}
 		*o.data = (*o.data)[:size]
 	}
-	o._setLen()
+	o.updateLen()
 }
 
-func (o *objectGoSlice) putIdx(idx int64, v Value, throw bool) {
-	if idx >= int64(len(*o.data)) {
+func (o *objectGoSlice) shrink(size int) {
+	tail := (*o.data)[size:]
+	for k := range tail {
+		tail[k] = nil
+	}
+	*o.data = (*o.data)[:size]
+	o.updateLen()
+}
+
+func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) {
+	if idx >= len(*o.data) {
 		if !o.sliceExtensible {
 			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
 			return
@@ -122,74 +134,118 @@ func (o *objectGoSlice) putIdx(idx int64, v Value, throw bool) {
 	(*o.data)[idx] = v.Export()
 }
 
-func (o *objectGoSlice) put(n Value, val Value, throw bool) {
-	if idx := toIdx(n); idx >= 0 {
-		o.putIdx(idx, val, throw)
-		return
+func toInt(i int64) int {
+	if bits.UintSize == 64 {
+		return int(i)
+	}
+	if i >= math.MaxInt32 {
+		panic(typeError("Integer value overflows 32-bit int"))
 	}
-	// TODO: length
-	o.baseObject.put(n, val, throw)
+	return int(i)
 }
 
-func (o *objectGoSlice) putStr(name string, val Value, throw bool) {
-	if idx := strToIdx(name); idx >= 0 {
-		o.putIdx(idx, val, throw)
-		return
+func (o *objectGoSlice) putLength(v Value, throw bool) bool {
+	newLen := toInt(toLength(v))
+	curLen := len(*o.data)
+	if newLen > curLen {
+		if !o.sliceExtensible {
+			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
+			return false
+		}
+		o.grow(newLen)
+	} else if newLen < curLen {
+		if !o.sliceExtensible {
+			o.val.runtime.typeErrorResult(throw, "Cannot shrink Go slice")
+			return false
+		}
+		o.shrink(newLen)
 	}
-	// TODO: length
-	o.baseObject.putStr(name, val, throw)
+	return true
 }
 
-func (o *objectGoSlice) _has(n Value) bool {
-	if idx := toIdx(n); idx >= 0 {
-		return idx < int64(len(*o.data))
+func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool {
+	if i := toInt(int64(idx)); i >= 0 {
+		if i >= len(*o.data) {
+			if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok {
+				return res
+			}
+		}
+		o.putIdx(i, val, throw)
+	} else {
+		name := idx.String()
+		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
+			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
+			return false
+		} else {
+			return res
+		}
 	}
-	return false
+	return true
 }
 
-func (o *objectGoSlice) _hasStr(name string) bool {
-	if idx := strToIdx(name); idx >= 0 {
-		return idx < int64(len(*o.data))
+func (o *objectGoSlice) setOwnStr(name string, val Value, throw bool) bool {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if idx >= len(*o.data) {
+			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
+				return res
+			}
+		}
+		o.putIdx(idx, val, throw)
+	} else {
+		if name == "length" {
+			return o.putLength(val, throw)
+		}
+		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
+			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
+			return false
+		} else {
+			return res
+		}
 	}
-	return false
+	return true
 }
 
-func (o *objectGoSlice) hasProperty(n Value) bool {
-	if o._has(n) {
-		return true
-	}
-	return o.baseObject.hasProperty(n)
+func (o *objectGoSlice) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw)
 }
 
-func (o *objectGoSlice) hasPropertyStr(name string) bool {
-	if o._hasStr(name) {
-		return true
-	}
-	return o.baseObject.hasPropertyStr(name)
+func (o *objectGoSlice) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
 }
 
-func (o *objectGoSlice) hasOwnProperty(n Value) bool {
-	if o._has(n) {
-		return true
+func (o *objectGoSlice) hasOwnPropertyIdx(idx valueInt) bool {
+	if idx := int64(idx); idx >= 0 {
+		return idx < int64(len(*o.data))
 	}
-	return o.baseObject.hasOwnProperty(n)
+	return false
 }
 
 func (o *objectGoSlice) hasOwnPropertyStr(name string) bool {
-	if o._hasStr(name) {
-		return true
+	if idx := strToIdx64(name); idx >= 0 {
+		return idx < int64(len(*o.data))
 	}
-	return o.baseObject.hasOwnPropertyStr(name)
+	return false
 }
 
-func (o *objectGoSlice) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
-	o.putStr(name, value, false)
-	return value
+func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
+	if i := toInt(int64(idx)); i >= 0 {
+		if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) {
+			return false
+		}
+		val := descr.Value
+		if val == nil {
+			val = _undefined
+		}
+		o.putIdx(i, val, throw)
+		return true
+	}
+	o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx)
+	return false
 }
 
-func (o *objectGoSlice) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	if idx := toIdx(n); idx >= 0 {
-		if !o.val.runtime.checkHostObjectPropertyDescr(n, descr, throw) {
+func (o *objectGoSlice) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 			return false
 		}
 		val := descr.Value
@@ -199,7 +255,11 @@ func (o *objectGoSlice) defineOwnProperty(n Value, descr propertyDescr, throw bo
 		o.putIdx(idx, val, throw)
 		return true
 	}
-	return o.baseObject.defineOwnProperty(n, descr, throw)
+	if name == "length" {
+		return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw)
+	}
+	o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name)
+	return false
 }
 
 func (o *objectGoSlice) toPrimitiveNumber() Value {
@@ -217,24 +277,29 @@ func (o *objectGoSlice) toPrimitive() Value {
 }
 
 func (o *objectGoSlice) deleteStr(name string, throw bool) bool {
-	if idx := strToIdx(name); idx >= 0 && idx < int64(len(*o.data)) {
-		(*o.data)[idx] = nil
+	if idx := strToIdx64(name); idx >= 0 {
+		if idx < int64(len(*o.data)) {
+			o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice")
+			return false
+		}
 		return true
 	}
 	return o.baseObject.deleteStr(name, throw)
 }
 
-func (o *objectGoSlice) delete(name Value, throw bool) bool {
-	if idx := toIdx(name); idx >= 0 && idx < int64(len(*o.data)) {
-		(*o.data)[idx] = nil
-		return true
+func (o *objectGoSlice) deleteIdx(i valueInt, throw bool) bool {
+	idx := int64(i)
+	if idx >= 0 {
+		if idx < int64(len(*o.data)) {
+			o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice")
+			return false
+		}
 	}
-	return o.baseObject.delete(name, throw)
+	return true
 }
 
 type goslicePropIter struct {
 	o          *objectGoSlice
-	recursive  bool
 	idx, limit int
 }
 
@@ -245,28 +310,22 @@ func (i *goslicePropIter) next() (propIterItem, iterNextFunc) {
 		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
 	}
 
-	if i.recursive {
-		return i.o.prototype.self._enumerate(i.recursive)()
-	}
-
 	return propIterItem{}, nil
 }
 
-func (o *objectGoSlice) enumerate(all, recursive bool) iterNextFunc {
-	return (&propFilterIter{
-		wrapped: o._enumerate(recursive),
-		all:     all,
-		seen:    make(map[string]bool),
-	}).next
-
+func (o *objectGoSlice) enumerateUnfiltered() iterNextFunc {
+	return o.recursiveIter((&goslicePropIter{
+		o:     o,
+		limit: len(*o.data),
+	}).next)
 }
 
-func (o *objectGoSlice) _enumerate(recursive bool) iterNextFunc {
-	return (&goslicePropIter{
-		o:         o,
-		recursive: recursive,
-		limit:     len(*o.data),
-	}).next
+func (o *objectGoSlice) ownKeys(_ bool, accum []Value) []Value {
+	for i := range *o.data {
+		accum = append(accum, asciiString(strconv.Itoa(i)))
+	}
+
+	return accum
 }
 
 func (o *objectGoSlice) export() interface{} {
@@ -289,15 +348,15 @@ func (o *objectGoSlice) sortLen() int64 {
 }
 
 func (o *objectGoSlice) sortGet(i int64) Value {
-	return o.get(intToValue(i))
+	return o.getIdx(valueInt(i), nil)
 }
 
 func (o *objectGoSlice) swap(i, j int64) {
-	ii := intToValue(i)
-	jj := intToValue(j)
-	x := o.get(ii)
-	y := o.get(jj)
+	ii := valueInt(i)
+	jj := valueInt(j)
+	x := o.getIdx(ii, nil)
+	y := o.getIdx(jj, nil)
 
-	o.put(ii, y, false)
-	o.put(jj, x, false)
+	o.setOwnIdx(ii, y, false)
+	o.setOwnIdx(jj, x, false)
 }

+ 187 - 121
object_goslice_reflect.go

@@ -17,98 +17,98 @@ func (o *objectGoSliceReflect) init() {
 	o.prototype = o.val.runtime.global.ArrayPrototype
 	o.sliceExtensible = o.value.CanSet()
 	o.lengthProp.writable = o.sliceExtensible
-	o._setLen()
+	o.updateLen()
 	o.baseObject._put("length", &o.lengthProp)
 }
 
-func (o *objectGoSliceReflect) _setLen() {
+func (o *objectGoSliceReflect) updateLen() {
 	o.lengthProp.value = intToValue(int64(o.value.Len()))
 }
 
-func (o *objectGoSliceReflect) _has(n Value) bool {
-	if idx := toIdx(n); idx >= 0 {
-		return idx < int64(o.value.Len())
+func (o *objectGoSliceReflect) _hasIdx(idx valueInt) bool {
+	if idx := int64(idx); idx >= 0 && idx < int64(o.value.Len()) {
+		return true
 	}
 	return false
 }
 
 func (o *objectGoSliceReflect) _hasStr(name string) bool {
-	if idx := strToIdx(name); idx >= 0 {
-		return idx < int64(o.value.Len())
+	if idx := strToIdx64(name); idx >= 0 && idx < int64(o.value.Len()) {
+		return true
 	}
 	return false
 }
 
-func (o *objectGoSliceReflect) getIdx(idx int64) Value {
-	if idx < int64(o.value.Len()) {
-		return o.val.runtime.ToValue(o.value.Index(int(idx)).Interface())
-	}
-	return nil
-}
-
-func (o *objectGoSliceReflect) _get(n Value) Value {
-	if idx := toIdx(n); idx >= 0 {
-		return o.getIdx(idx)
-	}
-	return nil
-}
-
-func (o *objectGoSliceReflect) _getStr(name string) Value {
-	if idx := strToIdx(name); idx >= 0 {
-		return o.getIdx(idx)
+func (o *objectGoSliceReflect) _getIdx(idx int) Value {
+	v := o.value.Index(idx)
+	if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
+		return _null
 	}
-	return nil
+	return o.val.runtime.ToValue(v.Interface())
 }
 
-func (o *objectGoSliceReflect) get(n Value) Value {
-	if v := o._get(n); v != nil {
-		return v
+func (o *objectGoSliceReflect) getIdx(idx valueInt, receiver Value) Value {
+	if idx := toInt(int64(idx)); idx >= 0 && idx < o.value.Len() {
+		return o._getIdx(idx)
 	}
-	return o.objectGoReflect.get(n)
+	return o.objectGoReflect.getStr(idx.String(), receiver)
 }
 
-func (o *objectGoSliceReflect) getStr(name string) Value {
-	if v := o._getStr(name); v != nil {
-		return v
+func (o *objectGoSliceReflect) getStr(name string, receiver Value) Value {
+	var ownProp Value
+	if idx := strToGoIdx(name); idx >= 0 && idx < o.value.Len() {
+		ownProp = o._getIdx(idx)
+	} else if name == "length" {
+		ownProp = &o.lengthProp
+	} else {
+		ownProp = o.objectGoReflect.getOwnPropStr(name)
 	}
-	return o.objectGoReflect.getStr(name)
+	return o.getStrWithOwnProp(ownProp, name, receiver)
 }
 
-func (o *objectGoSliceReflect) getProp(n Value) Value {
-	if v := o._get(n); v != nil {
-		return v
+func (o *objectGoSliceReflect) getOwnPropStr(name string) Value {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if idx < o.value.Len() {
+			return &valueProperty{
+				value:      o._getIdx(idx),
+				writable:   true,
+				enumerable: true,
+			}
+		}
+		return nil
 	}
-	return o.objectGoReflect.getProp(n)
-}
-
-func (o *objectGoSliceReflect) getPropStr(name string) Value {
-	if v := o._getStr(name); v != nil {
-		return v
+	if name == "length" {
+		return &o.lengthProp
 	}
-	return o.objectGoReflect.getPropStr(name)
+	return o.objectGoReflect.getOwnPropStr(name)
 }
 
-func (o *objectGoSliceReflect) getOwnPropStr(name string) Value {
-	if v := o._getStr(name); v != nil {
-		return v
+func (o *objectGoSliceReflect) getOwnPropIdx(idx valueInt) Value {
+	if idx := toInt(int64(idx)); idx >= 0 && idx < o.value.Len() {
+		return &valueProperty{
+			value:      o._getIdx(idx),
+			writable:   true,
+			enumerable: true,
+		}
 	}
-	return o.objectGoReflect.getOwnPropStr(name)
+	return nil
 }
 
-func (o *objectGoSliceReflect) putIdx(idx int64, v Value, throw bool) {
-	if idx >= int64(o.value.Len()) {
+func (o *objectGoSliceReflect) putIdx(idx int, v Value, throw bool) bool {
+	if idx >= o.value.Len() {
 		if !o.sliceExtensible {
 			o.val.runtime.typeErrorResult(throw, "Cannot extend a Go unaddressable reflect slice")
-			return
+			return false
 		}
-		o.grow(int(idx + 1))
+		o.grow(idx + 1)
 	}
 	val, err := o.val.runtime.toReflectValue(v, o.value.Type().Elem())
 	if err != nil {
 		o.val.runtime.typeErrorResult(throw, "Go type conversion error: %v", err)
-		return
+		return false
 	}
-	o.value.Index(int(idx)).Set(val)
+	o.value.Index(idx).Set(val)
+	return true
 }
 
 func (o *objectGoSliceReflect) grow(size int) {
@@ -132,71 +132,136 @@ func (o *objectGoSliceReflect) grow(size int) {
 		reflect.Copy(n, o.value)
 		o.value.Set(n)
 	} else {
+		tail := o.value.Slice(o.value.Len(), size)
+		zero := reflect.Zero(o.value.Type().Elem())
+		for i := 0; i < tail.Len(); i++ {
+			tail.Index(i).Set(zero)
+		}
 		o.value.SetLen(size)
 	}
-	o._setLen()
+	o.updateLen()
 }
 
-func (o *objectGoSliceReflect) put(n Value, val Value, throw bool) {
-	if idx := toIdx(n); idx >= 0 {
-		o.putIdx(idx, val, throw)
-		return
+func (o *objectGoSliceReflect) shrink(size int) {
+	tail := o.value.Slice(size, o.value.Len())
+	zero := reflect.Zero(o.value.Type().Elem())
+	for i := 0; i < tail.Len(); i++ {
+		tail.Index(i).Set(zero)
 	}
-	// TODO: length
-	o.objectGoReflect.put(n, val, throw)
+	o.value.SetLen(size)
+	o.updateLen()
 }
 
-func (o *objectGoSliceReflect) putStr(name string, val Value, throw bool) {
-	if idx := strToIdx(name); idx >= 0 {
-		o.putIdx(idx, val, throw)
-		return
-	}
-	if name == "length" {
-		o.baseObject.putStr(name, val, throw)
-		return
+func (o *objectGoSliceReflect) putLength(v Value, throw bool) bool {
+	newLen := toInt(toLength(v))
+	curLen := o.value.Len()
+	if newLen > curLen {
+		if !o.sliceExtensible {
+			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
+			return false
+		}
+		o.grow(newLen)
+	} else if newLen < curLen {
+		if !o.sliceExtensible {
+			o.val.runtime.typeErrorResult(throw, "Cannot shrink Go slice")
+			return false
+		}
+		o.shrink(newLen)
 	}
-	o.objectGoReflect.putStr(name, val, throw)
+	return true
 }
 
-func (o *objectGoSliceReflect) hasProperty(n Value) bool {
-	if o._has(n) {
-		return true
+func (o *objectGoSliceReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool {
+	if i := toInt(int64(idx)); i >= 0 {
+		if i >= o.value.Len() {
+			if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok {
+				return res
+			}
+		}
+		o.putIdx(i, val, throw)
+	} else {
+		name := idx.String()
+		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
+			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
+			return false
+		} else {
+			return res
+		}
 	}
-	return o.objectGoReflect.hasProperty(n)
+	return true
 }
 
-func (o *objectGoSliceReflect) hasPropertyStr(name string) bool {
-	if o._hasStr(name) {
-		return true
+func (o *objectGoSliceReflect) setOwnStr(name string, val Value, throw bool) bool {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if idx >= o.value.Len() {
+			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
+				return res
+			}
+		}
+		o.putIdx(idx, val, throw)
+	} else {
+		if name == "length" {
+			return o.putLength(val, throw)
+		}
+		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
+			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
+			return false
+		} else {
+			return res
+		}
 	}
-	return o.objectGoReflect.hasOwnPropertyStr(name)
+	return true
 }
 
-func (o *objectGoSliceReflect) hasOwnProperty(n Value) bool {
-	if o._has(n) {
-		return true
-	}
-	return o.objectGoReflect.hasOwnProperty(n)
+func (o *objectGoSliceReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignIdx(idx, trueValIfPresent(o._hasIdx(idx)), val, receiver, throw)
+}
+
+func (o *objectGoSliceReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return o._setForeignStr(name, trueValIfPresent(o._hasStr(name)), val, receiver, throw)
+}
+
+func (o *objectGoSliceReflect) hasOwnPropertyIdx(idx valueInt) bool {
+	return o._hasIdx(idx)
 }
 
 func (o *objectGoSliceReflect) hasOwnPropertyStr(name string) bool {
 	if o._hasStr(name) {
 		return true
 	}
-	return o.objectGoReflect.hasOwnPropertyStr(name)
+	return o.objectGoReflect._has(name)
 }
 
-func (o *objectGoSliceReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
-	o.putStr(name, value, false)
-	return value
+func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
+	if i := toInt(int64(idx)); i >= 0 {
+		if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) {
+			return false
+		}
+		val := descr.Value
+		if val == nil {
+			val = _undefined
+		}
+		o.putIdx(i, val, throw)
+		return true
+	}
+	o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx)
+	return false
 }
 
-func (o *objectGoSliceReflect) defineOwnProperty(name Value, descr propertyDescr, throw bool) bool {
-	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
-		return false
+func (o *objectGoSliceReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+	if idx := strToGoIdx(name); idx >= 0 {
+		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
+			return false
+		}
+		val := descr.Value
+		if val == nil {
+			val = _undefined
+		}
+		o.putIdx(idx, val, throw)
+		return true
 	}
-	o.put(name, descr.Value, throw)
-	return true
+	o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name)
+	return false
 }
 
 func (o *objectGoSliceReflect) toPrimitiveNumber() Value {
@@ -214,24 +279,30 @@ func (o *objectGoSliceReflect) toPrimitive() Value {
 }
 
 func (o *objectGoSliceReflect) deleteStr(name string, throw bool) bool {
-	if idx := strToIdx(name); idx >= 0 && idx < int64(o.value.Len()) {
-		o.value.Index(int(idx)).Set(reflect.Zero(o.value.Type().Elem()))
+	if idx := strToIdx64(name); idx >= 0 {
+		if idx < int64(o.value.Len()) {
+			o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice")
+			return false
+		}
 		return true
 	}
+
 	return o.objectGoReflect.deleteStr(name, throw)
 }
 
-func (o *objectGoSliceReflect) delete(name Value, throw bool) bool {
-	if idx := toIdx(name); idx >= 0 && idx < int64(o.value.Len()) {
-		o.value.Index(int(idx)).Set(reflect.Zero(o.value.Type().Elem()))
-		return true
+func (o *objectGoSliceReflect) deleteIdx(i valueInt, throw bool) bool {
+	idx := int64(i)
+	if idx >= 0 {
+		if idx < int64(o.value.Len()) {
+			o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice")
+			return false
+		}
 	}
-	return o.objectGoReflect.delete(name, throw)
+	return true
 }
 
 type gosliceReflectPropIter struct {
 	o          *objectGoSliceReflect
-	recursive  bool
 	idx, limit int
 }
 
@@ -242,26 +313,21 @@ func (i *gosliceReflectPropIter) next() (propIterItem, iterNextFunc) {
 		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
 	}
 
-	if i.recursive {
-		return i.o.prototype.self._enumerate(i.recursive)()
-	}
-
-	return propIterItem{}, nil
+	return i.o.objectGoReflect.enumerateUnfiltered()()
 }
 
-func (o *objectGoSliceReflect) enumerate(all, recursive bool) iterNextFunc {
-	return (&propFilterIter{
-		wrapped: o._enumerate(recursive),
-		all:     all,
-		seen:    make(map[string]bool),
-	}).next
+func (o *objectGoSliceReflect) ownKeys(all bool, accum []Value) []Value {
+	for i := 0; i < o.value.Len(); i++ {
+		accum = append(accum, asciiString(strconv.Itoa(i)))
+	}
+
+	return o.objectGoReflect.ownKeys(all, accum)
 }
 
-func (o *objectGoSliceReflect) _enumerate(recursive bool) iterNextFunc {
+func (o *objectGoSliceReflect) enumerateUnfiltered() iterNextFunc {
 	return (&gosliceReflectPropIter{
-		o:         o,
-		recursive: recursive,
-		limit:     o.value.Len(),
+		o:     o,
+		limit: o.value.Len(),
 	}).next
 }
 
@@ -277,15 +343,15 @@ func (o *objectGoSliceReflect) sortLen() int64 {
 }
 
 func (o *objectGoSliceReflect) sortGet(i int64) Value {
-	return o.get(intToValue(i))
+	return o.getIdx(valueInt(i), nil)
 }
 
 func (o *objectGoSliceReflect) swap(i, j int64) {
-	ii := intToValue(i)
-	jj := intToValue(j)
-	x := o.get(ii)
-	y := o.get(jj)
+	ii := valueInt(i)
+	jj := valueInt(j)
+	x := o.getIdx(ii, nil)
+	y := o.getIdx(jj, nil)
 
-	o.put(ii, y, false)
-	o.put(jj, x, false)
+	o.setOwnIdx(ii, y, false)
+	o.setOwnIdx(jj, x, false)
 }

+ 146 - 2
object_goslice_reflect_test.go

@@ -1,6 +1,8 @@
 package goja
 
-import "testing"
+import (
+	"testing"
+)
 
 func TestGoSliceReflectBasic(t *testing.T) {
 	const SCRIPT = `
@@ -109,7 +111,7 @@ func TestGoSliceReflectPush(t *testing.T) {
 
 }
 
-func TestGoSliceReflectProto(t *testing.T) {
+func TestGoSliceReflectProtoMethod(t *testing.T) {
 	const SCRIPT = `
 	a.join(",")
 	`
@@ -160,3 +162,145 @@ func TestGoSliceReflectGetStr(t *testing.T) {
 		}
 	}
 }
+
+func TestGoSliceReflectNilObjectIfaceVal(t *testing.T) {
+	r := New()
+	a := []Value{(*Object)(nil)}
+	r.Set("a", a)
+	ret, err := r.RunString(`
+	""+a[0];
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !asciiString("null").SameAs(ret) {
+		t.Fatalf("ret: %v", ret)
+	}
+}
+
+func TestGoSliceReflectSetLength(t *testing.T) {
+	r := New()
+	a := []int{1, 2, 3, 4}
+	b := []testing.TB{&testing.T{}, &testing.T{}, (*testing.T)(nil)}
+	r.Set("a", &a)
+	r.Set("b", &b)
+	_, err := r.RunString(`
+	'use strict';
+	a.length = 3;
+	if (a.length !== 3) {
+		throw new Error("length="+a.length);
+	}
+	if (a[3] !== undefined) {
+		throw new Error("a[3]="+a[3]);
+	}
+	a.length = 5;
+	if (a.length !== 5) {
+		throw new Error("a.length="+a.length);
+	}
+	if (a[3] !== 0) {
+		throw new Error("a[3]="+a[3]);
+	}
+	if (a[4] !== 0) {
+		throw new Error("a[4]="+a[4]);
+	}
+
+	b.length = 3;
+	if (b.length !== 3) {
+		throw new Error("b.length="+b.length);
+	}
+	if (b[3] !== undefined) {
+		throw new Error("b[3]="+b[3]);
+	}
+	b.length = 5;
+	if (b.length !== 5) {
+		throw new Error("length="+b.length);
+	}
+	if (b[3] !== null) {
+		throw new Error("b[3]="+b[3]);
+	}
+	if (b[4] !== null) {
+		throw new Error("b[4]="+b[4]);
+	}
+	if (b[2] !== null) {
+		throw new Error("b[2]="+b[2]);
+	}
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoSliceReflectProto(t *testing.T) {
+	r := New()
+	a := []*Object{{}, nil, {}}
+	r.Set("a", &a)
+	_, err := r.RunString(TESTLIB + `
+	var proto = [,2,,4];
+	Object.setPrototypeOf(a, proto);
+	assert.sameValue(a[1], null, "a[1]");
+	assert.sameValue(a[3], 4, "a[3]");
+	var desc = Object.getOwnPropertyDescriptor(a, "1");
+	assert.sameValue(desc.value, null, "desc.value");
+	assert(desc.writable, "writable");
+	assert(desc.enumerable, "enumerable");
+	assert(!desc.configurable, "configurable");
+	var v5;
+	Object.defineProperty(proto, "5", {
+		set: function(v) {
+			v5 = v;
+		}
+	});
+	a[5] = "test";
+	assert.sameValue(v5, "test", "v5");
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+}
+
+func TestGoSliceReflectProtoProto(t *testing.T) {
+	r := New()
+	a := []*Object{{}, nil, {}}
+	proto := []*Object{{}, {}, {}, {}}
+	r.Set("a", &a)
+	r.Set("proto", proto)
+	_, err := r.RunString(`
+	"use strict";
+	var protoproto = {};
+	Object.defineProperty(protoproto, "3", {
+		value: 42
+	});
+	Object.setPrototypeOf(proto, protoproto);
+	Object.setPrototypeOf(a, proto);
+	if (a.hasOwnProperty("3")) {
+		throw new Error("a.hasOwnProperty(\"3\")");
+	}
+	if (a[3] !== null) {
+		throw new Error("a[3]="+a[3]);
+	}
+	a[3] = null;
+	if (a[3] !== null) {
+		throw new Error("a[3]=" + a[3]);
+	}
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+}
+
+func TestGoSliceReflectDelete(t *testing.T) {
+	r := New()
+	a := []*Object{{}, nil, {}}
+	r.Set("a", a)
+	v, err := r.RunString(`
+	!delete a[0] && !delete a[1] && delete a[3];
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if v != valueTrue {
+		t.Fatalf("not true: %v", v)
+	}
+}

+ 101 - 2
object_goslice_test.go

@@ -1,6 +1,8 @@
 package goja
 
-import "testing"
+import (
+	"testing"
+)
 
 func TestGoSliceBasic(t *testing.T) {
 	const SCRIPT = `
@@ -69,7 +71,7 @@ func TestGoSliceExpand(t *testing.T) {
 	}
 }
 
-func TestGoSliceProto(t *testing.T) {
+func TestGoSliceProtoMethod(t *testing.T) {
 	const SCRIPT = `
 	a.join(",")
 	`
@@ -85,3 +87,100 @@ func TestGoSliceProto(t *testing.T) {
 		t.Fatalf("Unexpected result: '%s'", s)
 	}
 }
+
+func TestGoSliceSetLength(t *testing.T) {
+	r := New()
+	a := []interface{}{1, 2, 3, 4}
+	r.Set("a", &a)
+	_, err := r.RunString(`
+	'use strict';
+	a.length = 3;
+	if (a.length !== 3) {
+		throw new Error("length="+a.length);
+	}
+	if (a[3] !== undefined) {
+		throw new Error("a[3](1)="+a[3]);
+	}
+	a.length = 5;
+	if (a.length !== 5) {
+		throw new Error("length="+a.length);
+	}
+	if (a[3] !== null) {
+		throw new Error("a[3](2)="+a[3]);
+	}
+	if (a[4] !== null) {
+		throw new Error("a[4]="+a[4]);
+	}
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoSliceProto(t *testing.T) {
+	r := New()
+	a := []interface{}{1, nil, 3}
+	r.Set("a", &a)
+	_, err := r.RunString(TESTLIB + `
+	var proto = [,2,,4];
+	Object.setPrototypeOf(a, proto);
+	assert.sameValue(a[1], null, "a[1]");
+	assert.sameValue(a[3], 4, "a[3]");
+	var desc = Object.getOwnPropertyDescriptor(a, "1");
+	assert.sameValue(desc.value, null, "desc.value");
+	assert(desc.writable, "writable");
+	assert(desc.enumerable, "enumerable");
+	assert(!desc.configurable, "configurable");
+	var v5;
+	Object.defineProperty(proto, "5", {
+		set: function(v) {
+			v5 = v;
+		}
+	});
+	a[5] = "test";
+	assert.sameValue(v5, "test", "v5");
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+}
+
+func TestGoSliceProtoProto(t *testing.T) {
+	r := New()
+	a := []interface{}{1, nil, 3}
+	proto := []interface{}{1, 2, 3, 4}
+	r.Set("a", &a)
+	r.Set("proto", proto)
+	_, err := r.RunString(`
+	"use strict";
+	var protoproto = Object.create(null);
+	Object.defineProperty(protoproto, "3", {
+		value: 42
+	});
+	Object.setPrototypeOf(proto, protoproto);
+	Object.setPrototypeOf(a, proto);
+	a[3] = 11;
+	if (a[3] !== 11) {
+		throw new Error("a[3]=" + a[3]);
+	}
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGoSliceDelete(t *testing.T) {
+	r := New()
+	a := []interface{}{1, nil, 3}
+	r.Set("a", a)
+	v, err := r.RunString(`
+	!delete a[0] && !delete a[1] && delete a[3];
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if v != valueTrue {
+		t.Fatalf("not true: %v", v)
+	}
+}

+ 116 - 42
object_lazy.go

@@ -13,88 +13,150 @@ func (o *lazyObject) className() string {
 	return obj.className()
 }
 
-func (o *lazyObject) get(n Value) Value {
+func (o *lazyObject) getIdx(p valueInt, receiver Value) Value {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.get(n)
+	return obj.getIdx(p, receiver)
 }
 
-func (o *lazyObject) getProp(n Value) Value {
+func (o *lazyObject) getSym(p *valueSymbol, receiver Value) Value {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.getProp(n)
+	return obj.getSym(p, receiver)
 }
 
-func (o *lazyObject) getPropStr(name string) Value {
+func (o *lazyObject) getOwnPropIdx(idx valueInt) Value {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.getPropStr(name)
+	return obj.getOwnPropIdx(idx)
 }
 
-func (o *lazyObject) getStr(name string) Value {
+func (o *lazyObject) getOwnPropSym(s *valueSymbol) Value {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.getStr(name)
+	return obj.getOwnPropSym(s)
 }
 
-func (o *lazyObject) getOwnPropStr(name string) Value {
+func (o *lazyObject) hasPropertyIdx(idx valueInt) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.getOwnPropStr(name)
+	return obj.hasPropertyIdx(idx)
 }
 
-func (o *lazyObject) getOwnProp(name Value) Value {
+func (o *lazyObject) hasPropertySym(s *valueSymbol) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.getOwnProp(name)
+	return obj.hasPropertySym(s)
 }
 
-func (o *lazyObject) put(n Value, val Value, throw bool) {
+func (o *lazyObject) hasOwnPropertyIdx(idx valueInt) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	obj.put(n, val, throw)
+	return obj.hasOwnPropertyIdx(idx)
 }
 
-func (o *lazyObject) putStr(name string, val Value, throw bool) {
+func (o *lazyObject) hasOwnPropertySym(s *valueSymbol) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	obj.putStr(name, val, throw)
+	return obj.hasOwnPropertySym(s)
 }
 
-func (o *lazyObject) hasProperty(n Value) bool {
+func (o *lazyObject) defineOwnPropertyStr(name string, desc PropertyDescriptor, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.hasProperty(n)
+	return obj.defineOwnPropertyStr(name, desc, throw)
 }
 
-func (o *lazyObject) hasPropertyStr(name string) bool {
+func (o *lazyObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.hasPropertyStr(name)
+	return obj.defineOwnPropertyIdx(name, desc, throw)
 }
 
-func (o *lazyObject) hasOwnProperty(n Value) bool {
+func (o *lazyObject) defineOwnPropertySym(name *valueSymbol, desc PropertyDescriptor, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.hasOwnProperty(n)
+	return obj.defineOwnPropertySym(name, desc, throw)
 }
 
-func (o *lazyObject) hasOwnPropertyStr(name string) bool {
+func (o *lazyObject) deleteIdx(idx valueInt, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.hasOwnPropertyStr(name)
+	return obj.deleteIdx(idx, throw)
+}
+
+func (o *lazyObject) deleteSym(s *valueSymbol, throw bool) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.deleteSym(s, throw)
+}
+
+func (o *lazyObject) getStr(name string, receiver Value) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.getStr(name, receiver)
+}
+
+func (o *lazyObject) getOwnPropStr(name string) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.getOwnPropStr(name)
+}
+
+func (o *lazyObject) setOwnStr(p string, v Value, throw bool) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.setOwnStr(p, v, throw)
+}
+
+func (o *lazyObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.setOwnIdx(p, v, throw)
+}
+
+func (o *lazyObject) setOwnSym(p *valueSymbol, v Value, throw bool) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.setOwnSym(p, v, throw)
 }
 
-func (o *lazyObject) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+func (o *lazyObject) setForeignStr(p string, v, receiver Value, throw bool) (bool, bool) {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj._putProp(name, value, writable, enumerable, configurable)
+	return obj.setForeignStr(p, v, receiver, throw)
 }
 
-func (o *lazyObject) defineOwnProperty(name Value, descr propertyDescr, throw bool) bool {
+func (o *lazyObject) setForeignIdx(p valueInt, v, receiver Value, throw bool) (bool, bool) {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.defineOwnProperty(name, descr, throw)
+	return obj.setForeignIdx(p, v, receiver, throw)
+}
+
+func (o *lazyObject) setForeignSym(p *valueSymbol, v, receiver Value, throw bool) (bool, bool) {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.setForeignSym(p, v, receiver, throw)
+}
+
+func (o *lazyObject) hasPropertyStr(name string) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.hasPropertyStr(name)
+}
+
+func (o *lazyObject) hasOwnPropertyStr(name string) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.hasOwnPropertyStr(name)
+}
+
+func (o *lazyObject) _putProp(string, Value, bool, bool, bool) Value {
+	panic("cannot use _putProp() in lazy object")
+}
+
+func (o *lazyObject) _putSym(*valueSymbol, Value) {
+	panic("cannot use _putSym() in lazy object")
 }
 
 func (o *lazyObject) toPrimitiveNumber() Value {
@@ -121,16 +183,16 @@ func (o *lazyObject) assertCallable() (call func(FunctionCall) Value, ok bool) {
 	return obj.assertCallable()
 }
 
-func (o *lazyObject) deleteStr(name string, throw bool) bool {
+func (o *lazyObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.deleteStr(name, throw)
+	return obj.assertConstructor()
 }
 
-func (o *lazyObject) delete(name Value, throw bool) bool {
+func (o *lazyObject) deleteStr(name string, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.delete(name, throw)
+	return obj.deleteStr(name, throw)
 }
 
 func (o *lazyObject) proto() *Object {
@@ -151,22 +213,22 @@ func (o *lazyObject) isExtensible() bool {
 	return obj.isExtensible()
 }
 
-func (o *lazyObject) preventExtensions() {
+func (o *lazyObject) preventExtensions(throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	obj.preventExtensions()
+	return obj.preventExtensions(throw)
 }
 
-func (o *lazyObject) enumerate(all, recusrive bool) iterNextFunc {
+func (o *lazyObject) enumerateUnfiltered() iterNextFunc {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.enumerate(all, recusrive)
+	return obj.enumerateUnfiltered()
 }
 
-func (o *lazyObject) _enumerate(recursive bool) iterNextFunc {
+func (o *lazyObject) enumerate() iterNextFunc {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj._enumerate(recursive)
+	return obj.enumerate()
 }
 
 func (o *lazyObject) export() interface{} {
@@ -187,16 +249,28 @@ func (o *lazyObject) equal(other objectImpl) bool {
 	return obj.equal(other)
 }
 
-func (o *lazyObject) getOwnSymbols() []Value {
+func (o *lazyObject) ownKeys(all bool, accum []Value) []Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.ownKeys(all, accum)
+}
+
+func (o *lazyObject) ownSymbols() []Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.ownSymbols()
+}
+
+func (o *lazyObject) ownPropertyKeys(all bool, accum []Value) []Value {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.getOwnSymbols()
+	return obj.ownPropertyKeys(all, accum)
 }
 
-func (o *lazyObject) setProto(proto *Object) *Object {
+func (o *lazyObject) setProto(proto *Object, throw bool) bool {
 	obj := o.create(o.val)
 	o.val.self = obj
-	return obj.setProto(proto)
+	return obj.setProto(proto, throw)
 }
 
 func (o *lazyObject) sortLen() int64 {

+ 46 - 16
object_test.go

@@ -5,8 +5,8 @@ import "testing"
 func TestArray1(t *testing.T) {
 	r := &Runtime{}
 	a := r.newArray(nil)
-	a.put(valueInt(0), asciiString("test"), true)
-	if l := a.getStr("length").ToInteger(); l != 1 {
+	a.setOwnIdx(valueInt(0), asciiString("test"), true)
+	if l := a.getStr("length", nil).ToInteger(); l != 1 {
 		t.Fatalf("Unexpected length: %d", l)
 	}
 }
@@ -67,6 +67,36 @@ func TestDefineProperty(t *testing.T) {
 	}
 }
 
+func TestPropertyOrder(t *testing.T) {
+	const SCRIPT = `
+	var o = {};
+	var sym1 = Symbol(1);
+	var sym2 = Symbol(2);
+	o[sym2] = 1;
+	o[4294967294] = 1;
+	o[2] = 1;
+	o[1] = 1;
+	o[0] = 1;
+	o["02"] = 1;
+	o[4294967295] = 1;
+	o["01"] = 1;
+	o["00"] = 1;
+	o[sym1] = 1;
+	var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1];
+	var actual = Reflect.ownKeys(o);
+	if (actual.length !== expected.length) {
+		throw new Error("Unexpected length: "+actual.length);
+	}
+	for (var i = 0; i < actual.length; i++) {
+		if (actual[i] !== expected[i]) {
+			throw new Error("Unexpected list: " + actual);
+		}
+	}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
 func BenchmarkPut(b *testing.B) {
 	v := &Object{}
 
@@ -82,7 +112,7 @@ func BenchmarkPut(b *testing.B) {
 	var val Value = valueInt(123)
 
 	for i := 0; i < b.N; i++ {
-		o.put(key, val, false)
+		v.setOwn(key, val, false)
 	}
 }
 
@@ -101,7 +131,7 @@ func BenchmarkPutStr(b *testing.B) {
 	var val Value = valueInt(123)
 
 	for i := 0; i < b.N; i++ {
-		o.putStr("test", val, false)
+		o.setOwnStr("test", val, false)
 	}
 }
 
@@ -119,7 +149,7 @@ func BenchmarkGet(b *testing.B) {
 	var n Value = asciiString("test")
 
 	for i := 0; i < b.N; i++ {
-		o.get(n)
+		v.get(n, nil)
 	}
 
 }
@@ -136,7 +166,7 @@ func BenchmarkGetStr(b *testing.B) {
 	o.init()
 
 	for i := 0; i < b.N; i++ {
-		o.getStr("test")
+		o.getStr("test", nil)
 	}
 }
 
@@ -190,11 +220,11 @@ func BenchmarkArrayGetStr(b *testing.B) {
 
 	a.init()
 
-	a.put(valueInt(0), asciiString("test"), false)
+	v.setOwn(valueInt(0), asciiString("test"), false)
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		a.getStr("0")
+		a.getStr("0", nil)
 	}
 
 }
@@ -216,12 +246,12 @@ func BenchmarkArrayGet(b *testing.B) {
 
 	var idx Value = valueInt(0)
 
-	a.put(idx, asciiString("test"), false)
+	v.setOwn(idx, asciiString("test"), false)
 
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		a.get(idx)
+		v.get(idx, nil)
 	}
 
 }
@@ -249,7 +279,7 @@ func BenchmarkArrayPut(b *testing.B) {
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		a.put(idx, val, false)
+		v.setOwn(idx, val, false)
 	}
 
 }
@@ -267,9 +297,9 @@ func BenchmarkAdd(b *testing.B) {
 	y = valueInt(2)
 
 	for i := 0; i < b.N; i++ {
-		if xi, ok := x.assertInt(); ok {
-			if yi, ok := y.assertInt(); ok {
-				x = valueInt(xi + yi)
+		if xi, ok := x.(valueInt); ok {
+			if yi, ok := y.(valueInt); ok {
+				x = xi + yi
 			}
 		}
 	}
@@ -284,8 +314,8 @@ func BenchmarkAddString(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		var z Value
-		if xi, ok := x.assertString(); ok {
-			if yi, ok := y.assertString(); ok {
+		if xi, ok := x.(valueString); ok {
+			if yi, ok := y.(valueString); ok {
 				z = xi.concat(yi)
 			}
 		}

+ 17 - 0
parser/expression.go

@@ -373,6 +373,23 @@ func (self *_parser) parseBracketMember(left ast.Expression) ast.Expression {
 
 func (self *_parser) parseNewExpression() ast.Expression {
 	idx := self.expect(token.NEW)
+	if self.token == token.PERIOD {
+		self.next()
+		prop := self.parseIdentifier()
+		if prop.Name == "target" {
+			if !self.scope.inFunction {
+				self.error(idx, "new.target expression is not allowed here")
+			}
+			return &ast.MetaProperty{
+				Meta: &ast.Identifier{
+					Name: token.NEW.String(),
+					Idx:  idx,
+				},
+				Property: prop,
+			}
+		}
+		self.errorUnexpectedToken(token.IDENTIFIER)
+	}
 	callee := self.parseLeftHandSideExpression()
 	node := &ast.NewExpression{
 		New:    idx,

+ 764 - 0
proxy.go

@@ -0,0 +1,764 @@
+package goja
+
+type Proxy struct {
+	proxy *proxyObject
+}
+
+type proxyPropIter struct {
+	p     *proxyObject
+	names []Value
+	idx   int
+}
+
+func (i *proxyPropIter) next() (propIterItem, iterNextFunc) {
+	for i.idx < len(i.names) {
+		name := i.names[i.idx]
+		i.idx++
+		if prop := i.p.val.getOwnProp(name); prop != nil {
+			return propIterItem{name: name.String(), value: prop}, i.next
+		}
+	}
+	if proto := i.p.proto(); proto != nil {
+		return proto.self.enumerateUnfiltered()()
+	}
+	return propIterItem{}, nil
+}
+
+func (r *Runtime) newProxyObject(target *Object, handler *Object, proto *Object) *proxyObject {
+	if p, ok := target.self.(*proxyObject); ok {
+		if p.handler == nil {
+			panic(r.NewTypeError("Cannot create proxy with a revoked proxy as target"))
+		}
+	}
+	if p, ok := handler.self.(*proxyObject); ok {
+		if p.handler == nil {
+			panic(r.NewTypeError("Cannot create proxy with a revoked proxy as handler"))
+		}
+	}
+	v := &Object{runtime: r}
+	p := &proxyObject{}
+	v.self = p
+	p.val = v
+	p.class = classObject
+	if proto == nil {
+		p.prototype = r.global.ObjectPrototype
+	} else {
+		p.prototype = proto
+	}
+	p.extensible = false
+	p.init()
+	p.target = target
+	p.handler = handler
+	if call, ok := target.self.assertCallable(); ok {
+		p.call = call
+	}
+	if ctor := target.self.assertConstructor(); ctor != nil {
+		p.ctor = ctor
+	}
+	return p
+}
+
+func (p *Proxy) Revoke() {
+	p.proxy.revoke()
+}
+
+type proxyTrap string
+
+const (
+	proxy_trap_getPrototypeOf           = "getPrototypeOf"
+	proxy_trap_setPrototypeOf           = "setPrototypeOf"
+	proxy_trap_isExtensible             = "isExtensible"
+	proxy_trap_preventExtensions        = "preventExtensions"
+	proxy_trap_getOwnPropertyDescriptor = "getOwnPropertyDescriptor"
+	proxy_trap_defineProperty           = "defineProperty"
+	proxy_trap_has                      = "has"
+	proxy_trap_get                      = "get"
+	proxy_trap_set                      = "set"
+	proxy_trap_deleteProperty           = "deleteProperty"
+	proxy_trap_ownKeys                  = "ownKeys"
+	proxy_trap_apply                    = "apply"
+	proxy_trap_construct                = "construct"
+)
+
+func (p proxyTrap) String() (name string) {
+	return string(p)
+}
+
+type proxyObject struct {
+	baseObject
+	target  *Object
+	handler *Object
+	call    func(FunctionCall) Value
+	ctor    func(args []Value, newTarget *Object) *Object
+}
+
+func (p *proxyObject) proxyCall(trap proxyTrap, args ...Value) (Value, bool) {
+	r := p.val.runtime
+	if p.handler == nil {
+		panic(r.NewTypeError("Proxy already revoked"))
+	}
+
+	if m := toMethod(r.getVStr(p.handler, trap.String())); m != nil {
+		return m(FunctionCall{
+			This:      p.handler,
+			Arguments: args,
+		}), true
+	}
+
+	return nil, false
+}
+
+func (p *proxyObject) proto() *Object {
+	if v, ok := p.proxyCall(proxy_trap_getPrototypeOf, p.target); ok {
+		var handlerProto *Object
+		if v != _null {
+			handlerProto = p.val.runtime.toObject(v)
+		}
+		if !p.target.self.isExtensible() && !p.__sameValue(handlerProto, p.target.self.proto()) {
+			panic(p.val.runtime.NewTypeError("'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype"))
+		}
+		return handlerProto
+	}
+
+	return p.target.self.proto()
+}
+
+func (p *proxyObject) setProto(proto *Object, throw bool) bool {
+	if v, ok := p.proxyCall(proxy_trap_setPrototypeOf, p.target, proto); ok {
+		if v.ToBoolean() {
+			if !p.target.self.isExtensible() && !p.__sameValue(proto, p.target.self.proto()) {
+				panic(p.val.runtime.NewTypeError("'setPrototypeOf' on proxy: trap returned truish for setting a new prototype on the non-extensible proxy target"))
+			}
+			return true
+		} else {
+			p.val.runtime.typeErrorResult(throw, "'setPrototypeOf' on proxy: trap returned falsish")
+		}
+	}
+
+	return p.target.self.setProto(proto, throw)
+}
+
+func (p *proxyObject) isExtensible() bool {
+	if v, ok := p.proxyCall(proxy_trap_isExtensible, p.target); ok {
+		booleanTrapResult := v.ToBoolean()
+		if te := p.target.self.isExtensible(); booleanTrapResult != te {
+			panic(p.val.runtime.NewTypeError("'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is '%v')", te))
+		}
+		return booleanTrapResult
+	}
+
+	return p.target.self.isExtensible()
+}
+
+func (p *proxyObject) preventExtensions(throw bool) bool {
+	if v, ok := p.proxyCall(proxy_trap_preventExtensions, p.target); ok {
+		booleanTrapResult := v.ToBoolean()
+		if !booleanTrapResult {
+			p.val.runtime.typeErrorResult(throw, "'preventExtensions' on proxy: trap returned falsish")
+			return false
+		}
+		if te := p.target.self.isExtensible(); booleanTrapResult && te {
+			panic(p.val.runtime.NewTypeError("'preventExtensions' on proxy: trap returned truish but the proxy target is extensible"))
+		}
+	}
+
+	return p.target.self.preventExtensions(throw)
+}
+
+func propToValueProp(v Value) *valueProperty {
+	if v == nil {
+		return nil
+	}
+	if v, ok := v.(*valueProperty); ok {
+		return v
+	}
+	return &valueProperty{
+		value:        v,
+		writable:     true,
+		configurable: true,
+		enumerable:   true,
+	}
+}
+
+func (p *proxyObject) proxyDefineOwnProperty(name Value, descr PropertyDescriptor, throw bool) (bool, bool) {
+	if v, ok := p.proxyCall(proxy_trap_defineProperty, p.target, name, descr.toValue(p.val.runtime)); ok {
+		booleanTrapResult := v.ToBoolean()
+		if !booleanTrapResult {
+			p.val.runtime.typeErrorResult(throw, "'defineProperty' on proxy: trap returned falsish")
+			return false, true
+		}
+		targetDesc := propToValueProp(p.target.getOwnProp(name))
+		extensibleTarget := p.target.self.isExtensible()
+		settingConfigFalse := descr.Configurable == FLAG_FALSE
+		if targetDesc == nil {
+			if !extensibleTarget {
+				panic(p.val.runtime.NewTypeError())
+			}
+			if settingConfigFalse {
+				panic(p.val.runtime.NewTypeError())
+			}
+		} else {
+			if !p.__isCompatibleDescriptor(extensibleTarget, &descr, targetDesc) {
+				panic(p.val.runtime.NewTypeError())
+			}
+			if settingConfigFalse && targetDesc.configurable {
+				panic(p.val.runtime.NewTypeError())
+			}
+		}
+		return booleanTrapResult, true
+	}
+	return false, false
+}
+
+func (p *proxyObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+	if v, ok := p.proxyDefineOwnProperty(newStringValue(name), descr, throw); ok {
+		return v
+	}
+	return p.target.self.defineOwnPropertyStr(name, descr, throw)
+}
+
+func (p *proxyObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
+	if v, ok := p.proxyDefineOwnProperty(idx, descr, throw); ok {
+		return v
+	}
+	return p.target.self.defineOwnPropertyIdx(idx, descr, throw)
+}
+
+func (p *proxyObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescriptor, throw bool) bool {
+	if v, ok := p.proxyDefineOwnProperty(s, descr, throw); ok {
+		return v
+	}
+	return p.target.self.defineOwnPropertySym(s, descr, throw)
+}
+
+func (p *proxyObject) proxyHas(name Value) (bool, bool) {
+	if v, ok := p.proxyCall(proxy_trap_has, p.target, name); ok {
+		booleanTrapResult := v.ToBoolean()
+		if !booleanTrapResult {
+			targetDesc := propToValueProp(p.target.getOwnProp(name))
+			if targetDesc != nil {
+				if !targetDesc.configurable {
+					panic(p.val.runtime.NewTypeError("'has' on proxy: trap returned falsish for property '%s' which exists in the proxy target as non-configurable", name.String()))
+				}
+				if !p.target.self.isExtensible() {
+					panic(p.val.runtime.NewTypeError("'has' on proxy: trap returned falsish for property '%s' but the proxy target is not extensible", name.String()))
+				}
+			}
+		}
+		return booleanTrapResult, true
+	}
+
+	return false, false
+}
+
+func (p *proxyObject) hasPropertyStr(name string) bool {
+	if b, ok := p.proxyHas(newStringValue(name)); ok {
+		return b
+	}
+
+	return p.target.self.hasPropertyStr(name)
+}
+
+func (p *proxyObject) hasPropertyIdx(idx valueInt) bool {
+	if b, ok := p.proxyHas(idx); ok {
+		return b
+	}
+
+	return p.target.self.hasPropertyIdx(idx)
+}
+
+func (p *proxyObject) hasPropertySym(s *valueSymbol) bool {
+	if b, ok := p.proxyHas(s); ok {
+		return b
+	}
+
+	return p.target.self.hasPropertySym(s)
+}
+
+func (p *proxyObject) hasOwnPropertyStr(name string) bool {
+	return p.getOwnPropStr(name) != nil
+}
+
+func (p *proxyObject) hasOwnPropertyIdx(idx valueInt) bool {
+	return p.getOwnPropIdx(idx) != nil
+}
+
+func (p *proxyObject) hasOwnPropertySym(s *valueSymbol) bool {
+	return p.getOwnPropSym(s) != nil
+}
+
+func (p *proxyObject) proxyGetOwnPropertyDescriptor(name Value) (Value, bool) {
+	target := p.target
+	if v, ok := p.proxyCall(proxy_trap_getOwnPropertyDescriptor, target, name); ok {
+		r := p.val.runtime
+
+		targetDesc := propToValueProp(target.getOwnProp(name))
+
+		var trapResultObj *Object
+		if v != nil && v != _undefined {
+			if obj, ok := v.(*Object); ok {
+				trapResultObj = obj
+			} else {
+				panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined for property '%s'", name.String()))
+			}
+		}
+		if trapResultObj == nil {
+			if targetDesc == nil {
+				return nil, true
+			}
+			if !targetDesc.configurable {
+				panic(r.NewTypeError())
+			}
+			if !target.self.isExtensible() {
+				panic(r.NewTypeError())
+			}
+			return nil, true
+		}
+		extensibleTarget := target.self.isExtensible()
+		resultDesc := r.toPropertyDescriptor(trapResultObj)
+		resultDesc.complete()
+		if !p.__isCompatibleDescriptor(extensibleTarget, &resultDesc, targetDesc) {
+			panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap returned descriptor for property '%s' that is incompatible with the existing property in the proxy target", name.String()))
+		}
+
+		if resultDesc.Configurable == FLAG_FALSE {
+			if targetDesc == nil {
+				panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '%s' which is non-existent in the proxy target", name.String()))
+			}
+
+			if targetDesc.configurable {
+				panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '%s' which is configurable in the proxy target", name.String()))
+			}
+		}
+
+		if resultDesc.Writable == FLAG_TRUE && resultDesc.Configurable == FLAG_TRUE &&
+			resultDesc.Enumerable == FLAG_TRUE {
+			return resultDesc.Value, true
+		}
+		return r.toValueProp(trapResultObj), true
+	}
+
+	return nil, false
+}
+
+func (p *proxyObject) getOwnPropStr(name string) Value {
+	if v, ok := p.proxyGetOwnPropertyDescriptor(newStringValue(name)); ok {
+		return v
+	}
+
+	return p.target.self.getOwnPropStr(name)
+}
+
+func (p *proxyObject) getOwnPropIdx(idx valueInt) Value {
+	if v, ok := p.proxyGetOwnPropertyDescriptor(idx.toString()); ok {
+		return v
+	}
+
+	return p.target.self.getOwnPropIdx(idx)
+}
+
+func (p *proxyObject) getOwnPropSym(s *valueSymbol) Value {
+	if v, ok := p.proxyGetOwnPropertyDescriptor(s); ok {
+		return v
+	}
+
+	return p.target.self.getOwnPropSym(s)
+}
+
+func (p *proxyObject) getStr(name string, receiver Value) Value {
+	if v, ok := p.proxyGet(newStringValue(name), receiver); ok {
+		return v
+	}
+	return p.target.self.getStr(name, receiver)
+}
+
+func (p *proxyObject) getIdx(idx valueInt, receiver Value) Value {
+	if v, ok := p.proxyGet(idx.toString(), receiver); ok {
+		return v
+	}
+	return p.target.self.getIdx(idx, receiver)
+}
+
+func (p *proxyObject) getSym(s *valueSymbol, receiver Value) Value {
+	if v, ok := p.proxyGet(s, receiver); ok {
+		return v
+	}
+	return p.target.self.getSym(s, receiver)
+
+}
+
+func (p *proxyObject) proxyGet(name, receiver Value) (Value, bool) {
+	target := p.target
+	if v, ok := p.proxyCall(proxy_trap_get, target, name, receiver); ok {
+		if targetDesc, ok := target.getOwnProp(name).(*valueProperty); ok {
+			if !targetDesc.accessor {
+				if !targetDesc.writable && !targetDesc.configurable && !v.SameAs(targetDesc.value) {
+					panic(p.val.runtime.NewTypeError("'get' on proxy: property '%s' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '%s' but got '%s')", name.String(), nilSafe(targetDesc.value), ret))
+				}
+			} else {
+				if !targetDesc.configurable && targetDesc.getterFunc == nil && v != _undefined {
+					panic(p.val.runtime.NewTypeError("'get' on proxy: property '%s' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '%s')", name.String(), ret))
+				}
+			}
+		}
+		return v, true
+	}
+
+	return nil, false
+}
+
+func (p *proxyObject) proxySet(name, value, receiver Value, throw bool) (bool, bool) {
+	target := p.target
+	if v, ok := p.proxyCall(proxy_trap_set, target, name, value, receiver); ok {
+		if v.ToBoolean() {
+			if prop, ok := target.getOwnProp(name).(*valueProperty); ok {
+				if prop.accessor {
+					if !prop.configurable && prop.setterFunc == nil {
+						panic(p.val.runtime.NewTypeError("'set' on proxy: trap returned truish for property '%s' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter", name.String()))
+					}
+				} else if !prop.configurable && !prop.writable && !p.__sameValue(prop.value, value) {
+					panic(p.val.runtime.NewTypeError("'set' on proxy: trap returned truish for property '%s' which exists in the proxy target as a non-configurable and non-writable data property with a different value", name.String()))
+				}
+			}
+			return true, true
+		}
+		if throw {
+			panic(p.val.runtime.NewTypeError("'set' on proxy: trap returned falsish for property '%s'", name.String()))
+		}
+		return false, true
+	}
+
+	return false, false
+}
+
+func (p *proxyObject) setOwnStr(name string, v Value, throw bool) bool {
+	if res, ok := p.proxySet(newStringValue(name), v, p.val, throw); ok {
+		return res
+	}
+	return p.target.setStr(name, v, p.val, throw)
+}
+
+func (p *proxyObject) setOwnIdx(idx valueInt, v Value, throw bool) bool {
+	if res, ok := p.proxySet(idx.toString(), v, p.val, throw); ok {
+		return res
+	}
+	return p.target.setIdx(idx, v, p.val, throw)
+}
+
+func (p *proxyObject) setOwnSym(s *valueSymbol, v Value, throw bool) bool {
+	if res, ok := p.proxySet(s, v, p.val, throw); ok {
+		return res
+	}
+	return p.target.setSym(s, v, p.val, throw)
+}
+
+func (p *proxyObject) setForeignStr(name string, v, receiver Value, throw bool) (bool, bool) {
+	if res, ok := p.proxySet(newStringValue(name), v, receiver, throw); ok {
+		return res, true
+	}
+	return p.target.setStr(name, v, receiver, throw), true
+}
+
+func (p *proxyObject) setForeignIdx(idx valueInt, v, receiver Value, throw bool) (bool, bool) {
+	if res, ok := p.proxySet(idx.toString(), v, receiver, throw); ok {
+		return res, true
+	}
+	return p.target.setIdx(idx, v, receiver, throw), true
+}
+
+func (p *proxyObject) setForeignSym(s *valueSymbol, v, receiver Value, throw bool) (bool, bool) {
+	if res, ok := p.proxySet(s, v, receiver, throw); ok {
+		return res, true
+	}
+	return p.target.setSym(s, v, receiver, throw), true
+}
+
+func (p *proxyObject) proxyDelete(n Value) (bool, bool) {
+	target := p.target
+	if v, ok := p.proxyCall(proxy_trap_deleteProperty, target, n); ok {
+		if v.ToBoolean() {
+			if targetDesc, ok := target.getOwnProp(n).(*valueProperty); ok {
+				if !targetDesc.configurable {
+					panic(p.val.runtime.NewTypeError("'deleteProperty' on proxy: property '%s' is a non-configurable property but the trap returned truish", n.String()))
+				}
+			}
+			return true, true
+		}
+		return false, true
+	}
+	return false, false
+}
+
+func (p *proxyObject) deleteStr(name string, throw bool) bool {
+	if ret, ok := p.proxyDelete(newStringValue(name)); ok {
+		return ret
+	}
+
+	return p.target.self.deleteStr(name, throw)
+}
+
+func (p *proxyObject) deleteIdx(idx valueInt, throw bool) bool {
+	if ret, ok := p.proxyDelete(idx.toString()); ok {
+		return ret
+	}
+
+	return p.target.self.deleteIdx(idx, throw)
+}
+
+func (p *proxyObject) deleteSym(s *valueSymbol, throw bool) bool {
+	if ret, ok := p.proxyDelete(s); ok {
+		return ret
+	}
+
+	return p.target.self.deleteSym(s, throw)
+}
+
+func (p *proxyObject) ownPropertyKeys(all bool, _ []Value) []Value {
+	if v, ok := p.proxyOwnKeys(); ok {
+		return v
+	}
+	return p.target.self.ownPropertyKeys(all, nil)
+}
+
+func (p *proxyObject) proxyOwnKeys() ([]Value, bool) {
+	target := p.target
+	if v, ok := p.proxyCall(proxy_trap_ownKeys, p.target); ok {
+		keys := p.val.runtime.toObject(v)
+		var keyList []Value
+		keySet := make(map[Value]struct{})
+		l := toLength(keys.self.getStr("length", nil))
+		for k := int64(0); k < l; k++ {
+			item := keys.self.getIdx(valueInt(k), nil)
+			if _, ok := item.(valueString); !ok {
+				if _, ok := item.(*valueSymbol); !ok {
+					panic(p.val.runtime.NewTypeError("%s is not a valid property name", item.String()))
+				}
+			}
+			keyList = append(keyList, item)
+			keySet[item] = struct{}{}
+		}
+		ext := target.self.isExtensible()
+		for _, itemName := range target.self.ownPropertyKeys(true, nil) {
+			if _, exists := keySet[itemName]; exists {
+				delete(keySet, itemName)
+			} else {
+				if !ext {
+					panic(p.val.runtime.NewTypeError("'ownKeys' on proxy: trap result did not include '%s'", itemName.String()))
+				}
+				prop := target.getOwnProp(itemName)
+				if prop, ok := prop.(*valueProperty); ok && !prop.configurable {
+					panic(p.val.runtime.NewTypeError("'ownKeys' on proxy: trap result did not include non-configurable '%s'", itemName.String()))
+				}
+			}
+		}
+		if !ext && len(keyList) > 0 && len(keySet) > 0 {
+			panic(p.val.runtime.NewTypeError("'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible"))
+		}
+
+		return keyList, true
+	}
+
+	return nil, false
+}
+
+func (p *proxyObject) enumerateUnfiltered() iterNextFunc {
+	return (&proxyPropIter{
+		p:     p,
+		names: p.ownKeys(true, nil),
+	}).next
+}
+
+func (p *proxyObject) assertCallable() (call func(FunctionCall) Value, ok bool) {
+	if p.call != nil {
+		return func(call FunctionCall) Value {
+			return p.apply(call)
+		}, true
+	}
+	return nil, false
+}
+
+func (p *proxyObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
+	if p.ctor != nil {
+		return p.construct
+	}
+	return nil
+}
+
+func (p *proxyObject) apply(call FunctionCall) Value {
+	if p.call == nil {
+		p.val.runtime.NewTypeError("proxy target is not a function")
+	}
+	if v, ok := p.proxyCall(proxy_trap_apply, p.target, nilSafe(call.This), p.val.runtime.newArrayValues(call.Arguments)); ok {
+		return v
+	}
+	return p.call(call)
+}
+
+func (p *proxyObject) construct(args []Value, newTarget *Object) *Object {
+	if p.ctor == nil {
+		panic(p.val.runtime.NewTypeError("proxy target is not a constructor"))
+	}
+	if newTarget == nil {
+		newTarget = p.val
+	}
+	if v, ok := p.proxyCall(proxy_trap_construct, p.target, p.val.runtime.newArrayValues(args), newTarget); ok {
+		return p.val.runtime.toObject(v)
+	}
+	return p.ctor(args, newTarget)
+}
+
+func (p *proxyObject) __isCompatibleDescriptor(extensible bool, desc *PropertyDescriptor, current *valueProperty) bool {
+	if current == nil {
+		return extensible
+	}
+
+	/*if desc.Empty() {
+		return true
+	}*/
+
+	/*if p.__isEquivalentDescriptor(desc, current) {
+		return true
+	}*/
+
+	if !current.configurable {
+		if desc.Configurable == FLAG_TRUE {
+			return false
+		}
+
+		if desc.Enumerable != FLAG_NOT_SET && desc.Enumerable.Bool() != current.enumerable {
+			return false
+		}
+
+		if p.__isGenericDescriptor(desc) {
+			return true
+		}
+
+		if p.__isDataDescriptor(desc) != !current.accessor {
+			return desc.Configurable != FLAG_FALSE
+		}
+
+		if p.__isDataDescriptor(desc) && !current.accessor {
+			if desc.Configurable == FLAG_FALSE {
+				if desc.Writable == FLAG_FALSE && current.writable {
+					return false
+				}
+				if desc.Writable == FLAG_FALSE {
+					if desc.Value != nil && !desc.Value.SameAs(current.value) {
+						return false
+					}
+				}
+			}
+			return true
+		}
+		if p.__isAccessorDescriptor(desc) && current.accessor {
+			if desc.Configurable == FLAG_FALSE {
+				if desc.Setter != nil && desc.Setter.SameAs(current.setterFunc) {
+					return false
+				}
+				if desc.Getter != nil && desc.Getter.SameAs(current.getterFunc) {
+					return false
+				}
+			}
+		}
+	}
+	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
+	}
+	if val1 != nil {
+		return val1.SameAs(val2)
+	}
+	return false
+}
+
+func (p *proxyObject) filterKeys(vals []Value, all, symbols bool) []Value {
+	if !all {
+		k := 0
+		for i, val := range vals {
+			var prop Value
+			if symbols {
+				if s, ok := val.(*valueSymbol); ok {
+					prop = p.getOwnPropSym(s)
+				} else {
+					continue
+				}
+			} else {
+				if _, ok := val.(*valueSymbol); !ok {
+					prop = p.getOwnPropStr(val.String())
+				} else {
+					continue
+				}
+			}
+			if prop == nil {
+				continue
+			}
+			if prop, ok := prop.(*valueProperty); ok && !prop.enumerable {
+				continue
+			}
+			if k != i {
+				vals[k] = vals[i]
+			}
+			k++
+		}
+		vals = vals[:k]
+	} else {
+		k := 0
+		for i, val := range vals {
+			if _, ok := val.(*valueSymbol); ok != symbols {
+				continue
+			}
+			if k != i {
+				vals[k] = vals[i]
+			}
+			k++
+		}
+		vals = vals[:k]
+	}
+	return vals
+}
+
+func (p *proxyObject) ownKeys(all bool, _ []Value) []Value { // we can assume accum is empty
+	if vals, ok := p.proxyOwnKeys(); ok {
+		return p.filterKeys(vals, all, false)
+	}
+
+	return p.target.self.ownKeys(all, nil)
+}
+
+func (p *proxyObject) ownSymbols() []Value {
+	if vals, ok := p.proxyOwnKeys(); ok {
+		return p.filterKeys(vals, true, true)
+	}
+
+	return p.target.self.ownSymbols()
+}
+
+func (p *proxyObject) className() string {
+	if p.target == nil {
+		panic(p.val.runtime.NewTypeError("proxy has been revoked"))
+	}
+	if p.call != nil || p.ctor != nil {
+		return classFunction
+	}
+	return classObject
+}
+
+func (p *proxyObject) revoke() {
+	p.handler = nil
+	p.target = nil
+}

+ 5 - 5
regexp.go

@@ -294,14 +294,14 @@ func (r *regexpObject) execResultToArray(target valueString, result []int) Value
 		}
 	}
 	match := r.val.runtime.newArrayValues(valueArray)
-	match.self.putStr("input", target, false)
-	match.self.putStr("index", intToValue(int64(matchIndex)), false)
+	match.self.setOwnStr("input", target, false)
+	match.self.setOwnStr("index", intToValue(int64(matchIndex)), false)
 	return match
 }
 
 func (r *regexpObject) execRegexp(target valueString) (match bool, result []int) {
 	lastIndex := int64(0)
-	if p := r.getStr("lastIndex"); p != nil {
+	if p := r.getStr("lastIndex", nil); p != nil {
 		lastIndex = p.ToInteger()
 		if lastIndex < 0 {
 			lastIndex = 0
@@ -315,7 +315,7 @@ func (r *regexpObject) execRegexp(target valueString) (match bool, result []int)
 		result = r.pattern.FindSubmatchIndex(target, int(index))
 	}
 	if result == nil || r.sticky && result[0] != 0 {
-		r.putStr("lastIndex", intToValue(0), true)
+		r.setOwnStr("lastIndex", intToValue(0), true)
 		return
 	}
 	match = true
@@ -325,7 +325,7 @@ func (r *regexpObject) execRegexp(target valueString) (match bool, result []int)
 		result[i] += int(index)
 	}
 	if r.global || r.sticky {
-		r.putStr("lastIndex", intToValue(int64(result[1])), true)
+		r.setOwnStr("lastIndex", intToValue(int64(result[1])), true)
 	}
 	return
 }

+ 178 - 118
runtime.go

@@ -15,6 +15,7 @@ import (
 
 	js_ast "github.com/dop251/goja/ast"
 	"github.com/dop251/goja/parser"
+	"runtime"
 )
 
 const (
@@ -24,6 +25,7 @@ const (
 var (
 	typeCallable = reflect.TypeOf(Callable(nil))
 	typeValue    = reflect.TypeOf((*Value)(nil)).Elem()
+	typeObject   = reflect.TypeOf((*Object)(nil))
 	typeTime     = reflect.TypeOf(time.Time{})
 )
 
@@ -45,6 +47,7 @@ type global struct {
 	RegExp   *Object
 	Date     *Object
 	Symbol   *Object
+	Proxy    *Object
 
 	ArrayBuffer *Object
 	WeakSet     *Object
@@ -146,17 +149,40 @@ type Runtime struct {
 	vm *vm
 }
 
-type stackFrame struct {
+type StackFrame struct {
 	prg      *Program
 	funcName string
 	pc       int
 }
 
-func (f *stackFrame) position() Position {
+func (f *StackFrame) SrcName() string {
+	if f.prg == nil {
+		return "<native>"
+	}
+	return f.prg.src.name
+}
+
+func (f *StackFrame) FuncName() string {
+	if f.funcName == "" && f.prg == nil {
+		return "<native>"
+	}
+	if f.funcName == "" {
+		return "<anonymous>"
+	}
+	return f.funcName
+}
+
+func (f *StackFrame) Position() Position {
+	if f.prg == nil || f.prg.src == nil {
+		return Position{
+			0,
+			0,
+		}
+	}
 	return f.prg.src.Position(f.prg.sourceOffset(f.pc))
 }
 
-func (f *stackFrame) write(b *bytes.Buffer) {
+func (f *StackFrame) Write(b *bytes.Buffer) {
 	if f.prg != nil {
 		if n := f.prg.funcName; n != "" {
 			b.WriteString(n)
@@ -168,7 +194,7 @@ func (f *stackFrame) write(b *bytes.Buffer) {
 			b.WriteString("<eval>")
 		}
 		b.WriteByte(':')
-		b.WriteString(f.position().String())
+		b.WriteString(f.Position().String())
 		b.WriteByte('(')
 		b.WriteString(strconv.Itoa(f.pc))
 		b.WriteByte(')')
@@ -189,7 +215,7 @@ func (f *stackFrame) write(b *bytes.Buffer) {
 
 type Exception struct {
 	val   Value
-	stack []stackFrame
+	stack []StackFrame
 }
 
 type InterruptedError struct {
@@ -227,7 +253,7 @@ func (e *InterruptedError) Error() string {
 func (e *Exception) writeFullStack(b *bytes.Buffer) {
 	for _, frame := range e.stack {
 		b.WriteString("\tat ")
-		frame.write(b)
+		frame.Write(b)
 		b.WriteByte('\n')
 	}
 }
@@ -235,7 +261,7 @@ func (e *Exception) writeFullStack(b *bytes.Buffer) {
 func (e *Exception) writeShortStack(b *bytes.Buffer) {
 	if len(e.stack) > 0 && (e.stack[0].prg != nil || e.stack[0].funcName != "") {
 		b.WriteString(" at ")
-		e.stack[0].write(b)
+		e.stack[0].Write(b)
 	}
 }
 
@@ -273,7 +299,7 @@ func (r *Runtime) addToGlobal(name string, value Value) {
 func (r *Runtime) createIterProto(val *Object) objectImpl {
 	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 
-	o.put(symIterator, valueProp(r.newNativeFunc(r.returnThis, nil, "[Symbol.iterator]", nil, 0), true, false, true), true)
+	o._putSym(symIterator, valueProp(r.newNativeFunc(r.returnThis, nil, "[Symbol.iterator]", nil, 0), true, false, true))
 	return o
 }
 
@@ -299,6 +325,8 @@ func (r *Runtime) init() {
 	r.initRegExp()
 	r.initDate()
 	r.initBoolean()
+	r.initProxy()
+	r.initReflect()
 
 	r.initErrors()
 
@@ -341,7 +369,7 @@ func (r *Runtime) throwReferenceError(name string) {
 }
 
 func (r *Runtime) newSyntaxError(msg string, offset int) Value {
-	return r.builtin_new((r.global.SyntaxError), []Value{newStringValue(msg)})
+	return r.builtin_new(r.global.SyntaxError, []Value{newStringValue(msg)})
 }
 
 func newBaseObjectObj(obj, proto *Object, class string) *baseObject {
@@ -402,7 +430,7 @@ func (r *Runtime) newFunc(name string, len int, strict bool) (f *funcObject) {
 	return
 }
 
-func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value) *Object, name string, proto *Object, length int) *nativeFuncObject {
+func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *nativeFuncObject {
 	f := &nativeFuncObject{
 		baseFuncObject: baseFuncObject{
 			baseObject: baseObject{
@@ -413,7 +441,7 @@ func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, con
 			},
 		},
 		f:         call,
-		construct: construct,
+		construct: r.wrapNativeConstruct(construct, proto),
 	}
 	v.self = f
 	f.init(name, length)
@@ -441,7 +469,7 @@ func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name
 		return f.defaultConstruct(call, c.Arguments)
 	}
 
-	f.construct = func(args []Value) *Object {
+	f.construct = func(args []Value, proto *Object) *Object {
 		return f.defaultConstruct(call, args)
 	}
 
@@ -455,7 +483,7 @@ func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name
 	return v
 }
 
-func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value) *Object, name string, proto *Object, length int) *Object {
+func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *Object {
 	v := &Object{runtime: r}
 
 	f := &nativeFuncObject{
@@ -468,7 +496,7 @@ func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(ar
 			},
 		},
 		f:         call,
-		construct: construct,
+		construct: r.wrapNativeConstruct(construct, proto),
 	}
 	v.self = f
 	f.init(name, length)
@@ -489,10 +517,8 @@ func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Val
 				prototype:  r.global.FunctionPrototype,
 			},
 		},
-		f: r.constructWrap(construct, proto),
-		construct: func(args []Value) *Object {
-			return construct(args, proto)
-		},
+		f:         r.constructToCall(construct, proto),
+		construct: r.wrapNativeConstruct(construct, proto),
 	}
 
 	f.init(name, length)
@@ -515,10 +541,8 @@ func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto
 	f.extensible = true
 	v.self = f
 	f.prototype = proto
-	f.f = r.constructWrap(construct, prototype)
-	f.construct = func(args []Value) *Object {
-		return construct(args, prototype)
-	}
+	f.f = r.constructToCall(construct, prototype)
+	f.construct = r.wrapNativeConstruct(construct, prototype)
 	f.init(name, length)
 	if prototype != nil {
 		f._putProp("prototype", prototype, false, false, false)
@@ -545,18 +569,18 @@ func (r *Runtime) builtin_Number(call FunctionCall) Value {
 	if len(call.Arguments) > 0 {
 		return call.Arguments[0].ToNumber()
 	} else {
-		return intToValue(0)
+		return valueInt(0)
 	}
 }
 
-func (r *Runtime) builtin_newNumber(args []Value) *Object {
+func (r *Runtime) builtin_newNumber(args []Value, proto *Object) *Object {
 	var v Value
 	if len(args) > 0 {
 		v = args[0].ToNumber()
 	} else {
 		v = intToValue(0)
 	}
-	return r.newPrimitiveObject(v, r.global.NumberPrototype, classNumber)
+	return r.newPrimitiveObject(v, proto, classNumber)
 }
 
 func (r *Runtime) builtin_Boolean(call FunctionCall) Value {
@@ -571,7 +595,7 @@ func (r *Runtime) builtin_Boolean(call FunctionCall) Value {
 	}
 }
 
-func (r *Runtime) builtin_newBoolean(args []Value) *Object {
+func (r *Runtime) builtin_newBoolean(args []Value, proto *Object) *Object {
 	var v Value
 	if len(args) > 0 {
 		if args[0].ToBoolean() {
@@ -582,13 +606,13 @@ func (r *Runtime) builtin_newBoolean(args []Value) *Object {
 	} else {
 		v = valueFalse
 	}
-	return r.newPrimitiveObject(v, r.global.BooleanPrototype, classBoolean)
+	return r.newPrimitiveObject(v, proto, classBoolean)
 }
 
 func (r *Runtime) error_toString(call FunctionCall) Value {
 	obj := call.This.ToObject(r).self
-	msg := obj.getStr("message")
-	name := obj.getStr("name")
+	msg := obj.getStr("message", nil)
+	name := obj.getStr("name", nil)
 	var nameStr, msgStr string
 	if name != nil && name != _undefined {
 		nameStr = name.String()
@@ -615,33 +639,8 @@ func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
 	return obj.val
 }
 
-func getConstructor(construct *Object) func(args []Value) *Object {
-repeat:
-	switch f := construct.self.(type) {
-	case *nativeFuncObject:
-		if f.construct != nil {
-			return f.construct
-		}
-	case *boundFuncObject:
-		if f.construct != nil {
-			return f.construct
-		}
-	case *funcObject:
-		return f.construct
-	case *lazyObject:
-		construct.self = f.create(construct)
-		goto repeat
-	}
-
-	return nil
-}
-
 func (r *Runtime) builtin_new(construct *Object, args []Value) *Object {
-	f := getConstructor(construct)
-	if f == nil {
-		panic("Not a constructor")
-	}
-	return f(args)
+	return r.toConstructor(construct)(args, nil)
 }
 
 func (r *Runtime) throw(e Value) {
@@ -687,18 +686,33 @@ func (r *Runtime) builtin_eval(call FunctionCall) Value {
 	if len(call.Arguments) == 0 {
 		return _undefined
 	}
-	if str, ok := call.Arguments[0].assertString(); ok {
+	if str, ok := call.Arguments[0].(valueString); ok {
 		return r.eval(str.String(), false, false, r.globalObject)
 	}
 	return call.Arguments[0]
 }
 
-func (r *Runtime) constructWrap(construct func(args []Value, proto *Object) *Object, proto *Object) func(call FunctionCall) Value {
+func (r *Runtime) constructToCall(construct func(args []Value, proto *Object) *Object, proto *Object) func(call FunctionCall) Value {
 	return func(call FunctionCall) Value {
 		return construct(call.Arguments, proto)
 	}
 }
 
+func (r *Runtime) wrapNativeConstruct(c func(args []Value, proto *Object) *Object, proto *Object) func(args []Value, newTarget *Object) *Object {
+	if c == nil {
+		return nil
+	}
+	return func(args []Value, newTarget *Object) *Object {
+		var p *Object
+		if newTarget != nil {
+			p = r.toObject(newTarget.self.getStr("prototype", nil))
+		} else {
+			p = proto
+		}
+		return c(args, p)
+	}
+}
+
 func (r *Runtime) toCallable(v Value) func(FunctionCall) Value {
 	if call, ok := r.toObject(v).self.assertCallable(); ok {
 		return call
@@ -716,11 +730,12 @@ func (r *Runtime) checkObjectCoercible(v Value) {
 
 func toUInt32(v Value) uint32 {
 	v = v.ToNumber()
-	if i, ok := v.assertInt(); ok {
+	if i, ok := v.(valueInt); ok {
 		return uint32(i)
 	}
 
-	if f, ok := v.assertFloat(); ok {
+	if f, ok := v.(valueFloat); ok {
+		f := float64(f)
 		if !math.IsNaN(f) && !math.IsInf(f, 0) {
 			return uint32(int64(f))
 		}
@@ -730,11 +745,12 @@ func toUInt32(v Value) uint32 {
 
 func toUInt16(v Value) uint16 {
 	v = v.ToNumber()
-	if i, ok := v.assertInt(); ok {
+	if i, ok := v.(valueInt); ok {
 		return uint16(i)
 	}
 
-	if f, ok := v.assertFloat(); ok {
+	if f, ok := v.(valueFloat); ok {
+		f := float64(f)
 		if !math.IsNaN(f) && !math.IsInf(f, 0) {
 			return uint16(int64(f))
 		}
@@ -758,11 +774,12 @@ func toLength(v Value) int64 {
 
 func toInt32(v Value) int32 {
 	v = v.ToNumber()
-	if i, ok := v.assertInt(); ok {
+	if i, ok := v.(valueInt); ok {
 		return int32(i)
 	}
 
-	if f, ok := v.assertFloat(); ok {
+	if f, ok := v.(valueFloat); ok {
+		f := float64(f)
 		if !math.IsNaN(f) && !math.IsInf(f, 0) {
 			return int32(int64(f))
 		}
@@ -928,6 +945,18 @@ func (r *Runtime) RunProgram(p *Program) (result Value, err error) {
 	return
 }
 
+func (r *Runtime) CaptureCallStack(n int) []StackFrame {
+	m := len(r.vm.callStack)
+	if n > 0 {
+		m -= m - n
+	} else {
+		m = 0
+	}
+	stackFrames := make([]StackFrame, 0)
+	stackFrames = r.vm.captureStack(stackFrames, m)
+	return stackFrames
+}
+
 // Interrupt a running JavaScript. The corresponding Go call will return an *InterruptedError containing v.
 // Note, it only works while in JavaScript code, it does not interrupt native Go functions (which includes all built-ins).
 // If the runtime is currently not running, it will be immediately interrupted on the next Run*() call.
@@ -975,8 +1004,15 @@ func (r *Runtime) ToValue(i interface{}) Value {
 	switch i := i.(type) {
 	case nil:
 		return _null
+	case *Object:
+		if i == nil || i.runtime == nil {
+			return _null
+		}
+		if i.runtime != r {
+			panic(r.NewTypeError("Illegal runtime transition of an Object"))
+		}
+		return i
 	case Value:
-		// TODO: prevent importing Objects from a different runtime
 		return i
 	case string:
 		return newStringValue(i)
@@ -987,9 +1023,20 @@ func (r *Runtime) ToValue(i interface{}) Value {
 			return valueFalse
 		}
 	case func(FunctionCall) Value:
-		return r.newNativeFunc(i, nil, "", nil, 0)
+		name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+		return r.newNativeFunc(i, nil, name, nil, 0)
 	case func(ConstructorCall) *Object:
-		return r.newNativeConstructor(i, "", 0)
+		name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+		return r.newNativeConstructor(i, name, 0)
+	case *Proxy:
+		if i == nil {
+			return _null
+		}
+		proxy := i.proxy.val
+		if proxy.runtime != r {
+			panic(r.NewTypeError("Illegal runtime transition of a Proxy"))
+		}
+		return proxy
 	case int:
 		return intToValue(int64(i))
 	case int8:
@@ -1116,7 +1163,8 @@ func (r *Runtime) ToValue(i interface{}) Value {
 		obj.self = a
 		return obj
 	case reflect.Func:
-		return r.newNativeFunc(r.wrapReflectFunc(value), nil, "", nil, value.Type().NumIn())
+		name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+		return r.newNativeFunc(r.wrapReflectFunc(value), nil, name, nil, value.Type().NumIn())
 	}
 
 	obj := &Object{runtime: r}
@@ -1236,34 +1284,34 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 	case reflect.Bool:
 		return reflect.ValueOf(v.ToBoolean()).Convert(typ), nil
 	case reflect.Int:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(int(i)).Convert(typ), nil
 	case reflect.Int64:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(i).Convert(typ), nil
 	case reflect.Int32:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(int32(i)).Convert(typ), nil
 	case reflect.Int16:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(int16(i)).Convert(typ), nil
 	case reflect.Int8:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(int8(i)).Convert(typ), nil
 	case reflect.Uint:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(uint(i)).Convert(typ), nil
 	case reflect.Uint64:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(uint64(i)).Convert(typ), nil
 	case reflect.Uint32:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(uint32(i)).Convert(typ), nil
 	case reflect.Uint16:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(uint16(i)).Convert(typ), nil
 	case reflect.Uint8:
-		i, _ := toInt(v)
+		i, _ := toInt64(v)
 		return reflect.ValueOf(uint8(i)).Convert(typ), nil
 	}
 
@@ -1273,12 +1321,18 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 		}
 	}
 
-	if typ.Implements(typeValue) {
+	if typeValue.AssignableTo(typ) {
 		return reflect.ValueOf(v), nil
 	}
 
+	if typeObject.AssignableTo(typ) {
+		if obj, ok := v.(*Object); ok {
+			return reflect.ValueOf(obj), nil
+		}
+	}
+
 	et := v.ExportType()
-	if et == nil {
+	if et == nil || et == reflectTypeNil {
 		return reflect.Zero(typ), nil
 	}
 	if et.AssignableTo(typ) {
@@ -1299,11 +1353,11 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 	case reflect.Slice:
 		if o, ok := v.(*Object); ok {
 			if o.self.className() == classArray {
-				l := int(toLength(o.self.getStr("length")))
+				l := int(toLength(o.self.getStr("length", nil)))
 				s := reflect.MakeSlice(typ, l, l)
 				elemTyp := typ.Elem()
 				for i := 0; i < l; i++ {
-					item := o.self.get(intToValue(int64(i)))
+					item := o.self.getIdx(valueInt(int64(i)), nil)
 					itemval, err := r.toReflectValue(item, elemTyp)
 					if err != nil {
 						return reflect.Value{}, fmt.Errorf("Could not convert array element %v to %v at %d: %s", v, typ, i, err)
@@ -1319,31 +1373,29 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 			keyTyp := typ.Key()
 			elemTyp := typ.Elem()
 			needConvertKeys := !reflect.ValueOf("").Type().AssignableTo(keyTyp)
-			for item, f := o.self.enumerate(false, false)(); f != nil; item, f = f() {
+			for _, itemName := range o.self.ownKeys(false, nil) {
 				var kv reflect.Value
 				var err error
 				if needConvertKeys {
-					kv, err = r.toReflectValue(newStringValue(item.name), keyTyp)
+					kv, err = r.toReflectValue(itemName, keyTyp)
 					if err != nil {
-						return reflect.Value{}, fmt.Errorf("Could not convert map key %s to %v", item.name, typ)
+						return reflect.Value{}, fmt.Errorf("Could not convert map key %s to %v", itemName.String(), typ)
 					}
 				} else {
-					kv = reflect.ValueOf(item.name)
+					kv = reflect.ValueOf(itemName.String())
 				}
 
-				ival := item.value
-				if ival == nil {
-					ival = o.self.getStr(item.name)
-				}
+				ival := o.get(itemName, nil)
 				if ival != nil {
 					vv, err := r.toReflectValue(ival, elemTyp)
 					if err != nil {
-						return reflect.Value{}, fmt.Errorf("Could not convert map value %v to %v at key %s", ival, typ, item.name)
+						return reflect.Value{}, fmt.Errorf("Could not convert map value %v to %v at key %s", ival, typ, itemName.String())
 					}
 					m.SetMapIndex(kv, vv)
 				} else {
 					m.SetMapIndex(kv, reflect.Zero(elemTyp))
 				}
+
 			}
 			return m, nil
 		}
@@ -1361,7 +1413,7 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 					if field.Anonymous {
 						v = o
 					} else {
-						v = o.self.getStr(name)
+						v = o.self.getStr(name, nil)
 					}
 
 					if v != nil {
@@ -1393,7 +1445,7 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 		return ptrVal, nil
 	}
 
-	return reflect.Value{}, fmt.Errorf("Could not convert %v to %v", v, typ)
+	return reflect.Value{}, fmt.Errorf("could not convert %v to %v", v, typ)
 }
 
 func (r *Runtime) wrapJSFunc(fn Callable, typ reflect.Type) func(args []reflect.Value) (results []reflect.Value) {
@@ -1452,12 +1504,12 @@ func (r *Runtime) GlobalObject() *Object {
 // Set the specified value as a property of the global object.
 // The value is first converted using ToValue()
 func (r *Runtime) Set(name string, value interface{}) {
-	r.globalObject.self.putStr(name, r.ToValue(value), false)
+	r.globalObject.self.setOwnStr(name, r.ToValue(value), false)
 }
 
 // Get the specified property of the global object.
 func (r *Runtime) Get(name string) Value {
-	return r.globalObject.self.getStr(name)
+	return r.globalObject.self.getStr(name, nil)
 }
 
 // SetRandSource sets random source for this Runtime. If not called, the default math/rand is used.
@@ -1535,8 +1587,8 @@ func IsNull(v Value) bool {
 
 // IsNaN returns true if the supplied value is NaN.
 func IsNaN(v Value) bool {
-	f, ok := v.assertFloat()
-	return ok && math.IsNaN(f)
+	f, ok := v.(valueFloat)
+	return ok && math.IsNaN(float64(f))
 }
 
 // IsInfinity returns true if the supplied is (+/-)Infinity
@@ -1609,23 +1661,23 @@ func (r *Runtime) toObject(v Value, args ...interface{}) *Object {
 	}
 }
 
-func (r *Runtime) speciesConstructor(o, defaultConstructor *Object) func(args []Value) *Object {
-	c := o.self.getStr("constructor")
+func (r *Runtime) speciesConstructor(o, defaultConstructor *Object) func(args []Value, newTarget *Object) *Object {
+	c := o.self.getStr("constructor", nil)
 	if c != nil && c != _undefined {
-		c = r.toObject(c).self.get(symSpecies)
+		c = r.toObject(c).self.getSym(symSpecies, nil)
 	}
 	if c == nil || c == _undefined {
 		c = defaultConstructor
 	}
-	return getConstructor(r.toObject(c))
+	return r.toConstructor(c)
 }
 
 func (r *Runtime) returnThis(call FunctionCall) Value {
 	return call.This
 }
 
-func defineDataPropertyOrThrow(o *Object, p Value, v Value) {
-	o.self.defineOwnProperty(p, propertyDescr{
+func createDataPropertyOrThrow(o *Object, p Value, v Value) {
+	o.defineOwnProperty(p, PropertyDescriptor{
 		Writable:     FLAG_TRUE,
 		Enumerable:   FLAG_TRUE,
 		Configurable: FLAG_TRUE,
@@ -1639,20 +1691,12 @@ func toPropertyKey(key Value) Value {
 
 func (r *Runtime) getVStr(v Value, p string) Value {
 	o := v.ToObject(r)
-	prop := o.self.getPropStr(p)
-	if prop, ok := prop.(*valueProperty); ok {
-		return prop.get(v)
-	}
-	return prop
+	return o.self.getStr(p, v)
 }
 
 func (r *Runtime) getV(v Value, p Value) Value {
 	o := v.ToObject(r)
-	prop := o.self.getProp(p)
-	if prop, ok := prop.(*valueProperty); ok {
-		return prop.get(v)
-	}
-	return prop
+	return o.get(p, v)
 }
 
 func (r *Runtime) getIterator(obj Value, method func(FunctionCall) Value) *Object {
@@ -1670,15 +1714,15 @@ func (r *Runtime) getIterator(obj Value, method func(FunctionCall) Value) *Objec
 
 func (r *Runtime) iterate(iter *Object, step func(Value)) {
 	for {
-		res := r.toObject(toMethod(iter.self.getStr("next"))(FunctionCall{This: iter}))
-		if nilSafe(res.self.getStr("done")).ToBoolean() {
+		res := r.toObject(toMethod(iter.self.getStr("next", nil))(FunctionCall{This: iter}))
+		if nilSafe(res.self.getStr("done", nil)).ToBoolean() {
 			break
 		}
 		err := tryFunc(func() {
-			step(nilSafe(res.self.getStr("value")))
+			step(nilSafe(res.self.getStr("value", nil)))
 		})
 		if err != nil {
-			retMethod := toMethod(iter.self.getStr("return"))
+			retMethod := toMethod(iter.self.getStr("return", nil))
 			if retMethod != nil {
 				_ = tryFunc(func() {
 					retMethod(FunctionCall{This: iter})
@@ -1691,8 +1735,8 @@ func (r *Runtime) iterate(iter *Object, step func(Value)) {
 
 func (r *Runtime) createIterResultObject(value Value, done bool) Value {
 	o := r.NewObject()
-	o.self.putStr("value", value, false)
-	o.self.putStr("done", r.toBoolean(done), false)
+	o.self.setOwnStr("value", value, false)
+	o.self.setOwnStr("done", r.toBoolean(done), false)
 	return o
 }
 
@@ -1718,3 +1762,19 @@ func nilSafe(v Value) Value {
 	}
 	return _undefined
 }
+
+func isArray(object *Object) bool {
+	self := object.self
+	if proxy, ok := self.(*proxyObject); ok {
+		if proxy.target == nil {
+			panic(typeError("Cannot perform 'IsArray' on a proxy that has been revoked"))
+		}
+		return isArray(proxy.target)
+	}
+	switch self.className() {
+	case classArray:
+		return true
+	default:
+		return false
+	}
+}

+ 69 - 0
runtime_test.go

@@ -1356,6 +1356,75 @@ func TestPrimThisValueGetter(t *testing.T) {
 	testScript1(TESTLIB+SCRIPT, _undefined, t)
 }
 
+func TestObjSetSym(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+	var sym = Symbol(true);
+	var p1 = Object.create(null);
+	var p2 = Object.create(p1);
+	
+	Object.defineProperty(p1, sym, {
+	value: 42
+	});
+	
+	Object.defineProperty(p2, sym, {
+	value: 43,
+	writable: true,
+	});
+	var o = Object.create(p2);
+	o[sym] = 44;
+	o[sym];
+	`
+	testScript1(SCRIPT, intToValue(44), t)
+}
+
+func TestObjSet(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+	var p1 = Object.create(null);
+	var p2 = Object.create(p1);
+	
+	Object.defineProperty(p1, "test", {
+	value: 42
+	});
+	
+	Object.defineProperty(p2, "test", {
+	value: 43,
+	writable: true,
+	});
+	var o = Object.create(p2);
+	o.test = 44;
+	o.test;
+	`
+	testScript1(SCRIPT, intToValue(44), t)
+}
+
+func TestToValueNilValue(t *testing.T) {
+	r := New()
+	var a Value
+	r.Set("a", a)
+	ret, err := r.RunString(`
+	""+a;
+	`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !asciiString("null").SameAs(ret) {
+		t.Fatalf("ret: %v", ret)
+	}
+}
+
+func TestNativeCtorNewTarget(t *testing.T) {
+	const SCRIPT = `
+	function NewTarget() {
+	}
+
+	var o = Reflect.construct(Number, [1], NewTarget);
+	o.__proto__ === NewTarget.prototype && o.toString() === "[object Number]";
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
 /*
 func TestArrayConcatSparse(t *testing.T) {
 function foo(a,b,c)

+ 84 - 60
string.go

@@ -27,7 +27,6 @@ var (
 	stringPlusInfinity             = asciiString("+Infinity")
 	stringNegInfinity              = asciiString("-Infinity")
 	stringEmpty        valueString = asciiString("")
-	string__proto__    valueString = asciiString(__proto__)
 
 	stringError          valueString = asciiString("Error")
 	stringTypeError      valueString = asciiString("TypeError")
@@ -93,37 +92,27 @@ func (s *stringObject) setLength() {
 	s._put("length", &s.lengthProp)
 }
 
-func (s *stringObject) get(n Value) Value {
-	if idx := toIdx(n); idx >= 0 && idx < s.length {
-		return s.getIdx(idx)
+func (s *stringObject) getStr(name string, receiver Value) Value {
+	if i := strToIdx64(name); i >= 0 && i < s.length {
+		return s._getIdx(i)
 	}
-	return s.baseObject.get(n)
+	return s.baseObject.getStr(name, receiver)
 }
 
-func (s *stringObject) getStr(name string) Value {
-	if i := strToIdx(name); i >= 0 && i < s.length {
-		return s.getIdx(i)
-	}
-	return s.baseObject.getStr(name)
-}
-
-func (s *stringObject) getPropStr(name string) Value {
-	if i := strToIdx(name); i >= 0 && i < s.length {
-		return s.getIdx(i)
-	}
-	return s.baseObject.getPropStr(name)
-}
-
-func (s *stringObject) getProp(n Value) Value {
-	if i := toIdx(n); i >= 0 && i < s.length {
-		return s.getIdx(i)
+func (s *stringObject) getIdx(idx valueInt, receiver Value) Value {
+	i := int64(idx)
+	if i >= 0 {
+		if i < s.length {
+			return s._getIdx(i)
+		}
+		return nil
 	}
-	return s.baseObject.getProp(n)
+	return s.baseObject.getStr(idx.String(), receiver)
 }
 
 func (s *stringObject) getOwnPropStr(name string) Value {
-	if i := strToIdx(name); i >= 0 && i < s.length {
-		val := s.getIdx(i)
+	if i := strToIdx64(name); i >= 0 && i < s.length {
+		val := s._getIdx(i)
 		return &valueProperty{
 			value:      val,
 			enumerable: true,
@@ -133,42 +122,76 @@ func (s *stringObject) getOwnPropStr(name string) Value {
 	return s.baseObject.getOwnPropStr(name)
 }
 
-func (s *stringObject) getIdx(idx int64) Value {
+func (s *stringObject) getOwnPropIdx(idx valueInt) Value {
+	i := int64(idx)
+	if i >= 0 {
+		if i < s.length {
+			val := s._getIdx(i)
+			return &valueProperty{
+				value:      val,
+				enumerable: true,
+			}
+		}
+		return nil
+	}
+
+	return s.baseObject.getOwnPropStr(idx.String())
+}
+
+func (s *stringObject) _getIdx(idx int64) Value {
 	return s.value.substring(idx, idx+1)
 }
 
-func (s *stringObject) put(n Value, val Value, throw bool) {
-	if i := toIdx(n); i >= 0 && i < s.length {
+func (s *stringObject) setOwnStr(name string, val Value, throw bool) bool {
+	if i := strToIdx64(name); i >= 0 && i < s.length {
 		s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
-		return
+		return false
 	}
 
-	s.baseObject.put(n, val, throw)
+	return s.baseObject.setOwnStr(name, val, throw)
 }
 
-func (s *stringObject) putStr(name string, val Value, throw bool) {
-	if i := strToIdx(name); i >= 0 && i < s.length {
+func (s *stringObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
+	i := int64(idx)
+	if i >= 0 && i < s.length {
 		s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
-		return
+		return false
 	}
 
-	s.baseObject.putStr(name, val, throw)
+	return s.baseObject.setOwnStr(idx.String(), val, throw)
+}
+
+func (s *stringObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) {
+	return s._setForeignStr(name, s.getOwnPropStr(name), val, receiver, throw)
 }
 
-func (s *stringObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
-	if i := toIdx(n); i >= 0 && i < s.length {
+func (s *stringObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
+	return s._setForeignIdx(idx, s.getOwnPropIdx(idx), val, receiver, throw)
+}
+
+func (s *stringObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool {
+	if i := strToIdx64(name); i >= 0 && i < s.length {
+		s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
+		return false
+	}
+
+	return s.baseObject.defineOwnPropertyStr(name, descr, throw)
+}
+
+func (s *stringObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
+	i := int64(idx)
+	if i >= 0 && i < s.length {
 		s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
 		return false
 	}
 
-	return s.baseObject.defineOwnProperty(n, descr, throw)
+	return s.baseObject.defineOwnPropertyStr(idx.String(), descr, throw)
 }
 
 type stringPropIter struct {
 	str         valueString // separate, because obj can be the singleton
 	obj         *stringObject
 	idx, length int64
-	recursive   bool
 }
 
 func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
@@ -178,28 +201,27 @@ func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
 		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
 	}
 
-	return i.obj.baseObject._enumerate(i.recursive)()
+	return i.obj.baseObject.enumerateUnfiltered()()
 }
 
-func (s *stringObject) _enumerate(recursive bool) iterNextFunc {
+func (s *stringObject) enumerateUnfiltered() iterNextFunc {
 	return (&stringPropIter{
-		str:       s.value,
-		obj:       s,
-		length:    s.length,
-		recursive: recursive,
+		str:    s.value,
+		obj:    s,
+		length: s.length,
 	}).next
 }
 
-func (s *stringObject) enumerate(all, recursive bool) iterNextFunc {
-	return (&propFilterIter{
-		wrapped: s._enumerate(recursive),
-		all:     all,
-		seen:    make(map[string]bool),
-	}).next
+func (s *stringObject) ownKeys(all bool, accum []Value) []Value {
+	for i := int64(0); i < s.length; i++ {
+		accum = append(accum, asciiString(strconv.FormatInt(i, 10)))
+	}
+
+	return s.baseObject.ownKeys(all, accum)
 }
 
 func (s *stringObject) deleteStr(name string, throw bool) bool {
-	if i := strToIdx(name); i >= 0 && i < s.length {
+	if i := strToIdx64(name); i >= 0 && i < s.length {
 		s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
 		return false
 	}
@@ -207,25 +229,27 @@ func (s *stringObject) deleteStr(name string, throw bool) bool {
 	return s.baseObject.deleteStr(name, throw)
 }
 
-func (s *stringObject) delete(n Value, throw bool) bool {
-	if i := toIdx(n); i >= 0 && i < s.length {
+func (s *stringObject) deleteIdx(idx valueInt, throw bool) bool {
+	i := int64(idx)
+	if i >= 0 && i < s.length {
 		s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
 		return false
 	}
 
-	return s.baseObject.delete(n, throw)
+	return s.baseObject.deleteStr(idx.String(), throw)
 }
 
-func (s *stringObject) hasOwnProperty(n Value) bool {
-	if i := toIdx(n); i >= 0 && i < s.length {
+func (s *stringObject) hasOwnPropertyStr(name string) bool {
+	if i := strToIdx64(name); i >= 0 && i < s.length {
 		return true
 	}
-	return s.baseObject.hasOwnProperty(n)
+	return s.baseObject.hasOwnPropertyStr(name)
 }
 
-func (s *stringObject) hasOwnPropertyStr(name string) bool {
-	if i := strToIdx(name); i >= 0 && i < s.length {
+func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool {
+	i := int64(idx)
+	if i >= 0 && i < s.length {
 		return true
 	}
-	return s.baseObject.hasOwnPropertyStr(name)
+	return s.baseObject.hasOwnPropertyStr(idx.String())
 }

+ 5 - 17
string_ascii.go

@@ -164,7 +164,7 @@ func (s asciiString) ToNumber() Value {
 }
 
 func (s asciiString) ToObject(r *Runtime) *Object {
-	return r._newString(s)
+	return r._newString(s, r.global.StringPrototype)
 }
 
 func (s asciiString) SameAs(other Value) bool {
@@ -179,15 +179,15 @@ func (s asciiString) Equals(other Value) bool {
 		return s == o
 	}
 
-	if o, ok := other.assertInt(); ok {
+	if o, ok := other.(valueInt); ok {
 		if o1, e := s._toInt(); e == nil {
-			return o1 == o
+			return o1 == int64(o)
 		}
 		return false
 	}
 
-	if o, ok := other.assertFloat(); ok {
-		return s.ToFloat() == o
+	if o, ok := other.(valueFloat); ok {
+		return s.ToFloat() == float64(o)
 	}
 
 	if o, ok := other.(valueBool); ok {
@@ -210,18 +210,6 @@ func (s asciiString) StrictEquals(other Value) bool {
 	return false
 }
 
-func (s asciiString) assertInt() (int64, bool) {
-	return 0, false
-}
-
-func (s asciiString) assertFloat() (float64, bool) {
-	return 0, false
-}
-
-func (s asciiString) assertString() (valueString, bool) {
-	return s, true
-}
-
 func (s asciiString) baseObject(r *Runtime) *Object {
 	ss := r.stringSingleton
 	ss.value = s

+ 1 - 25
string_unicode.go

@@ -109,7 +109,7 @@ func (s unicodeString) ToNumber() Value {
 }
 
 func (s unicodeString) ToObject(r *Runtime) *Object {
-	return r._newString(s)
+	return r._newString(s, r.global.StringPrototype)
 }
 
 func (s unicodeString) equals(other unicodeString) bool {
@@ -137,18 +137,6 @@ func (s unicodeString) Equals(other Value) bool {
 		return true
 	}
 
-	if _, ok := other.assertInt(); ok {
-		return false
-	}
-
-	if _, ok := other.assertFloat(); ok {
-		return false
-	}
-
-	if _, ok := other.(valueBool); ok {
-		return false
-	}
-
 	if o, ok := other.(*Object); ok {
 		return s.Equals(o.self.toPrimitive())
 	}
@@ -159,18 +147,6 @@ func (s unicodeString) StrictEquals(other Value) bool {
 	return s.SameAs(other)
 }
 
-func (s unicodeString) assertInt() (int64, bool) {
-	return 0, false
-}
-
-func (s unicodeString) assertFloat() (float64, bool) {
-	return 0, false
-}
-
-func (s unicodeString) assertString() (valueString, bool) {
-	return s, true
-}
-
 func (s unicodeString) baseObject(r *Runtime) *Object {
 	ss := r.stringSingleton
 	ss.value = s

+ 62 - 32
tc39_test.go

@@ -26,7 +26,6 @@ var (
 		"test/language/literals/regexp/S7.8.5_A1.4_T2.js":             true, // UTF-16
 		"test/language/literals/regexp/S7.8.5_A2.1_T2.js":             true, // UTF-16
 		"test/language/literals/regexp/S7.8.5_A2.4_T2.js":             true, // UTF-16
-		"test/built-ins/Object/getOwnPropertyNames/15.2.3.4-4-44.js":  true, // property order
 		"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
@@ -36,36 +35,62 @@ var (
 		"test/built-ins/Array/prototype/concat/Array.prototype.concat_spreadable-string-wrapper.js": true,
 
 		// cross-realm
-		"test/built-ins/Symbol/unscopables/cross-realm.js":                                true,
-		"test/built-ins/Symbol/toStringTag/cross-realm.js":                                true,
-		"test/built-ins/Symbol/toPrimitive/cross-realm.js":                                true,
-		"test/built-ins/Symbol/split/cross-realm.js":                                      true,
-		"test/built-ins/Symbol/species/cross-realm.js":                                    true,
-		"test/built-ins/Symbol/search/cross-realm.js":                                     true,
-		"test/built-ins/Symbol/replace/cross-realm.js":                                    true,
-		"test/built-ins/Symbol/match/cross-realm.js":                                      true,
-		"test/built-ins/Symbol/keyFor/cross-realm.js":                                     true,
-		"test/built-ins/Symbol/iterator/cross-realm.js":                                   true,
-		"test/built-ins/Symbol/isConcatSpreadable/cross-realm.js":                         true,
-		"test/built-ins/Symbol/hasInstance/cross-realm.js":                                true,
-		"test/built-ins/Symbol/for/cross-realm.js":                                        true,
-		"test/built-ins/WeakSet/proto-from-ctor-realm.js":                                 true,
-		"test/built-ins/WeakMap/proto-from-ctor-realm.js":                                 true,
-		"test/built-ins/Map/proto-from-ctor-realm.js":                                     true,
-		"test/built-ins/Set/proto-from-ctor-realm.js":                                     true,
-		"test/built-ins/Object/proto-from-ctor.js":                                        true,
-		"test/built-ins/Array/from/proto-from-ctor-realm.js":                              true,
-		"test/built-ins/Array/of/proto-from-ctor-realm.js":                                true,
-		"test/built-ins/Array/prototype/concat/create-proto-from-ctor-realm-non-array.js": true,
-		"test/built-ins/Array/prototype/concat/create-proto-from-ctor-realm-array.js":     true,
-		"test/built-ins/Array/prototype/filter/create-proto-from-ctor-realm-non-array.js": true,
-		"test/built-ins/Array/prototype/filter/create-proto-from-ctor-realm-array.js":     true,
-		"test/built-ins/Array/prototype/map/create-proto-from-ctor-realm-non-array.js":    true,
-		"test/built-ins/Array/prototype/map/create-proto-from-ctor-realm-array.js":        true,
-		"test/built-ins/Array/prototype/slice/create-proto-from-ctor-realm-non-array.js":  true,
-		"test/built-ins/Array/prototype/slice/create-proto-from-ctor-realm-array.js":      true,
-		"test/built-ins/Array/prototype/splice/create-proto-from-ctor-realm-non-array.js": true,
-		"test/built-ins/Array/prototype/splice/create-proto-from-ctor-realm-array.js":     true,
+		"test/built-ins/Symbol/unscopables/cross-realm.js":                                                          true,
+		"test/built-ins/Symbol/toStringTag/cross-realm.js":                                                          true,
+		"test/built-ins/Symbol/toPrimitive/cross-realm.js":                                                          true,
+		"test/built-ins/Symbol/split/cross-realm.js":                                                                true,
+		"test/built-ins/Symbol/species/cross-realm.js":                                                              true,
+		"test/built-ins/Symbol/search/cross-realm.js":                                                               true,
+		"test/built-ins/Symbol/replace/cross-realm.js":                                                              true,
+		"test/built-ins/Symbol/match/cross-realm.js":                                                                true,
+		"test/built-ins/Symbol/keyFor/cross-realm.js":                                                               true,
+		"test/built-ins/Symbol/iterator/cross-realm.js":                                                             true,
+		"test/built-ins/Symbol/isConcatSpreadable/cross-realm.js":                                                   true,
+		"test/built-ins/Symbol/hasInstance/cross-realm.js":                                                          true,
+		"test/built-ins/Symbol/for/cross-realm.js":                                                                  true,
+		"test/built-ins/WeakSet/proto-from-ctor-realm.js":                                                           true,
+		"test/built-ins/WeakMap/proto-from-ctor-realm.js":                                                           true,
+		"test/built-ins/Map/proto-from-ctor-realm.js":                                                               true,
+		"test/built-ins/Set/proto-from-ctor-realm.js":                                                               true,
+		"test/built-ins/Object/proto-from-ctor.js":                                                                  true,
+		"test/built-ins/Array/from/proto-from-ctor-realm.js":                                                        true,
+		"test/built-ins/Array/of/proto-from-ctor-realm.js":                                                          true,
+		"test/built-ins/Array/prototype/concat/create-proto-from-ctor-realm-non-array.js":                           true,
+		"test/built-ins/Array/prototype/concat/create-proto-from-ctor-realm-array.js":                               true,
+		"test/built-ins/Array/prototype/filter/create-proto-from-ctor-realm-non-array.js":                           true,
+		"test/built-ins/Array/prototype/filter/create-proto-from-ctor-realm-array.js":                               true,
+		"test/built-ins/Array/prototype/map/create-proto-from-ctor-realm-non-array.js":                              true,
+		"test/built-ins/Array/prototype/map/create-proto-from-ctor-realm-array.js":                                  true,
+		"test/built-ins/Array/prototype/slice/create-proto-from-ctor-realm-non-array.js":                            true,
+		"test/built-ins/Array/prototype/slice/create-proto-from-ctor-realm-array.js":                                true,
+		"test/built-ins/Array/prototype/splice/create-proto-from-ctor-realm-non-array.js":                           true,
+		"test/built-ins/Array/prototype/splice/create-proto-from-ctor-realm-array.js":                               true,
+		"test/built-ins/Proxy/construct/arguments-realm.js":                                                         true,
+		"test/built-ins/Proxy/setPrototypeOf/trap-is-not-callable-realm.js":                                         true,
+		"test/built-ins/Proxy/getPrototypeOf/trap-is-not-callable-realm.js":                                         true,
+		"test/built-ins/Proxy/set/trap-is-not-callable-realm.js":                                                    true,
+		"test/built-ins/Proxy/getOwnPropertyDescriptor/trap-is-not-callable-realm.js":                               true,
+		"test/built-ins/Proxy/getOwnPropertyDescriptor/result-type-is-not-object-nor-undefined-realm.js":            true,
+		"test/built-ins/Proxy/get/trap-is-not-callable-realm.js":                                                    true,
+		"test/built-ins/Proxy/preventExtensions/trap-is-not-callable-realm.js":                                      true,
+		"test/built-ins/Proxy/defineProperty/null-handler-realm.js":                                                 true,
+		"test/built-ins/Proxy/ownKeys/trap-is-not-callable-realm.js":                                                true,
+		"test/built-ins/Proxy/ownKeys/return-not-list-object-throws-realm.js":                                       true,
+		"test/built-ins/Proxy/deleteProperty/trap-is-not-callable-realm.js":                                         true,
+		"test/built-ins/Proxy/isExtensible/trap-is-not-callable-realm.js":                                           true,
+		"test/built-ins/Proxy/defineProperty/trap-is-not-callable-realm.js":                                         true,
+		"test/built-ins/Proxy/defineProperty/targetdesc-undefined-target-is-not-extensible-realm.js":                true,
+		"test/built-ins/Proxy/defineProperty/targetdesc-undefined-not-configurable-descriptor-realm.js":             true,
+		"test/built-ins/Proxy/defineProperty/targetdesc-not-compatible-descriptor.js":                               true,
+		"test/built-ins/Proxy/defineProperty/targetdesc-not-compatible-descriptor-realm.js":                         true,
+		"test/built-ins/Proxy/defineProperty/targetdesc-not-compatible-descriptor-not-configurable-target-realm.js": true,
+		"test/built-ins/Proxy/defineProperty/targetdesc-configurable-desc-not-configurable-realm.js":                true,
+		"test/built-ins/Proxy/has/trap-is-not-callable-realm.js":                                                    true,
+		"test/built-ins/Proxy/defineProperty/desc-realm.js":                                                         true,
+		"test/built-ins/Proxy/apply/trap-is-not-callable-realm.js":                                                  true,
+		"test/built-ins/Proxy/apply/arguments-realm.js":                                                             true,
+		"test/built-ins/Proxy/construct/trap-is-undefined-proto-from-ctor-realm.js":                                 true,
+		"test/built-ins/Proxy/construct/trap-is-not-callable-realm.js":                                              true,
 
 		// class
 		"test/language/statements/class/subclass/builtin-objects/Symbol/symbol-valid-as-extends-value.js": true,
@@ -102,16 +127,19 @@ var (
 		"test/language/statements/for-of/Array.prototype.keys.js":            true,
 		"test/language/statements/for-of/Array.prototype.entries.js":         true,
 		"test/language/statements/for-of/Array.prototype.Symbol.iterator.js": true,
+
+		// arrow-function
+		"test/built-ins/Object/prototype/toString/proxy-function.js": true,
 	}
 
 	featuresBlackList = []string{
-		"Proxy",
 		"arrow-function",
 	}
 
 	es6WhiteList = map[string]bool{}
 
 	es6IdWhiteList = []string{
+		"9.5",
 		"12.9.3",
 		"12.9.4",
 		"19.1",
@@ -130,6 +158,8 @@ var (
 		"23.3",
 		"23.4",
 		"25.1.2",
+		"26.1",
+		"26.2",
 		"B.2.1",
 		"B.2.2",
 	}

+ 50 - 166
value.go

@@ -17,7 +17,7 @@ var (
 	_NaN          Value = valueFloat(math.NaN())
 	_positiveInf  Value = valueFloat(math.Inf(+1))
 	_negativeInf  Value = valueFloat(math.Inf(-1))
-	_positiveZero Value
+	_positiveZero Value = valueInt(0)
 	_negativeZero Value = valueFloat(math.Float64frombits(0 | (1 << 63)))
 	_epsilon            = valueFloat(2.2204460492503130808472633361816e-16)
 	_undefined    Value = valueUndefined{}
@@ -33,12 +33,12 @@ var (
 	reflectTypeString = reflect.TypeOf("")
 )
 
-var intCache [256]Value
-
 var (
 	mapHasher maphash.Hash
 )
 
+var intCache [256]Value
+
 type Value interface {
 	ToInteger() int64
 	toString() valueString
@@ -54,10 +54,6 @@ type Value interface {
 	Export() interface{}
 	ExportType() reflect.Type
 
-	assertInt() (int64, bool)
-	assertString() (valueString, bool)
-	assertFloat() (float64, bool)
-
 	baseObject(r *Runtime) *Object
 
 	hash() uint64
@@ -154,50 +150,35 @@ func (i valueInt) ToNumber() Value {
 }
 
 func (i valueInt) SameAs(other Value) bool {
-	if otherInt, ok := other.assertInt(); ok {
-		return int64(i) == otherInt
-	}
-	return false
+	return i == other
 }
 
 func (i valueInt) Equals(other Value) bool {
-	if o, ok := other.assertInt(); ok {
-		return int64(i) == o
-	}
-	if o, ok := other.assertFloat(); ok {
-		return float64(i) == o
-	}
-	if o, ok := other.assertString(); ok {
+	switch o := other.(type) {
+	case valueInt:
+		return i == o
+	case valueFloat:
+		return float64(i) == float64(o)
+	case valueString:
 		return o.ToNumber().Equals(i)
-	}
-	if o, ok := other.(valueBool); ok {
+	case valueBool:
 		return int64(i) == o.ToInteger()
-	}
-	if o, ok := other.(*Object); ok {
+	case *Object:
 		return i.Equals(o.self.toPrimitiveNumber())
 	}
+
 	return false
 }
 
 func (i valueInt) StrictEquals(other Value) bool {
-	if otherInt, ok := other.assertInt(); ok {
-		return int64(i) == otherInt
-	} else if otherFloat, ok := other.assertFloat(); ok {
-		return float64(i) == otherFloat
+	switch o := other.(type) {
+	case valueInt:
+		return i == o
+	case valueFloat:
+		return float64(i) == float64(o)
 	}
-	return false
-}
-
-func (i valueInt) assertInt() (int64, bool) {
-	return int64(i), true
-}
-
-func (i valueInt) assertFloat() (float64, bool) {
-	return 0, false
-}
 
-func (i valueInt) assertString() (valueString, bool) {
-	return nil, false
+	return false
 }
 
 func (i valueInt) baseObject(r *Runtime) *Object {
@@ -290,18 +271,6 @@ func (o valueBool) StrictEquals(other Value) bool {
 	return false
 }
 
-func (o valueBool) assertInt() (int64, bool) {
-	return 0, false
-}
-
-func (o valueBool) assertFloat() (float64, bool) {
-	return 0, false
-}
-
-func (o valueBool) assertString() (valueString, bool) {
-	return nil, false
-}
-
 func (o valueBool) baseObject(r *Runtime) *Object {
 	return r.global.BooleanPrototype
 }
@@ -407,18 +376,6 @@ func (n valueNull) StrictEquals(other Value) bool {
 	return same
 }
 
-func (n valueNull) assertInt() (int64, bool) {
-	return 0, false
-}
-
-func (n valueNull) assertFloat() (float64, bool) {
-	return 0, false
-}
-
-func (n valueNull) assertString() (valueString, bool) {
-	return nil, false
-}
-
 func (n valueNull) baseObject(*Runtime) *Object {
 	return nil
 }
@@ -467,18 +424,6 @@ func (p *valueProperty) ToNumber() Value {
 	return nil
 }
 
-func (p *valueProperty) assertInt() (int64, bool) {
-	return 0, false
-}
-
-func (p *valueProperty) assertFloat() (float64, bool) {
-	return 0, false
-}
-
-func (p *valueProperty) assertString() (valueString, bool) {
-	return nil, false
-}
-
 func (p *valueProperty) isWritable() bool {
 	return p.writable || p.setterFunc != nil
 }
@@ -598,18 +543,20 @@ func (f valueFloat) ToNumber() Value {
 }
 
 func (f valueFloat) SameAs(other Value) bool {
-	if o, ok := other.assertFloat(); ok {
+	switch o := other.(type) {
+	case valueFloat:
 		this := float64(f)
-		if math.IsNaN(this) && math.IsNaN(o) {
+		o1 := float64(o)
+		if math.IsNaN(this) && math.IsNaN(o1) {
 			return true
 		} else {
-			ret := this == o
+			ret := this == o1
 			if ret && this == 0 {
-				ret = math.Signbit(this) == math.Signbit(o)
+				ret = math.Signbit(this) == math.Signbit(o1)
 			}
 			return ret
 		}
-	} else if o, ok := other.assertInt(); ok {
+	case valueInt:
 		this := float64(f)
 		ret := this == float64(o)
 		if ret && this == 0 {
@@ -617,27 +564,19 @@ func (f valueFloat) SameAs(other Value) bool {
 		}
 		return ret
 	}
+
 	return false
 }
 
 func (f valueFloat) Equals(other Value) bool {
-	if o, ok := other.assertFloat(); ok {
-		return float64(f) == o
-	}
-
-	if o, ok := other.assertInt(); ok {
+	switch o := other.(type) {
+	case valueFloat:
+		return f == o
+	case valueInt:
 		return float64(f) == float64(o)
-	}
-
-	if _, ok := other.assertString(); ok {
-		return float64(f) == other.ToFloat()
-	}
-
-	if o, ok := other.(valueBool); ok {
+	case valueString, valueBool:
 		return float64(f) == o.ToFloat()
-	}
-
-	if o, ok := other.(*Object); ok {
+	case *Object:
 		return f.Equals(o.self.toPrimitiveNumber())
 	}
 
@@ -645,24 +584,14 @@ func (f valueFloat) Equals(other Value) bool {
 }
 
 func (f valueFloat) StrictEquals(other Value) bool {
-	if o, ok := other.assertFloat(); ok {
-		return float64(f) == o
-	} else if o, ok := other.assertInt(); ok {
+	switch o := other.(type) {
+	case valueFloat:
+		return f == o
+	case valueInt:
 		return float64(f) == float64(o)
 	}
-	return false
-}
 
-func (f valueFloat) assertInt() (int64, bool) {
-	return 0, false
-}
-
-func (f valueFloat) assertFloat() (float64, bool) {
-	return float64(f), true
-}
-
-func (f valueFloat) assertString() (valueString, bool) {
-	return nil, false
+	return false
 }
 
 func (f valueFloat) baseObject(r *Runtime) *Object {
@@ -728,21 +657,13 @@ func (o *Object) Equals(other Value) bool {
 		return o == other || o.self.equal(other.self)
 	}
 
-	if _, ok := other.assertInt(); ok {
-		return o.self.toPrimitive().Equals(other)
-	}
-
-	if _, ok := other.assertFloat(); ok {
+	switch o1 := other.(type) {
+	case valueInt, valueFloat, valueString:
 		return o.self.toPrimitive().Equals(other)
+	case valueBool:
+		return o.Equals(o1.ToNumber())
 	}
 
-	if other, ok := other.(valueBool); ok {
-		return o.Equals(other.ToNumber())
-	}
-
-	if _, ok := other.assertString(); ok {
-		return o.self.toPrimitive().Equals(other)
-	}
 	return false
 }
 
@@ -753,18 +674,6 @@ func (o *Object) StrictEquals(other Value) bool {
 	return false
 }
 
-func (o *Object) assertInt() (int64, bool) {
-	return 0, false
-}
-
-func (o *Object) assertFloat() (float64, bool) {
-	return 0, false
-}
-
-func (o *Object) assertString() (valueString, bool) {
-	return nil, false
-}
-
 func (o *Object) baseObject(*Runtime) *Object {
 	return o
 }
@@ -782,12 +691,14 @@ func (o *Object) hash() uint64 {
 }
 
 func (o *Object) Get(name string) Value {
-	return o.self.getStr(name)
+	return o.self.getStr(name, nil)
 }
 
 func (o *Object) Keys() (keys []string) {
-	for item, f := o.self.enumerate(false, false)(); f != nil; item, f = f() {
-		keys = append(keys, item.name)
+	names := o.self.ownKeys(false, nil)
+	keys = make([]string, 0, len(names))
+	for _, name := range names {
+		keys = append(keys, name.String())
 	}
 
 	return
@@ -797,7 +708,7 @@ func (o *Object) Keys() (keys []string) {
 // configurable: configurable, enumerable: enumerable})
 func (o *Object) DefineDataProperty(name string, value Value, writable, configurable, enumerable Flag) error {
 	return tryFunc(func() {
-		o.self.defineOwnProperty(newStringValue(name), propertyDescr{
+		o.self.defineOwnPropertyStr(name, PropertyDescriptor{
 			Value:        value,
 			Writable:     writable,
 			Configurable: configurable,
@@ -810,7 +721,7 @@ func (o *Object) DefineDataProperty(name string, value Value, writable, configur
 // configurable: configurable, enumerable: enumerable})
 func (o *Object) DefineAccessorProperty(name string, getter, setter Value, configurable, enumerable Flag) error {
 	return tryFunc(func() {
-		o.self.defineOwnProperty(newStringValue(name), propertyDescr{
+		o.self.defineOwnPropertyStr(name, PropertyDescriptor{
 			Getter:       getter,
 			Setter:       setter,
 			Configurable: configurable,
@@ -821,7 +732,7 @@ func (o *Object) DefineAccessorProperty(name string, getter, setter Value, confi
 
 func (o *Object) Set(name string, value interface{}) error {
 	return tryFunc(func() {
-		o.self.putStr(name, o.runtime.ToValue(value), true)
+		o.self.setOwnStr(name, o.runtime.ToValue(value), true)
 	})
 }
 
@@ -906,21 +817,6 @@ func (o valueUnresolved) StrictEquals(Value) bool {
 	return false
 }
 
-func (o valueUnresolved) assertInt() (int64, bool) {
-	o.throw()
-	return 0, false
-}
-
-func (o valueUnresolved) assertFloat() (float64, bool) {
-	o.throw()
-	return 0, false
-}
-
-func (o valueUnresolved) assertString() (valueString, bool) {
-	o.throw()
-	return nil, false
-}
-
 func (o valueUnresolved) baseObject(*Runtime) *Object {
 	o.throw()
 	return nil
@@ -996,18 +892,6 @@ func (s *valueSymbol) ExportType() reflect.Type {
 	return reflectTypeString
 }
 
-func (s *valueSymbol) assertInt() (int64, bool) {
-	return 0, false
-}
-
-func (s *valueSymbol) assertString() (valueString, bool) {
-	return nil, false
-}
-
-func (s *valueSymbol) assertFloat() (float64, bool) {
-	return 0, false
-}
-
 func (s *valueSymbol) baseObject(r *Runtime) *Object {
 	return r.newPrimitiveObject(s, r.global.SymbolPrototype, "Symbol")
 }

+ 114 - 110
vm.go

@@ -25,11 +25,12 @@ type stash struct {
 }
 
 type context struct {
-	prg      *Program
-	funcName string
-	stash    *stash
-	pc, sb   int
-	args     int
+	prg       *Program
+	funcName  string
+	stash     *stash
+	newTarget Value
+	pc, sb    int
+	args      int
 }
 
 type iterStackItem struct {
@@ -67,11 +68,11 @@ type objRef struct {
 }
 
 func (r *objRef) get() Value {
-	return r.base.getStr(r.name)
+	return r.base.getStr(r.name, nil)
 }
 
 func (r *objRef) set(v Value) {
-	r.base.putStr(r.name, v, r.strict)
+	r.base.setOwnStr(r.name, v, r.strict)
 }
 
 func (r *objRef) refname() string {
@@ -108,6 +109,7 @@ type vm struct {
 	callStack []context
 	iterStack []iterStackItem
 	refStack  []ref
+	newTarget Value
 
 	stashAllocs int
 	halt        bool
@@ -155,13 +157,13 @@ func floatToValue(f float64) (result Value) {
 	return valueFloat(f)
 }
 
-func toInt(v Value) (int64, bool) {
+func toInt64(v Value) (int64, bool) {
 	num := v.ToNumber()
-	if i, ok := num.assertInt(); ok {
-		return i, true
+	if i, ok := num.(valueInt); ok {
+		return int64(i), true
 	}
-	if f, ok := num.assertFloat(); ok {
-		if i, ok := floatToInt(f); ok {
+	if f, ok := num.(valueFloat); ok {
+		if i, ok := floatToInt(float64(f)); ok {
 			return i, true
 		}
 	}
@@ -170,14 +172,14 @@ func toInt(v Value) (int64, bool) {
 
 func toIntIgnoreNegZero(v Value) (int64, bool) {
 	num := v.ToNumber()
-	if i, ok := num.assertInt(); ok {
-		return i, true
+	if i, ok := num.(valueInt); ok {
+		return int64(i), true
 	}
-	if f, ok := num.assertFloat(); ok {
+	if f, ok := num.(valueFloat); ok {
 		if v == _negativeZero {
 			return 0, true
 		}
-		if i, ok := floatToInt(f); ok {
+		if i, ok := floatToInt(float64(f)); ok {
 			return i, true
 		}
 	}
@@ -200,8 +202,8 @@ func (s *valueStack) expand(idx int) {
 
 func (s *stash) put(name string, v Value) bool {
 	if s.obj != nil {
-		if found := s.obj.getStr(name); found != nil {
-			s.obj.putStr(name, v, false)
+		if found := s.obj.getStr(name, nil); found != nil {
+			s.obj.setOwnStr(name, v, false)
 			return true
 		}
 		return false
@@ -232,12 +234,10 @@ func (s *stash) getByIdx(idx uint32) Value {
 
 func (s *stash) getByName(name string, _ *vm) (v Value, exists bool) {
 	if s.obj != nil {
-		v = s.obj.getStr(name)
-		if v == nil {
-			return nil, false
-			//return valueUnresolved{r: vm.r, ref: name}, false
+		if s.obj.hasPropertyStr(name) {
+			return nilSafe(s.obj.getStr(name, nil)), true
 		}
-		return v, true
+		return nil, false
 	}
 	if idx, exists := s.names[name]; exists {
 		return s.values[idx], true
@@ -317,12 +317,12 @@ func (vm *vm) ClearInterrupt() {
 	atomic.StoreUint32(&vm.interrupted, 0)
 }
 
-func (vm *vm) captureStack(stack []stackFrame, ctxOffset int) []stackFrame {
+func (vm *vm) captureStack(stack []StackFrame, ctxOffset int) []StackFrame {
 	// Unroll the context stack
-	stack = append(stack, stackFrame{prg: vm.prg, pc: vm.pc, funcName: vm.funcName})
+	stack = append(stack, StackFrame{prg: vm.prg, pc: vm.pc, funcName: vm.funcName})
 	for i := len(vm.callStack) - 1; i > ctxOffset-1; i-- {
 		if vm.callStack[i].pc != -1 {
-			stack = append(stack, stackFrame{prg: vm.callStack[i].prg, pc: vm.callStack[i].pc - 1, funcName: vm.callStack[i].funcName})
+			stack = append(stack, StackFrame{prg: vm.callStack[i].prg, pc: vm.callStack[i].pc - 1, funcName: vm.callStack[i].funcName})
 		}
 	}
 	return stack
@@ -409,8 +409,13 @@ func (vm *vm) peek() Value {
 
 func (vm *vm) saveCtx(ctx *context) {
 	ctx.prg = vm.prg
-	ctx.funcName = vm.funcName
+	if vm.funcName != "" {
+		ctx.funcName = vm.funcName
+	} else if ctx.prg != nil && ctx.prg.funcName != "" {
+		ctx.funcName = ctx.prg.funcName
+	}
 	ctx.stash = vm.stash
+	ctx.newTarget = vm.newTarget
 	ctx.pc = vm.pc
 	ctx.sb = vm.sb
 	ctx.args = vm.args
@@ -436,6 +441,7 @@ func (vm *vm) restoreCtx(ctx *context) {
 	vm.stash = ctx.stash
 	vm.sb = ctx.sb
 	vm.args = ctx.args
+	vm.newTarget = ctx.newTarget
 }
 
 func (vm *vm) popCtx() {
@@ -595,8 +601,8 @@ func (_add) exec(vm *vm) {
 
 	var ret Value
 
-	leftString, isLeftString := left.assertString()
-	rightString, isRightString := right.assertString()
+	leftString, isLeftString := left.(valueString)
+	rightString, isRightString := right.(valueString)
 
 	if isLeftString || isRightString {
 		if !isLeftString {
@@ -607,8 +613,8 @@ func (_add) exec(vm *vm) {
 		}
 		ret = leftString.concat(rightString)
 	} else {
-		if leftInt, ok := left.assertInt(); ok {
-			if rightInt, ok := right.assertInt(); ok {
+		if leftInt, ok := left.(valueInt); ok {
+			if rightInt, ok := right.(valueInt); ok {
 				ret = intToValue(int64(leftInt) + int64(rightInt))
 			} else {
 				ret = floatToValue(float64(leftInt) + right.ToFloat())
@@ -633,9 +639,9 @@ func (_sub) exec(vm *vm) {
 
 	var result Value
 
-	if left, ok := left.assertInt(); ok {
-		if right, ok := right.assertInt(); ok {
-			result = intToValue(left - right)
+	if left, ok := left.(valueInt); ok {
+		if right, ok := right.(valueInt); ok {
+			result = intToValue(int64(left) - int64(right))
 			goto end
 		}
 	}
@@ -657,8 +663,8 @@ func (_mul) exec(vm *vm) {
 
 	var result Value
 
-	if left, ok := toInt(left); ok {
-		if right, ok := toInt(right); ok {
+	if left, ok := toInt64(left); ok {
+		if right, ok := toInt64(right); ok {
 			if left == 0 && right == -1 || left == -1 && right == 0 {
 				result = _negativeZero
 				goto end
@@ -750,8 +756,8 @@ func (_mod) exec(vm *vm) {
 
 	var result Value
 
-	if leftInt, ok := toInt(left); ok {
-		if rightInt, ok := toInt(right); ok {
+	if leftInt, ok := toInt64(left); ok {
+		if rightInt, ok := toInt64(right); ok {
 			if rightInt == 0 {
 				result = _NaN
 				goto end
@@ -782,7 +788,7 @@ func (_neg) exec(vm *vm) {
 
 	var result Value
 
-	if i, ok := toInt(operand); ok {
+	if i, ok := toInt64(operand); ok {
 		if i == 0 {
 			result = _negativeZero
 		} else {
@@ -816,7 +822,7 @@ var inc _inc
 func (_inc) exec(vm *vm) {
 	v := vm.stack[vm.sp-1]
 
-	if i, ok := toInt(v); ok {
+	if i, ok := toInt64(v); ok {
 		v = intToValue(i + 1)
 		goto end
 	}
@@ -835,7 +841,7 @@ var dec _dec
 func (_dec) exec(vm *vm) {
 	v := vm.stack[vm.sp-1]
 
-	if i, ok := toInt(v); ok {
+	if i, ok := toInt64(v); ok {
 		v = intToValue(i - 1)
 		goto end
 	}
@@ -953,7 +959,7 @@ func (_setElem) exec(vm *vm) {
 	propName := toPropertyKey(vm.stack[vm.sp-2])
 	val := vm.stack[vm.sp-1]
 
-	obj.self.put(propName, val, false)
+	obj.setOwn(propName, val, false)
 
 	vm.sp -= 2
 	vm.stack[vm.sp-1] = val
@@ -969,7 +975,7 @@ func (_setElemStrict) exec(vm *vm) {
 	propName := toPropertyKey(vm.stack[vm.sp-2])
 	val := vm.stack[vm.sp-1]
 
-	obj.self.put(propName, val, true)
+	obj.setOwn(propName, val, true)
 
 	vm.sp -= 2
 	vm.stack[vm.sp-1] = val
@@ -983,7 +989,7 @@ var deleteElem _deleteElem
 func (_deleteElem) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-2])
 	propName := toPropertyKey(vm.stack[vm.sp-1])
-	if !obj.self.hasProperty(propName) || obj.self.delete(propName, false) {
+	if obj.delete(propName, false) {
 		vm.stack[vm.sp-2] = valueTrue
 	} else {
 		vm.stack[vm.sp-2] = valueFalse
@@ -999,7 +1005,7 @@ var deleteElemStrict _deleteElemStrict
 func (_deleteElemStrict) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-2])
 	propName := toPropertyKey(vm.stack[vm.sp-1])
-	obj.self.delete(propName, true)
+	obj.delete(propName, true)
 	vm.stack[vm.sp-2] = valueTrue
 	vm.sp--
 	vm.pc++
@@ -1009,7 +1015,7 @@ type deleteProp string
 
 func (d deleteProp) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-1])
-	if !obj.self.hasPropertyStr(string(d)) || obj.self.deleteStr(string(d), false) {
+	if obj.self.deleteStr(string(d), false) {
 		vm.stack[vm.sp-1] = valueTrue
 	} else {
 		vm.stack[vm.sp-1] = valueFalse
@@ -1030,7 +1036,7 @@ type setProp string
 
 func (p setProp) exec(vm *vm) {
 	val := vm.stack[vm.sp-1]
-	vm.stack[vm.sp-2].ToObject(vm.r).self.putStr(string(p), val, false)
+	vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(string(p), val, false)
 	vm.stack[vm.sp-2] = val
 	vm.sp--
 	vm.pc++
@@ -1043,7 +1049,7 @@ func (p setPropStrict) exec(vm *vm) {
 	val := vm.stack[vm.sp-1]
 
 	obj1 := vm.r.toObject(obj)
-	obj1.self.putStr(string(p), val, true)
+	obj1.self.setOwnStr(string(p), val, true)
 	vm.stack[vm.sp-2] = val
 	vm.sp--
 	vm.pc++
@@ -1063,7 +1069,7 @@ type _setProto struct{}
 var setProto _setProto
 
 func (_setProto) exec(vm *vm) {
-	vm.r.toObject(vm.stack[vm.sp-2]).self.putStr(__proto__, vm.stack[vm.sp-1], true)
+	vm.r.toObject(vm.stack[vm.sp-2]).self.setProto(vm.r.toProto(vm.stack[vm.sp-1]), true)
 
 	vm.sp--
 	vm.pc++
@@ -1075,13 +1081,13 @@ func (s setPropGetter) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-2])
 	val := vm.stack[vm.sp-1]
 
-	descr := propertyDescr{
+	descr := PropertyDescriptor{
 		Getter:       val,
 		Configurable: FLAG_TRUE,
 		Enumerable:   FLAG_TRUE,
 	}
 
-	obj.self.defineOwnProperty(newStringValue(string(s)), descr, false)
+	obj.self.defineOwnPropertyStr(string(s), descr, false)
 
 	vm.sp--
 	vm.pc++
@@ -1093,13 +1099,13 @@ func (s setPropSetter) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-2])
 	val := vm.stack[vm.sp-1]
 
-	descr := propertyDescr{
+	descr := PropertyDescriptor{
 		Setter:       val,
 		Configurable: FLAG_TRUE,
 		Enumerable:   FLAG_TRUE,
 	}
 
-	obj.self.defineOwnProperty(newStringValue(string(s)), descr, false)
+	obj.self.defineOwnPropertyStr(string(s), descr, false)
 
 	vm.sp--
 	vm.pc++
@@ -1113,15 +1119,7 @@ func (g getProp) exec(vm *vm) {
 	if obj == nil {
 		panic(vm.r.NewTypeError("Cannot read property '%s' of undefined", g))
 	}
-	prop := obj.self.getPropStr(string(g))
-	if prop1, ok := prop.(*valueProperty); ok {
-		vm.stack[vm.sp-1] = prop1.get(v)
-	} else {
-		if prop == nil {
-			prop = _undefined
-		}
-		vm.stack[vm.sp-1] = prop
-	}
+	vm.stack[vm.sp-1] = nilSafe(obj.self.getStr(string(g), v))
 
 	vm.pc++
 }
@@ -1132,17 +1130,13 @@ func (g getPropCallee) exec(vm *vm) {
 	v := vm.stack[vm.sp-1]
 	obj := v.baseObject(vm.r)
 	if obj == nil {
-		panic(vm.r.NewTypeError("Cannot read property '%s' of undefined", g))
+		panic(vm.r.NewTypeError("Cannot read property '%s' of undefined or null", g))
 	}
-	prop := obj.self.getPropStr(string(g))
-	if prop1, ok := prop.(*valueProperty); ok {
-		vm.stack[vm.sp-1] = prop1.get(v)
-	} else {
-		if prop == nil {
-			prop = memberUnresolved{valueUnresolved{r: vm.r, ref: string(g)}}
-		}
-		vm.stack[vm.sp-1] = prop
+	prop := obj.self.getStr(string(g), v)
+	if prop == nil {
+		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: string(g)}}
 	}
+	vm.stack[vm.sp-1] = prop
 
 	vm.pc++
 }
@@ -1159,15 +1153,7 @@ func (_getElem) exec(vm *vm) {
 		panic(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String()))
 	}
 
-	prop := obj.self.getProp(propName)
-	if prop1, ok := prop.(*valueProperty); ok {
-		vm.stack[vm.sp-2] = prop1.get(v)
-	} else {
-		if prop == nil {
-			prop = _undefined
-		}
-		vm.stack[vm.sp-2] = prop
-	}
+	vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v))
 
 	vm.sp--
 	vm.pc++
@@ -1185,15 +1171,11 @@ func (_getElemCallee) exec(vm *vm) {
 		panic(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String()))
 	}
 
-	prop := obj.self.getProp(propName)
-	if prop1, ok := prop.(*valueProperty); ok {
-		vm.stack[vm.sp-2] = prop1.get(v)
-	} else {
-		if prop == nil {
-			prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.String()}}
-		}
-		vm.stack[vm.sp-2] = prop
+	prop := obj.get(propName, v)
+	if prop == nil {
+		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.String()}}
 	}
+	vm.stack[vm.sp-2] = prop
 
 	vm.sp--
 	vm.pc++
@@ -1249,7 +1231,7 @@ func (l newArray) exec(vm *vm) {
 }
 
 type newArraySparse struct {
-	l, objCount uint32
+	l, objCount int
 }
 
 func (n *newArraySparse) exec(vm *vm) {
@@ -1257,7 +1239,7 @@ func (n *newArraySparse) exec(vm *vm) {
 	copy(values, vm.stack[vm.sp-int(n.l):vm.sp])
 	arr := vm.r.newArrayObject()
 	setArrayValues(arr, values)
-	arr.objCount = int64(n.objCount)
+	arr.objCount = n.objCount
 	vm.sp -= int(n.l) - 1
 	vm.stack[vm.sp-1] = arr.val
 	vm.pc++
@@ -1309,7 +1291,7 @@ func (s setVar) exec(vm *vm) {
 	v := vm.peek()
 
 	level := int(s.idx >> 24)
-	idx := uint32(s.idx & 0x00FFFFFF)
+	idx := s.idx & 0x00FFFFFF
 	stash := vm.stash
 	name := s.name
 	for i := 0; i < level; i++ {
@@ -1322,7 +1304,7 @@ func (s setVar) exec(vm *vm) {
 	if stash != nil {
 		stash.putByIdx(idx, v)
 	} else {
-		vm.r.globalObject.self.putStr(name, v, false)
+		vm.r.globalObject.self.setOwnStr(name, v, false)
 	}
 
 end:
@@ -1462,7 +1444,7 @@ type setGlobal string
 func (s setGlobal) exec(vm *vm) {
 	v := vm.peek()
 
-	vm.r.globalObject.self.putStr(string(s), v, false)
+	vm.r.globalObject.self.setOwnStr(string(s), v, false)
 	vm.pc++
 }
 
@@ -1474,7 +1456,7 @@ func (s setGlobalStrict) exec(vm *vm) {
 	name := string(s)
 	o := vm.r.globalObject.self
 	if o.hasOwnPropertyStr(name) {
-		o.putStr(name, v, true)
+		o.setOwnStr(name, v, true)
 	} else {
 		vm.r.throwReferenceError(name)
 	}
@@ -1516,7 +1498,7 @@ func (g getVar) exec(vm *vm) {
 	if stash != nil {
 		vm.push(stash.getByIdx(idx))
 	} else {
-		v := vm.r.globalObject.self.getStr(name)
+		v := vm.r.globalObject.self.getStr(name, nil)
 		if v == nil {
 			if g.ref {
 				v = valueUnresolved{r: vm.r, ref: name}
@@ -1538,7 +1520,7 @@ type resolveVar struct {
 
 func (r resolveVar) exec(vm *vm) {
 	level := int(r.idx >> 24)
-	idx := uint32(r.idx & 0x00FFFFFF)
+	idx := r.idx & 0x00FFFFFF
 	stash := vm.stash
 	var ref ref
 	for i := 0; i < level; i++ {
@@ -1627,7 +1609,7 @@ func (n getVar1) exec(vm *vm) {
 		}
 	}
 	if val == nil {
-		val = vm.r.globalObject.self.getStr(name)
+		val = vm.r.globalObject.self.getStr(name, nil)
 		if val == nil {
 			vm.r.throwReferenceError(name)
 		}
@@ -1648,7 +1630,7 @@ func (n getVar1Callee) exec(vm *vm) {
 		}
 	}
 	if val == nil {
-		val = vm.r.globalObject.self.getStr(name)
+		val = vm.r.globalObject.self.getStr(name, nil)
 		if val == nil {
 			val = valueUnresolved{r: vm.r, ref: name}
 		}
@@ -1670,7 +1652,7 @@ func (vm *vm) callEval(n int, strict bool) {
 	if vm.r.toObject(vm.stack[vm.sp-n-1]) == vm.r.global.Eval {
 		if n > 0 {
 			srcVal := vm.stack[vm.sp-n]
-			if src, ok := srcVal.assertString(); ok {
+			if src, ok := srcVal.(valueString); ok {
 				var this Value
 				if vm.sb != 0 {
 					this = vm.stack[vm.sb]
@@ -1745,6 +1727,18 @@ repeat:
 		vm._nativeCall(f, n)
 	case *boundFuncObject:
 		vm._nativeCall(&f.nativeFuncObject, n)
+	case *proxyObject:
+		vm.pushCtx()
+		vm.prg = nil
+		vm.funcName = "proxy"
+		ret := f.apply(FunctionCall{This: vm.stack[vm.sp-n-2], Arguments: vm.stack[vm.sp-n : vm.sp]})
+		if ret == nil {
+			ret = _undefined
+		}
+		vm.stack[vm.sp-n-2] = ret
+		vm.popCtx()
+		vm.sp -= n + 1
+		vm.pc++
 	case *lazyObject:
 		obj.self = f.create(obj)
 		goto repeat
@@ -1971,15 +1965,15 @@ func cmp(px, py Value) Value {
 	var ret bool
 	var nx, ny float64
 
-	if xs, ok := px.assertString(); ok {
-		if ys, ok := py.assertString(); ok {
+	if xs, ok := px.(valueString); ok {
+		if ys, ok := py.(valueString); ok {
 			ret = xs.compareTo(ys) < 0
 			goto end
 		}
 	}
 
-	if xi, ok := px.assertInt(); ok {
-		if yi, ok := py.assertInt(); ok {
+	if xi, ok := px.(valueInt); ok {
+		if yi, ok := py.(valueInt); ok {
 			ret = xi < yi
 			goto end
 		}
@@ -2158,7 +2152,7 @@ func (_op_in) exec(vm *vm) {
 	left := vm.stack[vm.sp-2]
 	right := vm.r.toObject(vm.stack[vm.sp-1])
 
-	if right.self.hasProperty(left) {
+	if right.hasProperty(left) {
 		vm.stack[vm.sp-2] = valueTrue
 	} else {
 		vm.stack[vm.sp-2] = valueFalse
@@ -2243,12 +2237,22 @@ type _new uint32
 
 func (n _new) exec(vm *vm) {
 	sp := vm.sp - int(n)
-	obj := vm.r.toObject(vm.stack[sp-1])
-	if ctor := getConstructor(obj); ctor != nil {
-		vm.stack[sp-1] = ctor(vm.stack[sp:vm.sp])
-		vm.sp = sp
+	obj := vm.stack[sp-1]
+	ctor := vm.r.toConstructor(obj)
+	vm.stack[sp-1] = ctor(vm.stack[sp:vm.sp], nil)
+	vm.sp = sp
+	vm.pc++
+}
+
+type _loadNewTarget struct{}
+
+var loadNewTarget _loadNewTarget
+
+func (_loadNewTarget) exec(vm *vm) {
+	if t := vm.newTarget; t != nil {
+		vm.push(t)
 	} else {
-		panic(vm.r.NewTypeError("Not a constructor"))
+		vm.push(_undefined)
 	}
 	vm.pc++
 }
@@ -2387,7 +2391,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(false, true)})
+		vm.iterStack = append(vm.iterStack, iterStackItem{f: v.ToObject(vm.r).self.enumerate()})
 	}
 	vm.sp--
 	vm.pc++

+ 11 - 45
vm_test.go

@@ -33,7 +33,7 @@ func TestVM1(t *testing.T) {
 	rv := vm.pop()
 
 	if obj, ok := rv.(*Object); ok {
-		if v := obj.self.getStr("test").ToInteger(); v != 5 {
+		if v := obj.self.getStr("test", nil).ToInteger(); v != 5 {
 			t.Fatalf("Unexpected property value: %v", v)
 		}
 	} else {
@@ -72,48 +72,6 @@ func f_loadVal(vm *vm, i *instr) {
 	vm.pc++
 }
 
-func f_add(vm *vm) {
-	right := vm.stack[vm.sp-1]
-	left := vm.stack[vm.sp-2]
-
-	if o, ok := left.(*Object); ok {
-		left = o.self.toPrimitive()
-	}
-
-	if o, ok := right.(*Object); ok {
-		right = o.self.toPrimitive()
-	}
-
-	var ret Value
-
-	leftString, isLeftString := left.assertString()
-	rightString, isRightString := right.assertString()
-
-	if isLeftString || isRightString {
-		if !isLeftString {
-			leftString = left.toString()
-		}
-		if !isRightString {
-			rightString = right.toString()
-		}
-		ret = leftString.concat(rightString)
-	} else {
-		if leftInt, ok := left.assertInt(); ok {
-			if rightInt, ok := right.assertInt(); ok {
-				ret = intToValue(int64(leftInt) + int64(rightInt))
-			} else {
-				ret = floatToValue(float64(leftInt) + right.ToFloat())
-			}
-		} else {
-			ret = floatToValue(left.ToFloat() + right.ToFloat())
-		}
-	}
-
-	vm.stack[vm.sp-2] = ret
-	vm.sp--
-	vm.pc++
-}
-
 type instr struct {
 	code int
 	prim int
@@ -221,8 +179,6 @@ func BenchmarkVmNOP1(b *testing.B) {
 				break L
 			case 2:
 				f_loadVal(vm, instr)
-			case 3:
-				f_add(vm)
 			default:
 				jumptable[instr.code](vm, instr)
 			}
@@ -388,3 +344,13 @@ func BenchmarkFuncCall(b *testing.B) {
 		b.Fatal("f is not a function")
 	}
 }
+
+func BenchmarkAssertInt(b *testing.B) {
+	var v Value
+	v = intToValue(42)
+	for i := 0; i < b.N; i++ {
+		if i, ok := v.(valueInt); !ok || int64(i) != 42 {
+			b.Fatal()
+		}
+	}
+}

Some files were not shown because too many files changed in this diff