From a13c43b62b610fc21fc8928aada8be257962d63b Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Tue, 24 Mar 2020 15:09:28 +0000 Subject: [PATCH] Proxy (#135) Proxy, Reflect, new.target, tons of refactoring. Co-authored-by: noctarius --- array.go | 515 ++++++++++-------- array_sparse.go | 349 ++++++------ array_sparse_test.go | 83 ++- ast/node.go | 10 + builtin_array.go | 350 ++++++------ builtin_date.go | 28 +- builtin_error.go | 1 - builtin_function.go | 77 +-- builtin_json.go | 78 +-- builtin_map.go | 30 +- builtin_math.go | 6 +- builtin_number.go | 16 +- builtin_object.go | 296 +++++----- builtin_proxy.go | 281 ++++++++++ builtin_proxy_test.go | 828 ++++++++++++++++++++++++++++ builtin_reflect.go | 132 +++++ builtin_regexp.go | 96 ++-- builtin_set.go | 18 +- builtin_string.go | 25 +- builtin_symbol.go | 4 +- builtin_weakmap.go | 20 +- builtin_weakset.go | 20 +- builtin_weakset_test.go | 6 +- compiler_expr.go | 37 +- compiler_test.go | 2 +- date.go | 4 +- func.go | 114 ++-- object.go | 948 +++++++++++++++++++++++++-------- object_args.go | 79 ++- object_gomap.go | 156 ++---- object_gomap_reflect.go | 195 ++++--- object_gomap_reflect_test.go | 87 ++- object_gomap_test.go | 124 +++++ object_goreflect.go | 178 +++---- object_goreflect_test.go | 117 +++- object_goslice.go | 305 ++++++----- object_goslice_reflect.go | 308 ++++++----- object_goslice_reflect_test.go | 148 ++++- object_goslice_test.go | 103 +++- object_lazy.go | 158 ++++-- object_test.go | 62 ++- parser/expression.go | 17 + proxy.go | 764 ++++++++++++++++++++++++++ regexp.go | 10 +- runtime.go | 296 ++++++---- runtime_test.go | 69 +++ string.go | 144 ++--- string_ascii.go | 22 +- string_unicode.go | 26 +- tc39_test.go | 94 ++-- value.go | 216 ++------ vm.go | 224 ++++---- vm_test.go | 56 +- 53 files changed, 5857 insertions(+), 2475 deletions(-) create mode 100644 builtin_proxy.go create mode 100644 builtin_proxy_test.go create mode 100644 builtin_reflect.go create mode 100644 proxy.go diff --git a/array.go b/array.go index 53f777e9..5d795ba2 100644 --- a/array.go +++ b/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) } diff --git a/array_sparse.go b/array_sparse.go index a454ba58..69456b6d 100644 --- a/array_sparse.go +++ b/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 { diff --git a/array_sparse_test.go b/array_sparse_test.go index fe92305d..28791342 100644 --- a/array_sparse_test.go +++ b/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) } diff --git a/ast/node.go b/ast/node.go index 6c4ac7d0..33d49900 100644 --- a/ast/node.go +++ b/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 } diff --git a/builtin_array.go b/builtin_array.go index 13e13bae..9b521700 100644 --- a/builtin_array.go +++ b/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 } diff --git a/builtin_date.go b/builtin_date.go index a0ae93e8..fe976a1b 100644 --- a/builtin_date.go +++ b/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, diff --git a/builtin_error.go b/builtin_error.go index 5e95b426..0209931f 100644 --- a/builtin_error.go +++ b/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{}) diff --git a/builtin_function.go b/builtin_function.go index 78fd7252..f5b75f87 100644 --- a/builtin_function.go +++ b/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 diff --git a/builtin_json.go b/builtin_json.go index bbcb18e1..dea31878 100644 --- a/builtin_json.go +++ b/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 } diff --git a/builtin_map.go b/builtin_map.go index e0c437e9..d32fae65 100644 --- a/builtin_map.go +++ b/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 } diff --git a/builtin_math.go b/builtin_math.go index f6ec2162..c275e94f 100644 --- a/builtin_math.go +++ b/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) } diff --git a/builtin_number.go b/builtin_number.go index a1516398..2154e481 100644 --- a/builtin_number.go +++ b/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 { diff --git a/builtin_object.go b/builtin_object.go index 1ae0abdc..bd679026 100644 --- a/builtin_object.go +++ b/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 diff --git a/builtin_proxy.go b/builtin_proxy.go new file mode 100644 index 00000000..faae41cc --- /dev/null +++ b/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) +} diff --git a/builtin_proxy_test.go b/builtin_proxy_test.go new file mode 100644 index 00000000..fc0a50f6 --- /dev/null +++ b/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) +} diff --git a/builtin_reflect.go b/builtin_reflect.go new file mode 100644 index 00000000..d8e78471 --- /dev/null +++ b/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)) +} diff --git a/builtin_regexp.go b/builtin_regexp.go index 42749635..76cbc732 100644 --- a/builtin_regexp.go +++ b/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) } diff --git a/builtin_set.go b/builtin_set.go index e4ffbbf3..bbd70770 100644 --- a/builtin_set.go +++ b/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 } diff --git a/builtin_string.go b/builtin_string.go index 305470f8..4a6113ff 100644 --- a/builtin_string.go +++ b/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) diff --git a/builtin_symbol.go b/builtin_symbol.go index 599078d1..83346e14 100644 --- a/builtin_symbol.go +++ b/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 } diff --git a/builtin_weakmap.go b/builtin_weakmap.go index be0f36e9..a9debccf 100644 --- a/builtin_weakmap.go +++ b/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 } diff --git a/builtin_weakset.go b/builtin_weakset.go index d73697c9..e3aa4243 100644 --- a/builtin_weakset.go +++ b/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 } diff --git a/builtin_weakset_test.go b/builtin_weakset_test.go index 0194a981..3b01fa3f 100644 --- a/builtin_weakset_test.go +++ b/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") } diff --git a/compiler_expr.go b/compiler_expr.go index 6bc6da54..581208e8 100644 --- a/compiler_expr.go +++ b/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, }) } diff --git a/compiler_test.go b/compiler_test.go index 8920b809..341681c7 100644 --- a/compiler_test.go +++ b/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 } diff --git a/date.go b/date.go index 67aaf029..a22deb1a 100644 --- a/date.go +++ b/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) diff --git a/func.go b/func.go index 6b8e4cd9..138afc65 100644 --- a/func.go +++ b/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 { diff --git a/object.go b/object.go index 73de00f0..620ddec8 100644 --- a/object.go +++ b/object.go @@ -2,8 +2,10 @@ package goja import ( "fmt" + "math" "reflect" "runtime" + "sort" "unsafe" ) @@ -93,7 +95,9 @@ type Object struct { type iterNextFunc func() (propIterItem, iterNextFunc) -type propertyDescr struct { +type PropertyDescriptor struct { + jsDescriptor *Object + Value Value Writable, Configurable, Enumerable Flag @@ -101,40 +105,125 @@ type propertyDescr struct { Getter, Setter Value } +func (p *PropertyDescriptor) Empty() bool { + var empty PropertyDescriptor + return *p == empty +} + +func (p *PropertyDescriptor) toValue(r *Runtime) Value { + if p.jsDescriptor != nil { + return p.jsDescriptor + } + + o := r.NewObject() + s := o.self + + if p.Value != nil { + s._putProp("value", p.Value, true, true, true) + } + + if p.Writable != FLAG_NOT_SET { + s._putProp("writable", valueBool(p.Writable.Bool()), true, true, true) + } + + if p.Enumerable != FLAG_NOT_SET { + s._putProp("enumerable", valueBool(p.Enumerable.Bool()), true, true, true) + } + + if p.Configurable != FLAG_NOT_SET { + s._putProp("configurable", valueBool(p.Configurable.Bool()), true, true, true) + } + + if p.Getter != nil { + s._putProp("get", p.Getter, true, true, true) + } + if p.Setter != nil { + s._putProp("set", p.Setter, true, true, true) + } + + return o +} + +func (p *PropertyDescriptor) complete() { + if p.Getter == nil && p.Setter == nil { + if p.Value == nil { + p.Value = _undefined + } + if p.Writable == FLAG_NOT_SET { + p.Writable = FLAG_FALSE + } + } else { + if p.Getter == nil { + p.Getter = _undefined + } + if p.Setter == nil { + p.Setter = _undefined + } + } + if p.Enumerable == FLAG_NOT_SET { + p.Enumerable = FLAG_FALSE + } + if p.Configurable == FLAG_NOT_SET { + p.Configurable = FLAG_FALSE + } +} + type objectImpl interface { sortable className() string - get(Value) Value - getProp(Value) Value - getPropStr(string) Value - getStr(string) Value - getOwnProp(Value) Value + getStr(p string, receiver Value) Value + getIdx(p valueInt, receiver Value) Value + getSym(p *valueSymbol, receiver Value) Value + getOwnPropStr(string) Value - put(Value, Value, bool) - putStr(string, Value, bool) - hasProperty(Value) bool + getOwnPropIdx(valueInt) Value + getOwnPropSym(*valueSymbol) Value + + setOwnStr(p string, v Value, throw bool) bool + setOwnIdx(p valueInt, v Value, throw bool) bool + setOwnSym(p *valueSymbol, v Value, throw bool) bool + + setForeignStr(p string, v, receiver Value, throw bool) (res bool, handled bool) + setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) + setForeignSym(p *valueSymbol, v, receiver Value, throw bool) (res bool, handled bool) + hasPropertyStr(string) bool - hasOwnProperty(Value) bool + hasPropertyIdx(idx valueInt) bool + hasPropertySym(s *valueSymbol) bool + hasOwnPropertyStr(string) bool - _putProp(name string, value Value, writable, enumerable, configurable bool) Value - defineOwnProperty(name Value, descr propertyDescr, throw bool) bool + hasOwnPropertyIdx(valueInt) bool + hasOwnPropertySym(s *valueSymbol) bool + + defineOwnPropertyStr(name string, desc PropertyDescriptor, throw bool) bool + defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool + defineOwnPropertySym(name *valueSymbol, desc PropertyDescriptor, throw bool) bool + + deleteStr(name string, throw bool) bool + deleteIdx(idx valueInt, throw bool) bool + deleteSym(s *valueSymbol, throw bool) bool + toPrimitiveNumber() Value toPrimitiveString() Value toPrimitive() Value assertCallable() (call func(FunctionCall) Value, ok bool) - deleteStr(name string, throw bool) bool - delete(name Value, throw bool) bool + assertConstructor() func(args []Value, newTarget *Object) *Object proto() *Object - setProto(proto *Object) *Object + setProto(proto *Object, throw bool) bool hasInstance(v Value) bool isExtensible() bool - preventExtensions() - enumerate(all, recursive bool) iterNextFunc - _enumerate(recursive bool) iterNextFunc + preventExtensions(throw bool) bool + enumerate() iterNextFunc + enumerateUnfiltered() iterNextFunc export() interface{} exportType() reflect.Type equal(objectImpl) bool - getOwnSymbols() []Value + ownKeys(all bool, accum []Value) []Value + ownSymbols() []Value + ownPropertyKeys(all bool, accum []Value) []Value + + _putProp(name string, value Value, writable, enumerable, configurable bool) Value + _putSym(s *valueSymbol, prop Value) } type baseObject struct { @@ -146,7 +235,9 @@ type baseObject struct { values map[string]Value propNames []string - symValues map[*valueSymbol]Value + lastSortedPropLen, idxPropCount int + + symValues *orderedMap } type primitiveValueObject struct { @@ -194,78 +285,102 @@ func (o *baseObject) className() string { return o.class } -func (o *baseObject) getPropStr(name string) Value { - if val := o.getOwnPropStr(name); val != nil { - return val +func (o *baseObject) hasPropertyStr(name string) bool { + if o.val.self.hasOwnPropertyStr(name) { + return true } if o.prototype != nil { - return o.prototype.self.getPropStr(name) + return o.prototype.self.hasPropertyStr(name) } - return nil + return false +} + +func (o *baseObject) hasPropertyIdx(idx valueInt) bool { + return o.val.self.hasPropertyStr(idx.String()) } -func (o *baseObject) getPropSym(s *valueSymbol) Value { - if val := o.symValues[s]; val != nil { - return val +func (o *baseObject) hasPropertySym(s *valueSymbol) bool { + if o.hasOwnPropertySym(s) { + return true } if o.prototype != nil { - return o.prototype.self.getProp(s) + return o.prototype.self.hasPropertySym(s) } - return nil + return false } -func (o *baseObject) getProp(n Value) Value { - if s, ok := n.(*valueSymbol); ok { - return o.getPropSym(s) +func (o *baseObject) getWithOwnProp(prop, p, receiver Value) Value { + if prop == nil && o.prototype != nil { + if receiver == nil { + return o.prototype.get(p, o.val) + } + return o.prototype.get(p, receiver) } - return o.val.self.getPropStr(n.String()) + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(o.val) + } + return prop.get(receiver) + } + return prop } -func (o *baseObject) hasProperty(n Value) bool { - return o.val.self.getProp(n) != nil +func (o *baseObject) getStrWithOwnProp(prop Value, name string, receiver Value) Value { + if prop == nil && o.prototype != nil { + if receiver == nil { + return o.prototype.self.getStr(name, o.val) + } + return o.prototype.self.getStr(name, receiver) + } + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(o.val) + } + return prop.get(receiver) + } + return prop } -func (o *baseObject) hasPropertyStr(name string) bool { - return o.val.self.getPropStr(name) != nil +func (o *baseObject) getIdx(idx valueInt, receiver Value) Value { + return o.val.self.getStr(idx.String(), receiver) } -func (o *baseObject) _getStr(name string) Value { - p := o.getOwnPropStr(name) +func (o *baseObject) getSym(s *valueSymbol, receiver Value) Value { + return o.getWithOwnProp(o.getOwnPropSym(s), s, receiver) +} - if p == nil && o.prototype != nil { - p = o.prototype.self.getPropStr(name) +func (o *baseObject) getStr(name string, receiver Value) Value { + prop := o.values[name] + if prop == nil { + if o.prototype != nil { + if receiver == nil { + return o.prototype.self.getStr(name, o.val) + } + return o.prototype.self.getStr(name, receiver) + } } - - if p, ok := p.(*valueProperty); ok { - return p.get(o.val) + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(o.val) + } + return prop.get(receiver) } - - return p + return prop } -func (o *baseObject) getStr(name string) Value { - p := o.val.self.getPropStr(name) - if p, ok := p.(*valueProperty); ok { - return p.get(o.val) - } - - return p +func (o *baseObject) getOwnPropIdx(idx valueInt) Value { + return o.val.self.getOwnPropStr(idx.String()) } -func (o *baseObject) getSym(s *valueSymbol) Value { - p := o.getPropSym(s) - if p, ok := p.(*valueProperty); ok { - return p.get(o.val) +func (o *baseObject) getOwnPropSym(s *valueSymbol) Value { + if o.symValues != nil { + return o.symValues.get(s) } - - return p + return nil } -func (o *baseObject) get(n Value) Value { - if s, ok := n.(*valueSymbol); ok { - return o.getSym(s) - } - return o.getStr(n.String()) +func (o *baseObject) getOwnPropStr(name string) Value { + return o.values[name] } func (o *baseObject) checkDeleteProp(name string, prop *valueProperty, throw bool) bool { @@ -289,194 +404,246 @@ func (o *baseObject) _delete(name string) { if n == name { copy(o.propNames[i:], o.propNames[i+1:]) o.propNames = o.propNames[:len(o.propNames)-1] + if i < o.lastSortedPropLen { + o.lastSortedPropLen-- + if i < o.idxPropCount { + o.idxPropCount-- + } + } break } } } -func (o *baseObject) deleteStr(name string, throw bool) bool { - if val, exists := o.values[name]; exists { - if !o.checkDelete(name, val, throw) { - return false - } - o._delete(name) - } - return true +func (o *baseObject) deleteIdx(idx valueInt, throw bool) bool { + return o.val.self.deleteStr(idx.String(), throw) } func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool { - if val, exists := o.symValues[s]; exists { - if !o.checkDelete(s.String(), val, throw) { - return false + if o.symValues != nil { + if val := o.symValues.get(s); val != nil { + if !o.checkDelete(s.String(), val, throw) { + return false + } + o.symValues.remove(s) } - delete(o.symValues, s) } return true } -func (o *baseObject) delete(n Value, throw bool) bool { - if s, ok := n.(*valueSymbol); ok { - return o.deleteSym(s, throw) - } - return o.deleteStr(n.String(), throw) -} - -func (o *baseObject) put(n Value, val Value, throw bool) { - if s, ok := n.(*valueSymbol); ok { - o.putSym(s, val, throw) - } else { - o.putStr(n.String(), val, throw) - } -} - -func (o *baseObject) getOwnPropStr(name string) Value { - v := o.values[name] - if v == nil && name == __proto__ { - return o.prototype - } - return v -} - -func (o *baseObject) getOwnProp(name Value) Value { - if s, ok := name.(*valueSymbol); ok { - return o.symValues[s] +func (o *baseObject) deleteStr(name string, throw bool) bool { + if val, exists := o.values[name]; exists { + if !o.checkDelete(name, val, throw) { + return false + } + o._delete(name) } - - return o.val.self.getOwnPropStr(name.String()) + return true } -func (o *baseObject) setProto(proto *Object) *Object { +func (o *baseObject) setProto(proto *Object, throw bool) bool { current := o.prototype if current.SameAs(proto) { - return nil + return true } if !o.extensible { - return o.val.runtime.NewTypeError("%s is not extensible", o.val) + o.val.runtime.typeErrorResult(throw, "%s is not extensible", o.val) + return false } for p := proto; p != nil; { if p.SameAs(o.val) { - return o.val.runtime.NewTypeError("Cyclic __proto__ value") + o.val.runtime.typeErrorResult(throw, "Cyclic __proto__ value") + return false } p = p.self.proto() } o.prototype = proto - return nil + return true } -func (o *baseObject) putStr(name string, val Value, throw bool) { - if v, exists := o.values[name]; exists { - if prop, ok := v.(*valueProperty); ok { - if !prop.isWritable() { - o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) - return +func (o *baseObject) _setProto(val Value) { + var proto *Object + if val != _null { + if obj, ok := val.(*Object); ok { + proto = obj + } else { + return + } + } + o.setProto(proto, true) +} + +func (o *baseObject) setOwnStr(name string, val Value, throw bool) bool { + ownDesc := o.values[name] + if ownDesc == nil { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, handled := proto.self.setForeignStr(name, val, o.val, throw); handled { + 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.values[name] = val + o.propNames = append(o.propNames, name) + } + return true + } + if prop, ok := ownDesc.(*valueProperty); ok { + if !prop.isWritable() { + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } else { prop.set(o.val, val) - return } + } else { o.values[name] = val - return } + return true +} - if name == __proto__ { - var proto *Object - if val != _null { - if obj, ok := val.(*Object); ok { - proto = obj - } else { - return +func (o *baseObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { + return o.val.self.setOwnStr(idx.String(), val, throw) +} + +func (o *baseObject) setOwnSym(name *valueSymbol, val Value, throw bool) bool { + var ownDesc Value + if o.symValues != nil { + ownDesc = o.symValues.get(name) + } + if ownDesc == nil { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, handled := proto.self.setForeignSym(name, val, o.val, throw); handled { + return res } } - if ex := o.setProto(proto); ex != nil { - panic(ex) + // new property + if !o.extensible { + o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + return false + } else { + if o.symValues == nil { + o.symValues = newOrderedMap() + } + o.symValues.set(name, val) } - return + return true } - - var pprop Value - if proto := o.prototype; proto != nil { - pprop = proto.self.getPropStr(name) + if prop, ok := ownDesc.(*valueProperty); ok { + if !prop.isWritable() { + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } else { + prop.set(o.val, val) + } + } else { + o.symValues.set(name, val) } + return true +} - if pprop != nil { - if prop, ok := pprop.(*valueProperty); ok { +func (o *baseObject) _setForeignStr(name string, prop, val, receiver Value, throw bool) (bool, bool) { + if prop != nil { + if prop, ok := prop.(*valueProperty); ok { if !prop.isWritable() { - o.val.runtime.typeErrorResult(throw) - return + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false, true } - if prop.accessor { - prop.set(o.val, val) - return + if prop.setterFunc != nil { + prop.set(receiver, val) + return true, true } } } else { - if !o.extensible { - o.val.runtime.typeErrorResult(throw) - return + if proto := o.prototype; proto != nil { + if receiver != proto { + return proto.self.setForeignStr(name, val, receiver, throw) + } + return proto.self.setOwnStr(name, val, throw), true } } - - o.values[name] = val - o.propNames = append(o.propNames, name) + return false, false } -func (o *baseObject) putSym(s *valueSymbol, val Value, throw bool) { - if v, exists := o.symValues[s]; exists { - if prop, ok := v.(*valueProperty); ok { +func (o *baseObject) _setForeignIdx(idx valueInt, prop, val, receiver Value, throw bool) (bool, bool) { + if prop != nil { + if prop, ok := prop.(*valueProperty); ok { if !prop.isWritable() { - o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", s.String()) - return + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d'", idx) + return false, true + } + if prop.setterFunc != nil { + prop.set(receiver, val) + return true, true } - prop.set(o.val, val) - return } - o.symValues[s] = val - return + } else { + if proto := o.prototype; proto != nil { + if receiver != proto { + return proto.self.setForeignIdx(idx, val, receiver, throw) + } + return proto.self.setOwnIdx(idx, val, throw), true + } } + return false, false +} - var pprop Value - if proto := o.prototype; proto != nil { - pprop = proto.self.getProp(s) - } +func (o *baseObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, o.values[name], val, receiver, throw) +} - if pprop != nil { - if prop, ok := pprop.(*valueProperty); ok { +func (o *baseObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) { + return o.val.self.setForeignStr(name.String(), val, receiver, throw) +} + +func (o *baseObject) setForeignSym(name *valueSymbol, val, receiver Value, throw bool) (bool, bool) { + var prop Value + if o.symValues != nil { + prop = o.symValues.get(name) + } + if prop != nil { + if prop, ok := prop.(*valueProperty); ok { if !prop.isWritable() { - o.val.runtime.typeErrorResult(throw) - return + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false, true } - if prop.accessor { - prop.set(o.val, val) - return + if prop.setterFunc != nil { + prop.set(receiver, val) + return true, true } } } else { - if !o.extensible { - o.val.runtime.typeErrorResult(throw) - return + if proto := o.prototype; proto != nil { + if receiver != o.val { + return proto.self.setForeignSym(name, val, receiver, throw) + } + return proto.self.setOwnSym(name, val, throw), true } } - - if o.symValues == nil { - o.symValues = make(map[*valueSymbol]Value, 1) - } - o.symValues[s] = val + return false, false } -func (o *baseObject) hasOwnProperty(n Value) bool { - if s, ok := n.(*valueSymbol); ok { - _, exists := o.symValues[s] - return exists +func (o *baseObject) hasOwnPropertySym(s *valueSymbol) bool { + if o.symValues != nil { + return o.symValues.has(s) } - v := o.values[n.String()] - return v != nil + return false } func (o *baseObject) hasOwnPropertyStr(name string) bool { - v := o.values[name] - return v != nil + _, exists := o.values[name] + return exists +} + +func (o *baseObject) hasOwnPropertyIdx(idx valueInt) bool { + return o.val.self.hasOwnPropertyStr(idx.String()) } -func (o *baseObject) _defineOwnProperty(name, existingValue Value, descr propertyDescr, throw bool) (val Value, ok bool) { +func (o *baseObject) _defineOwnProperty(name string, existingValue Value, descr PropertyDescriptor, throw bool) (val Value, ok bool) { getterObj, _ := descr.Getter.(*Object) setterObj, _ := descr.Setter.(*Object) @@ -485,7 +652,7 @@ func (o *baseObject) _defineOwnProperty(name, existingValue Value, descr propert if existingValue == nil { if !o.extensible { - o.val.runtime.typeErrorResult(throw) + o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", name) return nil, false } existing = &valueProperty{} @@ -574,27 +741,14 @@ func (o *baseObject) _defineOwnProperty(name, existingValue Value, descr propert return existing, true Reject: - o.val.runtime.typeErrorResult(throw, "Cannot redefine property: %s", name.toString()) + o.val.runtime.typeErrorResult(throw, "Cannot redefine property: %s", name) return nil, false } -func (o *baseObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool { - n = toPropertyKey(n) - if s, ok := n.(*valueSymbol); ok { - existingVal := o.symValues[s] - if v, ok := o._defineOwnProperty(n, existingVal, descr, throw); ok { - if o.symValues == nil { - o.symValues = make(map[*valueSymbol]Value, 1) - } - o.symValues[s] = v - return true - } - return false - } - name := n.String() +func (o *baseObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { existingVal := o.values[name] - if v, ok := o._defineOwnProperty(n, existingVal, descr, throw); ok { + if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok { o.values[name] = v if existingVal == nil { o.propNames = append(o.propNames, name) @@ -604,6 +758,25 @@ func (o *baseObject) defineOwnProperty(n Value, descr propertyDescr, throw bool) return false } +func (o *baseObject) defineOwnPropertyIdx(idx valueInt, desc PropertyDescriptor, throw bool) bool { + return o.val.self.defineOwnPropertyStr(idx.String(), desc, throw) +} + +func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescriptor, throw bool) bool { + var existingVal Value + if o.symValues != nil { + existingVal = o.symValues.get(s) + } + if v, ok := o._defineOwnProperty(s.String(), existingVal, descr, throw); ok { + if o.symValues == nil { + o.symValues = newOrderedMap() + } + o.symValues.set(s, v) + return true + } + return false +} + func (o *baseObject) _put(name string, v Value) { if _, exists := o.values[name]; !exists { o.propNames = append(o.propNames, name) @@ -630,8 +803,15 @@ func (o *baseObject) _putProp(name string, value Value, writable, enumerable, co return prop } +func (o *baseObject) _putSym(s *valueSymbol, prop Value) { + if o.symValues == nil { + o.symValues = newOrderedMap() + } + o.symValues.set(s, prop) +} + func (o *baseObject) tryExoticToPrimitive(hint string) Value { - exoticToPrimitive := toMethod(o.getSym(symToPrimitive)) + exoticToPrimitive := toMethod(o.getSym(symToPrimitive, nil)) if exoticToPrimitive != nil { return exoticToPrimitive(FunctionCall{ This: o.val, @@ -642,7 +822,7 @@ func (o *baseObject) tryExoticToPrimitive(hint string) Value { } func (o *baseObject) tryPrimitive(methodName string) Value { - if method, ok := o.getStr(methodName).(*Object); ok { + if method, ok := o.val.self.getStr(methodName, nil).(*Object); ok { if call, ok := method.self.assertCallable(); ok { v := call(FunctionCall{ This: o.val, @@ -697,6 +877,10 @@ func (o *baseObject) assertCallable() (func(FunctionCall) Value, bool) { return nil, false } +func (o *baseObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + return nil +} + func (o *baseObject) proto() *Object { return o.prototype } @@ -705,43 +889,42 @@ func (o *baseObject) isExtensible() bool { return o.extensible } -func (o *baseObject) preventExtensions() { +func (o *baseObject) preventExtensions(bool) bool { o.extensible = false + return true } func (o *baseObject) sortLen() int64 { - return toLength(o.val.self.getStr("length")) + return toLength(o.val.self.getStr("length", nil)) } func (o *baseObject) sortGet(i int64) Value { - return o.val.self.get(intToValue(i)) + return o.val.self.getIdx(valueInt(i), nil) } func (o *baseObject) swap(i, j int64) { - ii := intToValue(i) - jj := intToValue(j) + ii := valueInt(i) + jj := valueInt(j) - x := o.val.self.get(ii) - y := o.val.self.get(jj) + x := o.val.self.getIdx(ii, nil) + y := o.val.self.getIdx(jj, nil) - o.val.self.put(ii, y, false) - o.val.self.put(jj, x, false) + o.val.self.setOwnIdx(ii, y, false) + o.val.self.setOwnIdx(jj, x, false) } func (o *baseObject) export() interface{} { m := make(map[string]interface{}) - - for item, f := o.enumerate(false, false)(); f != nil; item, f = f() { - v := item.value - if v == nil { - v = o.getStr(item.name) - } + for _, itemName := range o.ownKeys(false, nil) { + itemNameStr := itemName.String() + v := o.val.self.getStr(itemNameStr, nil) if v != nil { - m[item.name] = v.Export() + m[itemNameStr] = v.Export() } else { - m[item.name] = nil + m[itemNameStr] = nil } } + return m } @@ -766,7 +949,6 @@ type propIterItem struct { type objectPropIter struct { o *baseObject propNames []string - recursive bool idx int } @@ -813,43 +995,130 @@ func (i *objectPropIter) next() (propIterItem, iterNextFunc) { } } - if i.recursive && i.o.prototype != nil { - return i.o.prototype.self._enumerate(i.recursive)() - } return propIterItem{}, nil } -func (o *baseObject) _enumerate(recursive bool) iterNextFunc { +func (o *baseObject) enumerate() iterNextFunc { + return (&propFilterIter{ + wrapped: o.val.self.enumerateUnfiltered(), + seen: make(map[string]bool), + }).next +} + +func (o *baseObject) ownIter() iterNextFunc { + if len(o.propNames) > o.lastSortedPropLen { + o.fixPropOrder() + } propNames := make([]string, len(o.propNames)) copy(propNames, o.propNames) return (&objectPropIter{ o: o, propNames: propNames, - recursive: recursive, }).next } -func (o *baseObject) enumerate(all, recursive bool) iterNextFunc { - return (&propFilterIter{ - wrapped: o._enumerate(recursive), - all: all, - seen: make(map[string]bool), +func (o *baseObject) recursiveIter(iter iterNextFunc) iterNextFunc { + return (&recursiveIter{ + o: o, + wrapped: iter, }).next } +func (o *baseObject) enumerateUnfiltered() iterNextFunc { + return o.recursiveIter(o.ownIter()) +} + +type recursiveIter struct { + o *baseObject + wrapped iterNextFunc +} + +func (iter *recursiveIter) next() (propIterItem, iterNextFunc) { + item, next := iter.wrapped() + if next != nil { + iter.wrapped = next + return item, iter.next + } + if proto := iter.o.prototype; proto != nil { + return proto.self.enumerateUnfiltered()() + } + return propIterItem{}, nil +} + func (o *baseObject) equal(objectImpl) bool { // Rely on parent reference comparison return false } -func (o *baseObject) getOwnSymbols() (res []Value) { - for s := range o.symValues { - res = append(res, s) +// Reorder property names so that any integer properties are shifted to the beginning of the list +// in ascending order. This is to conform to ES6 9.1.12. +// Personally I think this requirement is strange. I can sort of understand where they are coming from, +// this way arrays can be specified just as objects with a 'magic' length property. However, I think +// it's safe to assume most devs don't use Objects to store integer properties. Therefore, performing +// property type checks when adding (and potentially looking up) properties would be unreasonable. +// Instead, we keep insertion order and only change it when (if) the properties get enumerated. +func (o *baseObject) fixPropOrder() { + names := o.propNames + for i := o.lastSortedPropLen; i < len(names); i++ { + name := names[i] + if idx := strToIdx(name); idx != math.MaxUint32 { + k := sort.Search(o.idxPropCount, func(j int) bool { + return strToIdx(names[j]) >= idx + }) + if k < i { + copy(names[k+1:i+1], names[k:i]) + names[k] = name + } + o.idxPropCount++ + } + } + o.lastSortedPropLen = len(names) +} + +func (o *baseObject) ownKeys(all bool, keys []Value) []Value { + if len(o.propNames) > o.lastSortedPropLen { + o.fixPropOrder() + } + if all { + for _, k := range o.propNames { + keys = append(keys, newStringValue(k)) + } + } else { + for _, k := range o.propNames { + prop := o.values[k] + if prop, ok := prop.(*valueProperty); ok && !prop.enumerable { + continue + } + keys = append(keys, newStringValue(k)) + } + } + return keys +} + +func (o *baseObject) ownSymbols() (res []Value) { + if o.symValues != nil { + iter := o.symValues.newIter() + for { + entry := iter.next() + if entry == nil { + break + } + res = append(res, entry.key) + } } return } +func (o *baseObject) ownPropertyKeys(all bool, accum []Value) []Value { + accum = o.val.self.ownKeys(all, accum) + if all { + accum = append(accum, o.ownSymbols()...) + } + + return accum +} + func (o *baseObject) hasInstance(Value) bool { panic(o.val.runtime.NewTypeError("Expecting a function in instanceof check, but got %s", o.val.toString())) } @@ -867,7 +1136,7 @@ func toMethod(v Value) func(FunctionCall) Value { } func instanceOfOperator(o Value, c *Object) bool { - if instOfHandler := toMethod(c.self.get(symHasInstance)); instOfHandler != nil { + if instOfHandler := toMethod(c.self.getSym(symHasInstance, c)); instOfHandler != nil { return instOfHandler(FunctionCall{ This: c, Arguments: []Value{o}, @@ -877,6 +1146,219 @@ func instanceOfOperator(o Value, c *Object) bool { return c.self.hasInstance(o) } +func (o *Object) get(p Value, receiver Value) Value { + switch p := p.(type) { + case valueString: + return o.self.getStr(p.String(), receiver) + case valueInt: + return o.self.getIdx(p, receiver) + case *valueSymbol: + return o.self.getSym(p, receiver) + default: + return o.self.getStr(p.String(), receiver) + } +} + +func (o *Object) getOwnProp(p Value) Value { + switch p := p.(type) { + case valueString: + return o.self.getOwnPropStr(p.String()) + case valueInt: + return o.self.getOwnPropIdx(p) + case *valueSymbol: + return o.self.getOwnPropSym(p) + default: + return o.self.getOwnPropStr(p.String()) + } +} + +func (o *Object) hasOwnProperty(p Value) bool { + switch p := p.(type) { + case valueString: + return o.self.hasOwnPropertyStr(p.String()) + case valueInt: + return o.self.hasOwnPropertyIdx(p) + case *valueSymbol: + return o.self.hasOwnPropertySym(p) + default: + return o.self.hasOwnPropertyStr(p.String()) + } +} + +func (o *Object) hasProperty(p Value) bool { + switch p := p.(type) { + case valueString: + return o.self.hasPropertyStr(p.String()) + case valueInt: + return o.self.hasPropertyIdx(p) + case *valueSymbol: + return o.self.hasPropertySym(p) + default: + return o.self.hasPropertyStr(p.String()) + } +} + +func (o *Object) setStr(name string, val, receiver Value, throw bool) bool { + if receiver == o { + return o.self.setOwnStr(name, val, throw) + } else { + if res, ok := o.self.setForeignStr(name, val, receiver, throw); !ok { + if robj, ok := receiver.(*Object); ok { + if prop := robj.self.getOwnPropStr(name); prop != nil { + if desc, ok := prop.(*valueProperty); ok { + if desc.accessor { + o.runtime.typeErrorResult(throw, "Receiver property %s is an accessor", name) + return false + } + if !desc.writable { + o.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } + } + robj.self.defineOwnPropertyStr(name, PropertyDescriptor{Value: val}, throw) + } else { + robj.self.defineOwnPropertyStr(name, PropertyDescriptor{ + Value: val, + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + }, throw) + } + } else { + o.runtime.typeErrorResult(throw, "Receiver is not an object: %v", receiver) + return false + } + } else { + return res + } + } + return true +} + +func (o *Object) set(name Value, val, receiver Value, throw bool) bool { + switch name := name.(type) { + case valueString: + return o.setStr(name.String(), val, receiver, throw) + case valueInt: + return o.setIdx(name, val, receiver, throw) + case *valueSymbol: + return o.setSym(name, val, receiver, throw) + default: + return o.setStr(name.String(), val, receiver, throw) + } +} + +func (o *Object) setOwn(name Value, val Value, throw bool) bool { + switch name := name.(type) { + case valueInt: + return o.self.setOwnIdx(name, val, throw) + case *valueSymbol: + return o.self.setOwnSym(name, val, throw) + default: + return o.self.setOwnStr(name.String(), val, throw) + } +} + +func (o *Object) setIdx(name valueInt, val, receiver Value, throw bool) bool { + if receiver == o { + return o.self.setOwnIdx(name, val, throw) + } else { + if res, ok := o.self.setForeignIdx(name, val, receiver, throw); !ok { + if robj, ok := receiver.(*Object); ok { + if prop := robj.self.getOwnPropIdx(name); prop != nil { + if desc, ok := prop.(*valueProperty); ok { + if desc.accessor { + o.runtime.typeErrorResult(throw, "Receiver property %s is an accessor", name) + return false + } + if !desc.writable { + o.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } + } + robj.self.defineOwnPropertyIdx(name, PropertyDescriptor{Value: val}, throw) + } else { + robj.self.defineOwnPropertyIdx(name, PropertyDescriptor{ + Value: val, + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + }, throw) + } + } else { + o.runtime.typeErrorResult(throw, "Receiver is not an object: %v", receiver) + return false + } + } else { + return res + } + } + return true +} + +func (o *Object) setSym(name *valueSymbol, val, receiver Value, throw bool) bool { + if receiver == o { + return o.self.setOwnSym(name, val, throw) + } else { + if res, ok := o.self.setForeignSym(name, val, receiver, throw); !ok { + if robj, ok := receiver.(*Object); ok { + if prop := robj.self.getOwnPropSym(name); prop != nil { + if desc, ok := prop.(*valueProperty); ok { + if desc.accessor { + o.runtime.typeErrorResult(throw, "Receiver property %s is an accessor", name) + return false + } + if !desc.writable { + o.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } + } + robj.self.defineOwnPropertySym(name, PropertyDescriptor{Value: val}, throw) + } else { + robj.self.defineOwnPropertySym(name, PropertyDescriptor{ + Value: val, + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + }, throw) + } + } else { + o.runtime.typeErrorResult(throw, "Receiver is not an object: %v", receiver) + return false + } + } else { + return res + } + } + return true +} + +func (o *Object) delete(n Value, throw bool) bool { + switch n := n.(type) { + case valueString: + return o.self.deleteStr(n.String(), throw) + case valueInt: + return o.self.deleteIdx(n, throw) + case *valueSymbol: + return o.self.deleteSym(n, throw) + default: + return o.self.deleteStr(n.String(), throw) + } +} + +func (o *Object) defineOwnProperty(n Value, desc PropertyDescriptor, throw bool) bool { + switch n := n.(type) { + case valueString: + return o.self.defineOwnPropertyStr(n.String(), desc, throw) + case valueInt: + return o.self.defineOwnPropertyIdx(n, desc, throw) + case *valueSymbol: + return o.self.defineOwnPropertySym(n, desc, throw) + default: + return o.self.defineOwnPropertyStr(n.String(), desc, throw) + } +} + func (o *Object) getWeakCollRefs() *weakCollections { if o.weakColls == nil { o.weakColls = &weakCollections{} diff --git a/object_args.go b/object_args.go index d0ee818e..85aaf5b7 100644 --- a/object_args.go +++ b/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() } diff --git a/object_gomap.go b/object_gomap.go index 54c826a0..ad9d4e6a 100644 --- a/object_gomap.go +++ b/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) -} diff --git a/object_gomap_reflect.go b/object_gomap_reflect.go index fee6db3c..74ae87c9 100644 --- a/object_gomap_reflect.go +++ b/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 { diff --git a/object_gomap_reflect_test.go b/object_gomap_reflect_test.go index cb81ce06..9c07bf3c 100644 --- a/object_gomap_reflect_test.go +++ b/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) + } +} diff --git a/object_gomap_test.go b/object_gomap_test.go index 730bab1c..0e966e45 100644 --- a/object_gomap_test.go +++ b/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) + } +} diff --git a/object_goreflect.go b/object_goreflect.go index 9b85d3a6..f5cf9d26 100644 --- a/object_goreflect.go +++ b/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{} { diff --git a/object_goreflect_test.go b/object_goreflect_test.go index c0df1afb..94fbf10e 100644 --- a/object_goreflect_test.go +++ b/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) + } +} diff --git a/object_goslice.go b/object_goslice.go index c13ea040..48822a0c 100644 --- a/object_goslice.go +++ b/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) } diff --git a/object_goslice_reflect.go b/object_goslice_reflect.go index e2593618..f4ff9046 100644 --- a/object_goslice_reflect.go +++ b/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) } diff --git a/object_goslice_reflect_test.go b/object_goslice_reflect_test.go index 1bad711e..cc5dcf74 100644 --- a/object_goslice_reflect_test.go +++ b/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) + } +} diff --git a/object_goslice_test.go b/object_goslice_test.go index b23eefb6..106d0e1f 100644 --- a/object_goslice_test.go +++ b/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) + } +} diff --git a/object_lazy.go b/object_lazy.go index e86b7a66..d75a9744 100644 --- a/object_lazy.go +++ b/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 { diff --git a/object_test.go b/object_test.go index 6231fc57..9bce00f8 100644 --- a/object_test.go +++ b/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) } } diff --git a/parser/expression.go b/parser/expression.go index 3eaa64e3..a90bc1a9 100644 --- a/parser/expression.go +++ b/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, diff --git a/proxy.go b/proxy.go new file mode 100644 index 00000000..21526a96 --- /dev/null +++ b/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 +} diff --git a/regexp.go b/regexp.go index b50b56dd..df57847b 100644 --- a/regexp.go +++ b/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 } diff --git a/runtime.go b/runtime.go index d072973d..28c94d18 100644 --- a/runtime.go +++ b/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 "" + } + return f.prg.src.name +} + +func (f *StackFrame) FuncName() string { + if f.funcName == "" && f.prg == nil { + return "" + } + if f.funcName == "" { + return "" + } + 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("") } 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 + } +} diff --git a/runtime_test.go b/runtime_test.go index 372a1a04..04dac30c 100644 --- a/runtime_test.go +++ b/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) diff --git a/string.go b/string.go index 35175099..58b0249f 100644 --- a/string.go +++ b/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()) } diff --git a/string_ascii.go b/string_ascii.go index 16270a8a..8cbd443e 100644 --- a/string_ascii.go +++ b/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 diff --git a/string_unicode.go b/string_unicode.go index 06b1912a..b3cd74d6 100644 --- a/string_unicode.go +++ b/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 diff --git a/tc39_test.go b/tc39_test.go index d3920cb3..6bffdd35 100644 --- a/tc39_test.go +++ b/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", } diff --git a/value.go b/value.go index 44ba6ae8..6ef9d592 100644 --- a/value.go +++ b/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") } diff --git a/vm.go b/vm.go index b54ab3f5..88fe9c85 100644 --- a/vm.go +++ b/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++ diff --git a/vm_test.go b/vm_test.go index 91bdfad3..ef23fa25 100644 --- a/vm_test.go +++ b/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() + } + } +}