From c095cd62ab460e4c4e425ce6f43f2e4a9ac96511 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 24 Jul 2021 11:04:55 +0100 Subject: [PATCH 01/19] Fixed typed arrays' defineProperty and indexing. Fixes #308. Signed-off-by: Gabri (cherry picked from commit fc7d8129ea4e61e1f487e1d78bc0adc59a5781ad) --- array.go | 120 +--------------------- array_sparse.go | 10 +- array_test.go | 13 +++ builtin_proxy.go | 20 ++-- builtin_proxy_test.go | 20 +++- object.go | 16 ++- object_dynamic.go | 12 +-- proxy.go | 20 +--- runtime.go | 225 +++++++++++++++++++++++++++++++++++++++++- runtime_test.go | 78 +++++++++++++++ string_ascii.go | 4 +- tc39_test.go | 23 +++-- typedarrays.go | 81 ++++++++++----- typedarrays_test.go | 77 +++++++++++++++ 14 files changed, 530 insertions(+), 189 deletions(-) diff --git a/array.go b/array.go index 7782ea29..480bb704 100644 --- a/array.go +++ b/array.go @@ -162,7 +162,7 @@ func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value { func (a *arrayObject) getOwnPropStr(name unistring.String) Value { if len(a.values) > 0 { - if i := strToIdx(name); i != math.MaxUint32 { + if i := strToArrayIdx(name); i != math.MaxUint32 { if i < uint32(len(a.values)) { return a.values[i] } @@ -264,7 +264,7 @@ func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool { } func (a *arrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { return a._setOwnIdx(idx, val, throw) } else { if name == "length" { @@ -325,7 +325,7 @@ func (a *arrayObject) ownKeys(all bool, accum []Value) []Value { } func (a *arrayObject) hasOwnPropertyStr(name unistring.String) bool { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { return idx < uint32(len(a.values)) && a.values[idx] != nil } else { return a.baseObject.hasOwnPropertyStr(name) @@ -433,7 +433,7 @@ func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, th } func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { return a._defineIdxProperty(idx, descr, throw) } if name == "length" { @@ -467,7 +467,7 @@ func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool { } func (a *arrayObject) deleteStr(name unistring.String, throw bool) bool { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { return a._deleteIdxProp(idx, throw) } return a.baseObject.deleteStr(name, throw) @@ -522,116 +522,6 @@ func toIdx(v valueInt) uint32 { return math.MaxUint32 } -func strToIdx64(s unistring.String) int64 { - if s == "" { - return -1 - } - l := len(s) - if s[0] == '0' { - if l == 1 { - return 0 - } - return -1 - } - var n int64 - if l < 19 { - // guaranteed not to overflow - for i := 0; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - return -1 - } - n = n*10 + int64(c-'0') - } - return n - } - if l > 19 { - // guaranteed to overflow - return -1 - } - c18 := s[18] - if c18 < '0' || c18 > '9' { - return -1 - } - for i := 0; i < 18; i++ { - c := s[i] - if c < '0' || c > '9' { - return -1 - } - n = n*10 + int64(c-'0') - } - if n >= math.MaxInt64/10+1 { - return -1 - } - n *= 10 - n1 := n + int64(c18-'0') - if n1 < n { - return -1 - } - return n1 -} - -func strToIdx(s unistring.String) uint32 { - if s == "" { - return math.MaxUint32 - } - l := len(s) - if s[0] == '0' { - if l == 1 { - return 0 - } - return math.MaxUint32 - } - var n uint32 - if l < 10 { - // guaranteed not to overflow - for i := 0; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - return math.MaxUint32 - } - n = n*10 + uint32(c-'0') - } - return n - } - if l > 10 { - // guaranteed to overflow - return math.MaxUint32 - } - c9 := s[9] - if c9 < '0' || c9 > '9' { - return math.MaxUint32 - } - for i := 0; i < 9; i++ { - c := s[i] - if c < '0' || c > '9' { - return math.MaxUint32 - } - n = n*10 + uint32(c-'0') - } - if n >= math.MaxUint32/10+1 { - return math.MaxUint32 - } - n *= 10 - n1 := n + uint32(c9-'0') - if n1 < n { - return math.MaxUint32 - } - - return n1 -} - -func strToGoIdx(s unistring.String) int { - if bits.UintSize == 64 { - return int(strToIdx64(s)) - } - i := strToIdx(s) - if i >= math.MaxInt32 { - return -1 - } - return int(i) -} - func (a *arrayObject) MemUsage(ctx *MemUsageContext) (uint64, error) { if a == nil || ctx.IsObjVisited(a) { return SizeEmpty, nil diff --git a/array_sparse.go b/array_sparse.go index 345e3df1..3f62c81a 100644 --- a/array_sparse.go +++ b/array_sparse.go @@ -133,7 +133,7 @@ func (a *sparseArrayObject) getLengthProp() Value { } func (a *sparseArrayObject) getOwnPropStr(name unistring.String) Value { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { return a._getIdx(idx) } if name == "length" { @@ -214,7 +214,7 @@ func (a *sparseArrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool { } func (a *sparseArrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { return a._setOwnIdx(idx, val, throw) } else { if name == "length" { @@ -295,7 +295,7 @@ func (a *sparseArrayObject) setValues(values []Value, objCount int) { } func (a *sparseArrayObject) hasOwnPropertyStr(name unistring.String) bool { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { i := a.findIdx(idx) return i < len(a.items) && a.items[i].idx == idx } else { @@ -372,7 +372,7 @@ func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescript } func (a *sparseArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { return a._defineIdxProperty(idx, descr, throw) } if name == "length" { @@ -406,7 +406,7 @@ func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool { } func (a *sparseArrayObject) deleteStr(name unistring.String, throw bool) bool { - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { return a._deleteIdxProp(idx, throw) } return a.baseObject.deleteStr(name, throw) diff --git a/array_test.go b/array_test.go index ae5f5060..87b0d420 100644 --- a/array_test.go +++ b/array_test.go @@ -28,6 +28,19 @@ func TestArrayExportProps(t *testing.T) { } } +func TestArrayCanonicalIndex(t *testing.T) { + const SCRIPT = ` + var a = []; + a["00"] = 1; + a["01"] = 2; + if (a[0] !== undefined) { + throw new Error("a[0]"); + } + ` + + testScript1(SCRIPT, _undefined, t) +} + func BenchmarkArrayGetStr(b *testing.B) { b.StopTimer() r := New() diff --git a/builtin_proxy.go b/builtin_proxy.go index d829232b..c0d49c0f 100644 --- a/builtin_proxy.go +++ b/builtin_proxy.go @@ -38,7 +38,7 @@ func (h *nativeProxyHandler) preventExtensions(target *Object) (bool, bool) { func (h *nativeProxyHandler) getOwnPropertyDescriptorStr(target *Object, prop unistring.String) (Value, bool) { if trap := h.handler.GetOwnPropertyDescriptorIdx; trap != nil { - if idx, ok := strPropToInt(prop); ok { + if idx, ok := strToInt(prop); ok { desc := trap(target, idx) return desc.toValue(target.runtime), true } @@ -72,7 +72,7 @@ func (h *nativeProxyHandler) getOwnPropertyDescriptorSym(target *Object, prop *S func (h *nativeProxyHandler) definePropertyStr(target *Object, prop unistring.String, desc PropertyDescriptor) (bool, bool) { if trap := h.handler.DefinePropertyIdx; trap != nil { - if idx, ok := strPropToInt(prop); ok { + if idx, ok := strToInt(prop); ok { return trap(target, idx, desc), true } } @@ -101,7 +101,7 @@ func (h *nativeProxyHandler) definePropertySym(target *Object, prop *Symbol, des func (h *nativeProxyHandler) hasStr(target *Object, prop unistring.String) (bool, bool) { if trap := h.handler.HasIdx; trap != nil { - if idx, ok := strPropToInt(prop); ok { + if idx, ok := strToInt(prop); ok { return trap(target, idx), true } } @@ -130,7 +130,7 @@ func (h *nativeProxyHandler) hasSym(target *Object, prop *Symbol) (bool, bool) { func (h *nativeProxyHandler) getStr(target *Object, prop unistring.String, receiver Value) (Value, bool) { if trap := h.handler.GetIdx; trap != nil { - if idx, ok := strPropToInt(prop); ok { + if idx, ok := strToInt(prop); ok { return trap(target, idx, receiver), true } } @@ -159,7 +159,7 @@ func (h *nativeProxyHandler) getSym(target *Object, prop *Symbol, receiver Value func (h *nativeProxyHandler) setStr(target *Object, prop unistring.String, value Value, receiver Value) (bool, bool) { if trap := h.handler.SetIdx; trap != nil { - if idx, ok := strPropToInt(prop); ok { + if idx, ok := strToInt(prop); ok { return trap(target, idx, value, receiver), true } } @@ -188,7 +188,7 @@ func (h *nativeProxyHandler) setSym(target *Object, prop *Symbol, value Value, r func (h *nativeProxyHandler) deleteStr(target *Object, prop unistring.String) (bool, bool) { if trap := h.handler.DeletePropertyIdx; trap != nil { - if idx, ok := strPropToInt(prop); ok { + if idx, ok := strToInt(prop); ok { return trap(target, idx), true } } @@ -246,8 +246,12 @@ func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) proxyHan // ProxyTrapConfig provides a simplified Go-friendly API for implementing Proxy traps. // If an *Idx trap is defined it gets called for integer property keys, including negative ones. Note that -// this also includes string property keys that can be parsed into an integer. This allows more efficient -// array operations. +// this only includes string property keys that represent a canonical integer +// (i.e. "0", "123", but not "00", "01", " 1" or "-0"). +// For efficiency strings representing integers exceeding 2^53 are not checked to see if they are canonical, +// i.e. the *Idx traps will receive "9007199254740993" as well as "9007199254740994", even though the former is not +// a canonical representation in ECMAScript (Number("9007199254740993") === 9007199254740992). +// See https://262.ecma-international.org/#sec-canonicalnumericindexstring // If an *Idx trap is not set, the corresponding string one is used. type ProxyTrapConfig struct { // A trap for Object.getPrototypeOf, Reflect.getPrototypeOf, __proto__, Object.prototype.isPrototypeOf, instanceof diff --git a/builtin_proxy_test.go b/builtin_proxy_test.go index 24b91e31..68fedb02 100644 --- a/builtin_proxy_test.go +++ b/builtin_proxy_test.go @@ -327,7 +327,7 @@ func TestProxy_native_proxy_getOwnPropertyDescriptorIdx(t *testing.T) { a := vm.NewArray() proxy1 := vm.NewProxy(a, &ProxyTrapConfig{ GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor { - panic(vm.NewTypeError("GetOwnPropertyDescriptor was called")) + panic(vm.NewTypeError("GetOwnPropertyDescriptor was called for %q", prop)) }, GetOwnPropertyDescriptorIdx: func(target *Object, prop int) PropertyDescriptor { if prop >= -1 && prop <= 1 { @@ -353,8 +353,21 @@ func TestProxy_native_proxy_getOwnPropertyDescriptorIdx(t *testing.T) { }, }) + proxy3 := vm.NewProxy(a, &ProxyTrapConfig{ + GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor { + return PropertyDescriptor{ + Value: vm.ToValue(prop), + Configurable: FLAG_TRUE, + } + }, + GetOwnPropertyDescriptorIdx: func(target *Object, prop int) PropertyDescriptor { + panic(vm.NewTypeError("GetOwnPropertyDescriptorIdx was called for %d", prop)) + }, + }) + vm.Set("proxy1", proxy1) vm.Set("proxy2", proxy2) + vm.Set("proxy3", proxy3) _, err := vm.RunString(TESTLIBX + ` var desc; for (var i = -1; i <= 1; i++) { @@ -370,6 +383,11 @@ func TestProxy_native_proxy_getOwnPropertyDescriptorIdx(t *testing.T) { desc = Object.getOwnPropertyDescriptor(proxy2, ""+i); assert(deepEqual(desc, {value: ""+i, writable: false, enumerable: false, configurable: true}), "2. str "+i); } + + for (const prop of ["00", " 0", "-0", "01"]) { + desc = Object.getOwnPropertyDescriptor(proxy3, prop); + assert(deepEqual(desc, {value: prop, writable: false, enumerable: false, configurable: true}), "3. "+prop); + } `) if err != nil { t.Fatal(err) diff --git a/object.go b/object.go index ed693a9d..393ad01c 100644 --- a/object.go +++ b/object.go @@ -125,6 +125,18 @@ func (p *PropertyDescriptor) Empty() bool { return *p == empty } +func (p *PropertyDescriptor) IsAccessor() bool { + return p.Setter != nil || p.Getter != nil +} + +func (p *PropertyDescriptor) IsData() bool { + return p.Value != nil || p.Writable != FLAG_NOT_SET +} + +func (p *PropertyDescriptor) IsGeneric() bool { + return !p.IsAccessor() && !p.IsData() +} + func (p *PropertyDescriptor) toValue(r *Runtime) Value { if p.jsDescriptor != nil { return p.jsDescriptor @@ -1203,9 +1215,9 @@ func (o *baseObject) fixPropOrder() { names := o.propNames for i := o.lastSortedPropLen; i < len(names); i++ { name := names[i] - if idx := strToIdx(name); idx != math.MaxUint32 { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { k := sort.Search(o.idxPropCount, func(j int) bool { - return strToIdx(names[j]) >= idx + return strToArrayIdx(names[j]) >= idx }) if k < i { if namesMarkedForCopy(names) { diff --git a/object_dynamic.go b/object_dynamic.go index 0d03a3b2..99cfd42b 100644 --- a/object_dynamic.go +++ b/object_dynamic.go @@ -535,7 +535,7 @@ func (a *dynamicArray) getStr(p unistring.String, receiver Value) Value { if p == "length" { return intToValue(int64(a.a.Len())) } - if idx, ok := strPropToInt(p); ok { + if idx, ok := strToInt(p); ok { return a.a.Get(idx) } return a.getParentStr(p, receiver) @@ -555,7 +555,7 @@ func (a *dynamicArray) getOwnPropStr(u unistring.String) Value { writable: true, } } - if idx, ok := strPropToInt(u); ok { + if idx, ok := strToInt(u); ok { return a.a.Get(idx) } return nil @@ -577,7 +577,7 @@ func (a *dynamicArray) setOwnStr(p unistring.String, v Value, throw bool) bool { if p == "length" { return a._setLen(v, throw) } - if idx, ok := strPropToInt(p); ok { + if idx, ok := strToInt(p); ok { return a._setIdx(idx, v, throw) } a.val.runtime.typeErrorResult(throw, "Cannot set property %q on a dynamic array", p.String()) @@ -632,7 +632,7 @@ func (a *dynamicArray) hasOwnPropertyStr(u unistring.String) bool { if u == "length" { return true } - if idx, ok := strPropToInt(u); ok { + if idx, ok := strToInt(u); ok { return a._has(idx) } return false @@ -644,7 +644,7 @@ func (a *dynamicArray) hasOwnPropertyIdx(v valueInt) bool { func (a *dynamicArray) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { if a.checkDynamicObjectPropertyDescr(name, desc, throw) { - if idx, ok := strPropToInt(name); ok { + if idx, ok := strToInt(name); ok { return a._setIdx(idx, desc.Value, throw) } a.val.runtime.typeErrorResult(throw, "Cannot define property %q on a dynamic array", name.String()) @@ -667,7 +667,7 @@ func (a *dynamicArray) _delete(idx int, throw bool) bool { } func (a *dynamicArray) deleteStr(name unistring.String, throw bool) bool { - if idx, ok := strPropToInt(name); ok { + if idx, ok := strToInt(name); ok { return a._delete(idx, throw) } if a.hasOwnPropertyStr(name) { diff --git a/proxy.go b/proxy.go index 2a850cd9..3f0dcbf0 100644 --- a/proxy.go +++ b/proxy.go @@ -894,15 +894,15 @@ func (p *proxyObject) __isCompatibleDescriptor(extensible bool, desc *PropertyDe return false } - if p.__isGenericDescriptor(desc) { + if desc.IsGeneric() { return true } - if p.__isDataDescriptor(desc) != !current.accessor { + if desc.IsData() != !current.accessor { return desc.Configurable != FLAG_FALSE } - if p.__isDataDescriptor(desc) && !current.accessor { + if desc.IsData() && !current.accessor { if !current.configurable { if desc.Writable == FLAG_TRUE && !current.writable { return false @@ -915,7 +915,7 @@ func (p *proxyObject) __isCompatibleDescriptor(extensible bool, desc *PropertyDe } return true } - if p.__isAccessorDescriptor(desc) && current.accessor { + if desc.IsAccessor() && current.accessor { if !current.configurable { if desc.Setter != nil && desc.Setter.SameAs(current.setterFunc) { return false @@ -929,18 +929,6 @@ func (p *proxyObject) __isCompatibleDescriptor(extensible bool, desc *PropertyDe return true } -func (p *proxyObject) __isAccessorDescriptor(desc *PropertyDescriptor) bool { - return desc.Setter != nil || desc.Getter != nil -} - -func (p *proxyObject) __isDataDescriptor(desc *PropertyDescriptor) bool { - return desc.Value != nil || desc.Writable != FLAG_NOT_SET -} - -func (p *proxyObject) __isGenericDescriptor(desc *PropertyDescriptor) bool { - return !p.__isAccessorDescriptor(desc) && !p.__isDataDescriptor(desc) -} - func (p *proxyObject) __sameValue(val1, val2 Value) bool { if val1 == nil && val2 == nil { return true diff --git a/runtime.go b/runtime.go index a74068c7..556ed6e0 100644 --- a/runtime.go +++ b/runtime.go @@ -1214,6 +1214,18 @@ func toIntStrict(i int64) int { return int(i) } +func toIntClamp(i int64) int { + if bits.UintSize == 32 { + if i > math.MaxInt32 { + return math.MaxInt32 + } + if i < math.MinInt32 { + return math.MinInt32 + } + } + return int(i) +} + func (r *Runtime) toIndex(v Value) int { intIdx := v.ToInteger() if intIdx >= 0 && intIdx < maxInt { @@ -2689,9 +2701,214 @@ func (r *Runtime) setGlobal(name unistring.String, v Value, strict bool) { } } -func strPropToInt(s unistring.String) (int, bool) { - if res, err := strconv.Atoi(string(s)); err == nil { - return res, true +func strToArrayIdx(s unistring.String) uint32 { + if s == "" { + return math.MaxUint32 + } + l := len(s) + if s[0] == '0' { + if l == 1 { + return 0 + } + return math.MaxUint32 + } + var n uint32 + if l < 10 { + // guaranteed not to overflow + for i := 0; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + return math.MaxUint32 + } + n = n*10 + uint32(c-'0') + } + return n + } + if l > 10 { + // guaranteed to overflow + return math.MaxUint32 + } + c9 := s[9] + if c9 < '0' || c9 > '9' { + return math.MaxUint32 + } + for i := 0; i < 9; i++ { + c := s[i] + if c < '0' || c > '9' { + return math.MaxUint32 + } + n = n*10 + uint32(c-'0') + } + if n >= math.MaxUint32/10+1 { + return math.MaxUint32 + } + n *= 10 + n1 := n + uint32(c9-'0') + if n1 < n { + return math.MaxUint32 + } + + return n1 +} + +func strToInt32(s unistring.String) (int32, bool) { + if s == "" { + return -1, false + } + neg := s[0] == '-' + if neg { + s = s[1:] + } + l := len(s) + if s[0] == '0' { + if l == 1 { + return 0, !neg + } + return -1, false + } + var n uint32 + if l < 10 { + // guaranteed not to overflow + for i := 0; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + return -1, false + } + n = n*10 + uint32(c-'0') + } + } else if l > 10 { + // guaranteed to overflow + return -1, false + } else { + c9 := s[9] + if c9 >= '0' { + if !neg && c9 > '7' || c9 > '8' { + // guaranteed to overflow + return -1, false + } + for i := 0; i < 9; i++ { + c := s[i] + if c < '0' || c > '9' { + return -1, false + } + n = n*10 + uint32(c-'0') + } + if n >= math.MaxInt32/10+1 { + // valid number, but it overflows integer + return 0, false + } + n = n*10 + uint32(c9-'0') + } else { + return -1, false + } + } + if neg { + return int32(-n), true + } + return int32(n), true +} + +func strToInt64(s unistring.String) (int64, bool) { + if s == "" { + return -1, false + } + neg := s[0] == '-' + if neg { + s = s[1:] + } + l := len(s) + if s[0] == '0' { + if l == 1 { + return 0, !neg + } + return -1, false + } + var n uint64 + if l < 19 { + // guaranteed not to overflow + for i := 0; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + return -1, false + } + n = n*10 + uint64(c-'0') + } + } else if l > 19 { + // guaranteed to overflow + return -1, false + } else { + c18 := s[18] + if c18 >= '0' { + if !neg && c18 > '7' || c18 > '8' { + // guaranteed to overflow + return -1, false + } + for i := 0; i < 18; i++ { + c := s[i] + if c < '0' || c > '9' { + return -1, false + } + n = n*10 + uint64(c-'0') + } + if n >= math.MaxInt64/10+1 { + // valid number, but it overflows integer + return 0, false + } + n = n*10 + uint64(c18-'0') + } else { + return -1, false + } + } + if neg { + return int64(-n), true + } + return int64(n), true +} + +func strToInt(s unistring.String) (int, bool) { + if bits.UintSize == 32 { + n, ok := strToInt32(s) + return int(n), ok + } + n, ok := strToInt64(s) + return int(n), ok +} + +// Attempts to convert a string into a canonical integer. +// On success returns (number, true). +// If it was a canonical number, but not an integer returns (0, false). This includes -0 and overflows. +// In all other cases returns (-1, false). +// See https://262.ecma-international.org/#sec-canonicalnumericindexstring +func strToIntNum(s unistring.String) (int, bool) { + n, ok := strToInt64(s) + if n == 0 { + return 0, ok + } + if ok && n >= -maxInt && n <= maxInt { + if bits.UintSize == 32 { + if n > math.MaxInt32 || n < math.MinInt32 { + return 0, false + } + } + return int(n), true + } + str := stringValueFromRaw(s) + if str.ToNumber().toString().SameAs(str) { + return 0, false + } + return -1, false +} + +func strToGoIdx(s unistring.String) int { + if n, ok := strToInt(s); ok { + return n + } + return -1 +} + +func strToIdx64(s unistring.String) int64 { + if n, ok := strToInt64(s); ok { + return n } - return 0, false + return -1 } diff --git a/runtime_test.go b/runtime_test.go index 572a3ff7..5a99d4be 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -2192,6 +2192,84 @@ func TestStacktraceLocationThrowFromGo(t *testing.T) { } } +func TestStrToInt64(t *testing.T) { + if _, ok := strToInt64(""); ok { + t.Fatal("") + } + if n, ok := strToInt64("0"); !ok || n != 0 { + t.Fatal("0", n, ok) + } + if n, ok := strToInt64("-0"); ok { + t.Fatal("-0", n, ok) + } + if n, ok := strToInt64("-1"); !ok || n != -1 { + t.Fatal("-1", n, ok) + } + if n, ok := strToInt64("9223372036854775808"); ok { + t.Fatal("max+1", n, ok) + } + if n, ok := strToInt64("9223372036854775817"); ok { + t.Fatal("9223372036854775817", n, ok) + } + if n, ok := strToInt64("-9223372036854775818"); ok { + t.Fatal("-9223372036854775818", n, ok) + } + if n, ok := strToInt64("9223372036854775807"); !ok || n != 9223372036854775807 { + t.Fatal("max", n, ok) + } + if n, ok := strToInt64("-9223372036854775809"); ok { + t.Fatal("min-1", n, ok) + } + if n, ok := strToInt64("-9223372036854775808"); !ok || n != -9223372036854775808 { + t.Fatal("min", n, ok) + } + if n, ok := strToInt64("-00"); ok { + t.Fatal("-00", n, ok) + } + if n, ok := strToInt64("-01"); ok { + t.Fatal("-01", n, ok) + } +} + +func TestStrToInt32(t *testing.T) { + if _, ok := strToInt32(""); ok { + t.Fatal("") + } + if n, ok := strToInt32("0"); !ok || n != 0 { + t.Fatal("0", n, ok) + } + if n, ok := strToInt32("-0"); ok { + t.Fatal("-0", n, ok) + } + if n, ok := strToInt32("-1"); !ok || n != -1 { + t.Fatal("-1", n, ok) + } + if n, ok := strToInt32("2147483648"); ok { + t.Fatal("max+1", n, ok) + } + if n, ok := strToInt32("2147483657"); ok { + t.Fatal("2147483657", n, ok) + } + if n, ok := strToInt32("-2147483658"); ok { + t.Fatal("-2147483658", n, ok) + } + if n, ok := strToInt32("2147483647"); !ok || n != 2147483647 { + t.Fatal("max", n, ok) + } + if n, ok := strToInt32("-2147483649"); ok { + t.Fatal("min-1", n, ok) + } + if n, ok := strToInt32("-2147483648"); !ok || n != -2147483648 { + t.Fatal("min", n, ok) + } + if n, ok := strToInt32("-00"); ok { + t.Fatal("-00", n, ok) + } + if n, ok := strToInt32("-01"); ok { + t.Fatal("-01", n, ok) + } +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) diff --git a/string_ascii.go b/string_ascii.go index e2de3b03..da561f68 100644 --- a/string_ascii.go +++ b/string_ascii.go @@ -49,7 +49,7 @@ func (s asciiString) utf16Runes() []rune { } // ss must be trimmed -func strToInt(ss string) (int64, error) { +func stringToInt(ss string) (int64, error) { if ss == "" { return 0, nil } @@ -70,7 +70,7 @@ func strToInt(ss string) (int64, error) { } func (s asciiString) _toInt() (int64, error) { - return strToInt(strings.TrimSpace(string(s))) + return stringToInt(strings.TrimSpace(string(s))) } func isRangeErr(err error) bool { diff --git a/tc39_test.go b/tc39_test.go index 4b6a1e5d..11b93259 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -56,6 +56,13 @@ var ( "test/language/expressions/arrow-function/scope-param-elem-var-close.js": true, "test/language/expressions/arrow-function/scope-param-elem-var-open.js": true, + // These tests are out of date (fixed in https://github.com/tc39/test262/commit/7d998a098e5420cb4b6ee4a05eb8c386d750c596) + "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/key-is-numericindex.js": true, + "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/key-is-numericindex-desc-configurable.js": true, + + // Fixed in https://github.com/tc39/test262/commit/7d998a098e5420cb4b6ee4a05eb8c386d750c596 + "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/detached-buffer.js": true, + "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js": true, // timezone "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js": true, // timezone "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone @@ -242,13 +249,14 @@ var ( "test/language/expressions/call/spread-obj-spread-order.js": true, // template strings - "test/built-ins/String/raw/zero-literal-segments.js": true, - "test/built-ins/String/raw/template-substitutions-are-appended-on-same-index.js": true, - "test/built-ins/String/raw/special-characters.js": true, - "test/built-ins/String/raw/return-the-string-value-from-template.js": true, - "test/built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js": true, - "test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js": true, - "test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js": true, + "test/built-ins/String/raw/zero-literal-segments.js": true, + "test/built-ins/String/raw/template-substitutions-are-appended-on-same-index.js": true, + "test/built-ins/String/raw/special-characters.js": true, + "test/built-ins/String/raw/return-the-string-value-from-template.js": true, + "test/built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js": true, + "test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js": true, + "test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js": true, + "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/conversion-operation-consistent-nan.js": true, // restricted unicode regexp syntax "test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js": true, @@ -409,6 +417,7 @@ var ( "sec-functiondeclarationinstantiation", "sec-functiondeclarations-in-ifstatement-statement-clauses", "sec-evaldeclarationinstantiation", + "sec-integer-indexed-exotic-objects-defineownproperty-p-desc", } ) diff --git a/typedarrays.go b/typedarrays.go index 7e3b57b9..39aad261 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -473,15 +473,16 @@ func (a *float64Array) typeMatch(v Value) bool { } func (a *typedArrayObject) _getIdx(idx int) Value { - a.viewedArrayBuf.ensureNotDetached() if 0 <= idx && idx < a.length { + a.viewedArrayBuf.ensureNotDetached() return a.typedArray.get(idx + a.offset) } return nil } func (a *typedArrayObject) getOwnPropStr(name unistring.String) Value { - if idx, ok := strPropToInt(name); ok { + idx, ok := strToIntNum(name) + if ok { v := a._getIdx(idx) if v != nil { return &valueProperty{ @@ -492,24 +493,32 @@ func (a *typedArrayObject) getOwnPropStr(name unistring.String) Value { } return nil } + if idx == 0 { + return nil + } return a.baseObject.getOwnPropStr(name) } func (a *typedArrayObject) getOwnPropIdx(idx valueInt) Value { - v := a._getIdx(toIntStrict(int64(idx))) + v := a._getIdx(toIntClamp(int64(idx))) if v != nil { return &valueProperty{ - value: v, - writable: true, - enumerable: true, + value: v, + writable: true, + enumerable: true, + configurable: true, } } return nil } func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value { - if idx, ok := strPropToInt(name); ok { - prop := a._getIdx(idx) + idx, ok := strToIntNum(name) + if ok || idx == 0 { + var prop Value + if ok { + prop = a._getIdx(idx) + } if prop == nil { if a.prototype != nil { if receiver == nil { @@ -524,7 +533,7 @@ func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value { } func (a *typedArrayObject) getIdx(idx valueInt, receiver Value) Value { - prop := a._getIdx(toIntStrict(int64(idx))) + prop := a._getIdx(toIntClamp(int64(idx))) if prop == nil { if a.prototype != nil { if receiver == nil { @@ -538,8 +547,8 @@ func (a *typedArrayObject) getIdx(idx valueInt, receiver Value) Value { func (a *typedArrayObject) _putIdx(idx int, v Value, throw bool) bool { v = v.ToNumber() - a.viewedArrayBuf.ensureNotDetached() if idx >= 0 && idx < a.length { + a.viewedArrayBuf.ensureNotDetached() a.typedArray.set(idx+a.offset, v) return true } @@ -553,14 +562,18 @@ func (a *typedArrayObject) _hasIdx(idx int) bool { } func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bool { - if idx, ok := strPropToInt(p); ok { + idx, ok := strToIntNum(p) + if ok { return a._putIdx(idx, v, throw) } + if idx == 0 { + return false + } return a.baseObject.setOwnStr(p, v, throw) } func (a *typedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool { - return a._putIdx(toIntStrict(int64(p)), v, throw) + return a._putIdx(toIntClamp(int64(p)), v, throw) } func (a *typedArrayObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) { @@ -572,50 +585,72 @@ func (a *typedArrayObject) setForeignIdx(p valueInt, v, receiver Value, throw bo } func (a *typedArrayObject) hasOwnPropertyStr(name unistring.String) bool { - if idx, ok := strPropToInt(name); ok { + idx, ok := strToIntNum(name) + if ok { a.viewedArrayBuf.ensureNotDetached() - return idx < a.length + return idx >= 0 && idx < a.length + } + if idx == 0 { + return false } - return a.baseObject.hasOwnPropertyStr(name) } func (a *typedArrayObject) hasOwnPropertyIdx(idx valueInt) bool { - return a._hasIdx(toIntStrict(int64(idx))) + return a._hasIdx(toIntClamp(int64(idx))) } func (a *typedArrayObject) _defineIdxProperty(idx int, desc PropertyDescriptor, throw bool) bool { - prop, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw) + if desc.Configurable == FLAG_FALSE || desc.Enumerable == FLAG_FALSE { + return false + } + if desc.IsAccessor() { + return false + } + if desc.Writable == FLAG_FALSE { + return false + } + _, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw) if ok { - return a._putIdx(idx, prop, throw) + return a._putIdx(idx, desc.Value, throw) } return ok } func (a *typedArrayObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { - if idx, ok := strPropToInt(name); ok { + idx, ok := strToIntNum(name) + if ok { return a._defineIdxProperty(idx, desc, throw) } + if idx == 0 { + return false + } return a.baseObject.defineOwnPropertyStr(name, desc, throw) } func (a *typedArrayObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool { - return a._defineIdxProperty(toIntStrict(int64(name)), desc, throw) + return a._defineIdxProperty(toIntClamp(int64(name)), desc, throw) } func (a *typedArrayObject) deleteStr(name unistring.String, throw bool) bool { - if idx, ok := strPropToInt(name); ok { - if idx < a.length { + idx, ok := strToIntNum(name) + if ok { + if idx >= 0 && idx < a.length { a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String()) + return false } + return true + } + if idx == 0 { + return true } - return a.baseObject.deleteStr(name, throw) } func (a *typedArrayObject) deleteIdx(idx valueInt, throw bool) bool { if idx >= 0 && int64(idx) < int64(a.length) { a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String()) + return false } return true diff --git a/typedarrays_test.go b/typedarrays_test.go index 2629240a..a3134589 100644 --- a/typedarrays_test.go +++ b/typedarrays_test.go @@ -69,3 +69,80 @@ func TestArrayBufferGoWrapper(t *testing.T) { t.Fatal(err) } } + +func TestTypedArrayIdx(t *testing.T) { + const SCRIPT = ` + var a = new Uint8Array(1); + + // 32-bit integer overflow, should not panic on 32-bit architectures + if (a[4294967297] !== undefined) { + throw new Error("4294967297"); + } + + // Canonical non-integer + a["Infinity"] = 8; + if (a["Infinity"] !== undefined) { + throw new Error("Infinity"); + } + a["NaN"] = 1; + if (a["NaN"] !== undefined) { + throw new Error("NaN"); + } + + // Non-canonical integer + a["00"] = "00"; + if (a["00"] !== "00") { + throw new Error("00"); + } + + // Non-canonical non-integer + a["1e-3"] = "1e-3"; + if (a["1e-3"] !== "1e-3") { + throw new Error("1e-3"); + } + if (a["0.001"] !== undefined) { + throw new Error("0.001"); + } + + // Negative zero + a["-0"] = 88; + if (a["-0"] !== undefined) { + throw new Error("-0"); + } + + if (a[0] !== 0) { + throw new Error("0"); + } + + a["9007199254740992"] = 1; + if (a["9007199254740992"] !== undefined) { + throw new Error("9007199254740992"); + } + a["-9007199254740992"] = 1; + if (a["-9007199254740992"] !== undefined) { + throw new Error("-9007199254740992"); + } + + // Safe integer overflow, not canonical (Number("9007199254740993") === 9007199254740992) + a["9007199254740993"] = 1; + if (a["9007199254740993"] !== 1) { + throw new Error("9007199254740993"); + } + a["-9007199254740993"] = 1; + if (a["-9007199254740993"] !== 1) { + throw new Error("-9007199254740993"); + } + + // Safe integer overflow, canonical Number("9007199254740994") == 9007199254740994 + a["9007199254740994"] = 1; + if (a["9007199254740994"] !== undefined) { + throw new Error("9007199254740994"); + } + a["-9007199254740994"] = 1; + if (a["-9007199254740994"] !== undefined) { + throw new Error("-9007199254740994"); + } + ` + + testScript1(SCRIPT, _undefined, t) +} From 3923f3e44bddb3894784e733cd32647216364620 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 22 Jul 2021 10:32:14 +0300 Subject: [PATCH 02/19] Fix Proxy creation panicing on target not callable fixes #306 Signed-off-by: Gabri (cherry picked from commit b4db7b11564488d0cbb4f5b39ad13aad3db8040f) --- builtin_proxy_test.go | 13 +++++++++++++ proxy.go | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/builtin_proxy_test.go b/builtin_proxy_test.go index 68fedb02..e10bd272 100644 --- a/builtin_proxy_test.go +++ b/builtin_proxy_test.go @@ -1248,3 +1248,16 @@ func TestProxyExport(t *testing.T) { t.Fatalf("Export returned unexpected type: %T", v1) } } + +func TestProxy_proxy_createTargetNotCallable(t *testing.T) { + // from https://github.com/tc39/test262/blob/main/test/built-ins/Proxy/create-target-is-not-callable.js + const SCRIPT = ` + var p = new Proxy({}, {}); + + assert.throws(TypeError, function() { + p(); + }); + ` + + testScript1(TESTLIB+SCRIPT, _undefined, t) +} diff --git a/proxy.go b/proxy.go index 3f0dcbf0..6680d82a 100644 --- a/proxy.go +++ b/proxy.go @@ -851,7 +851,7 @@ func (p *proxyObject) assertConstructor() func(args []Value, newTarget *Object) func (p *proxyObject) apply(call FunctionCall) Value { if p.call == nil { - p.val.runtime.NewTypeError("proxy target is not a function") + panic(p.val.runtime.NewTypeError("proxy target is not a function")) } if v, ok := p.checkHandler().apply(p.target, nilSafe(call.This), call.Arguments); ok { return v From 73539f9e872b046af555ccc7a0fca32a6cd14b06 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 24 Jul 2021 11:55:50 +0100 Subject: [PATCH 03/19] Do not modify sb for variadic calls as it breaks local variables resolution. Instead place a marker on stack and use it to count the number of args. Fixes #311. Signed-off-by: Gabri (cherry picked from commit c6e1779132c9a00ce74a90ba8ea935a4ecaba7d9) --- compiler_test.go | 11 +++++++++++ vm.go | 25 ++++++++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/compiler_test.go b/compiler_test.go index ac425809..83b5b609 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -4059,6 +4059,17 @@ func TestVariadicNew(t *testing.T) { testScript1(SCRIPT, asciiString("1,a,2"), t) } +func TestVariadicUseStackVars(t *testing.T) { + const SCRIPT = ` + function A(message) { return message; } + function B(...args){ + return A(...args); + } + B("C"); + ` + testScript1(SCRIPT, asciiString("C"), t) +} + /* func TestBabel(t *testing.T) { src, err := ioutil.ReadFile("babel7.js") diff --git a/vm.go b/vm.go index 629598c0..ccd0aea6 100644 --- a/vm.go +++ b/vm.go @@ -2634,7 +2634,7 @@ type _callEvalVariadic struct{} var callEvalVariadic _callEvalVariadic func (_callEvalVariadic) exec(vm *vm) { - vm.callEval(vm.sp-vm.sb-2, false) + vm.callEval(vm.countVariadicArgs()-2, false) } type _callEvalVariadicStrict struct{} @@ -2642,7 +2642,7 @@ type _callEvalVariadicStrict struct{} var callEvalVariadicStrict _callEvalVariadicStrict func (_callEvalVariadicStrict) exec(vm *vm) { - vm.callEval(vm.sp-vm.sb-2, true) + vm.callEval(vm.countVariadicArgs()-2, true) } type _boxThis struct{} @@ -2659,13 +2659,14 @@ func (_boxThis) exec(vm *vm) { vm.pc++ } +var variadicMarker Value = newSymbol(asciiString("[variadic marker]")) + type _startVariadic struct{} var startVariadic _startVariadic func (_startVariadic) exec(vm *vm) { - vm.push(valueInt(vm.sb)) - vm.sb = vm.sp + vm.push(variadicMarker) vm.pc++ } @@ -2673,8 +2674,19 @@ type _callVariadic struct{} var callVariadic _callVariadic +func (vm *vm) countVariadicArgs() int { + count := 0 + for i := vm.sp - 1; i >= 0; i-- { + if vm.stack[i] == variadicMarker { + return count + } + count++ + } + panic("Variadic marker was not found. Compiler bug.") +} + func (_callVariadic) exec(vm *vm) { - call(vm.sp - vm.sb - 2).exec(vm) + call(vm.countVariadicArgs() - 2).exec(vm) } type _endVariadic struct{} @@ -2683,7 +2695,6 @@ var endVariadic _endVariadic func (_endVariadic) exec(vm *vm) { vm.sp-- - vm.sb = int(vm.stack[vm.sp-1].(valueInt)) vm.stack[vm.sp-1] = vm.stack[vm.sp] vm.pc++ } @@ -3632,7 +3643,7 @@ type _newVariadic struct{} var newVariadic _newVariadic func (_newVariadic) exec(vm *vm) { - _new(vm.sp - vm.sb - 1).exec(vm) + _new(vm.countVariadicArgs() - 1).exec(vm) } type _new uint32 From e10028fd633fa22304fa7ab13de78de52fe7f719 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sun, 25 Jul 2021 11:14:10 +0100 Subject: [PATCH 04/19] Support patterns in catch clause. See #305 Signed-off-by: Gabri (cherry picked from commit f87bc71c972e9c774734c716217c4ac7208724f5) --- ast/node.go | 2 +- compiler_stmt.go | 35 ++++++++++++++++++++++------------- compiler_test.go | 16 ++++++++++++++++ parser/expression.go | 11 +++++++---- parser/statement.go | 12 +++--------- tc39_test.go | 5 +++++ 6 files changed, 54 insertions(+), 27 deletions(-) diff --git a/ast/node.go b/ast/node.go index ac71f0c9..9ae2836c 100644 --- a/ast/node.go +++ b/ast/node.go @@ -292,7 +292,7 @@ type ( CatchStatement struct { Catch file.Idx - Parameter *Identifier + Parameter BindingTarget Body *BlockStatement } diff --git a/compiler_stmt.go b/compiler_stmt.go index d472f837..dfc4bc9a 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -102,12 +102,6 @@ func (c *compiler) updateEnterBlock(enter *enterBlock) { } func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) { - if c.scope.strict && v.Catch != nil && v.Catch.Parameter != nil { - switch v.Catch.Parameter.Name { - case "arguments", "eval": - c.throwSyntaxError(int(v.Catch.Parameter.Idx)-1, "Catch variable may not be eval or arguments in strict mode") - } - } c.block = &block{ typ: blockTry, outer: c.block, @@ -146,16 +140,31 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) { c.newBlockScope() list := v.Catch.Body.List funcs := c.extractFunctions(list) - c.createFunctionBindings(funcs) - c.scope.bindNameLexical(v.Catch.Parameter.Name, true, int(v.Catch.Parameter.Idx)-1) - bindings := c.scope.bindings - if l := len(bindings); l > 1 { - // make sure the catch variable always goes first - bindings[0], bindings[l-1] = bindings[l-1], bindings[0] + if _, ok := v.Catch.Parameter.(ast.Pattern); ok { + // add anonymous binding for the catch parameter, note it must be first + c.scope.addBinding(int(v.Catch.Idx0()) - 1) } - c.compileLexicalDeclarations(list, true) + c.createBindings(v.Catch.Parameter, func(name unistring.String, offset int) { + if c.scope.strict { + switch name { + case "arguments", "eval": + c.throwSyntaxError(offset, "Catch variable may not be eval or arguments in strict mode") + } + } + c.scope.bindNameLexical(name, true, offset) + }) enter := &enterBlock{} c.emit(enter) + if pattern, ok := v.Catch.Parameter.(ast.Pattern); ok { + c.scope.bindings[0].emitGet() + c.emitPattern(pattern, func(target, init compiledExpr) { + c.emitPatternLexicalAssign(target, init, false) + }, false) + } + for _, decl := range funcs { + c.scope.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1) + } + c.compileLexicalDeclarations(list, true) c.compileFunctions(funcs) c.compileStatements(list, bodyNeedResult) c.leaveScopeBlock(enter) diff --git a/compiler_test.go b/compiler_test.go index 83b5b609..429ec682 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -4070,6 +4070,22 @@ func TestVariadicUseStackVars(t *testing.T) { testScript1(SCRIPT, asciiString("C"), t) } +func TestCatchParamPattern(t *testing.T) { + const SCRIPT = ` + function f() { + let x = 3; + try { + throw {a: 1, b: 2}; + } catch ({a, b, c = x}) { + let x = 99; + return ""+a+" "+b+" "+c; + } + } + f(); + ` + testScript1(SCRIPT, asciiString("1 2 3"), t) +} + /* func TestBabel(t *testing.T) { src, err := ioutil.ReadFile("babel7.js") diff --git a/parser/expression.go b/parser/expression.go index dfae1e1a..21ac15d8 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -142,11 +142,10 @@ func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral { } } -func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) ast.Expression { +func (self *_parser) parseBindingTarget() (target ast.BindingTarget) { if self.token == token.LET { self.token = token.IDENTIFIER } - var target ast.BindingTarget switch self.token { case token.IDENTIFIER: target = &ast.Identifier{ @@ -161,11 +160,15 @@ func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) a default: idx := self.expect(token.IDENTIFIER) self.nextStatement() - return &ast.BadExpression{From: idx, To: self.idx} + target = &ast.BadExpression{From: idx, To: self.idx} } + return +} + +func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) ast.Expression { node := &ast.Binding{ - Target: target, + Target: self.parseBindingTarget(), } if declarationList != nil { diff --git a/parser/statement.go b/parser/statement.go index bd6597e6..4870c653 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -129,17 +129,11 @@ func (self *_parser) parseTryStatement() ast.Statement { if self.token == token.CATCH { catch := self.idx self.next() - var parameter *ast.Identifier + var parameter ast.BindingTarget if self.token == token.LEFT_PARENTHESIS { self.next() - if self.token != token.IDENTIFIER { - self.expect(token.IDENTIFIER) - self.nextStatement() - return &ast.BadStatement{From: catch, To: self.idx} - } else { - parameter = self.parseIdentifier() - self.expect(token.RIGHT_PARENTHESIS) - } + parameter = self.parseBindingTarget() + self.expect(token.RIGHT_PARENTHESIS) } node.Catch = &ast.CatchStatement{ Catch: catch, diff --git a/tc39_test.go b/tc39_test.go index 11b93259..6b37137c 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -190,6 +190,8 @@ var ( "test/language/statements/for-of/dstr/let-obj-ptrn-id-init-fn-name-class.js": true, "test/language/statements/for-of/dstr/const-ary-ptrn-elem-id-init-fn-name-class.js": true, "test/language/statements/for-of/dstr/let-ary-ptrn-elem-id-init-fn-name-class.js": true, + "test/language/statements/try/dstr/obj-ptrn-id-init-fn-name-class.js": true, + "test/language/statements/try/dstr/ary-ptrn-elem-id-init-fn-name-class.js": true, // arrow-function "test/built-ins/Object/prototype/toString/proxy-function.js": true, @@ -247,6 +249,8 @@ var ( "test/language/statements/for-of/dstr/let-obj-ptrn-id-init-fn-name-arrow.js": true, "test/language/statements/for-of/dstr/array-elem-init-fn-name-arrow.js": true, "test/language/expressions/call/spread-obj-spread-order.js": true, + "test/language/statements/try/dstr/obj-ptrn-id-init-fn-name-arrow.js": true, + "test/language/statements/try/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js": true, // template strings "test/built-ins/String/raw/zero-literal-segments.js": true, @@ -403,6 +407,7 @@ var ( "sec-with-statement*", "sec-switch-*", "sec-try-*", + "sec-runtime-semantics-catchclauseevaluation", "sec-strict-mode-of-ecmascript", "sec-let-and-const-declarations*", "sec-arguments-exotic-objects-defineownproperty-p-desc", From 9cf58f86d0be6d7399710d71caeff2b58905e10e Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 26 Jul 2021 12:22:21 +0100 Subject: [PATCH 05/19] Fixed the handling of Symbol properties in destructuring assignments. Fixes #312. Signed-off-by: Gabri (cherry picked from commit c8bd2d4a95a89a2eb89c4cb949abbb735fe645d2) --- builtin_proxy_test.go | 20 ++++++++++++++++++++ destruct.go | 36 ++++++++++++++++++++++++++---------- proxy.go | 19 ++++++++++++++++++- runtime_test.go | 12 ++++++++++++ tc39_test.go | 1 + vm.go | 15 +++------------ 6 files changed, 80 insertions(+), 23 deletions(-) diff --git a/builtin_proxy_test.go b/builtin_proxy_test.go index e10bd272..38c263ba 100644 --- a/builtin_proxy_test.go +++ b/builtin_proxy_test.go @@ -1261,3 +1261,23 @@ func TestProxy_proxy_createTargetNotCallable(t *testing.T) { testScript1(TESTLIB+SCRIPT, _undefined, t) } + +func TestProxyEnumerableSymbols(t *testing.T) { + const SCRIPT = ` + var getOwnKeys = []; + var ownKeysResult = [Symbol(), "foo", "0"]; + var proxy = new Proxy({}, { + getOwnPropertyDescriptor: function(_target, key) { + getOwnKeys.push(key); + }, + ownKeys: function() { + return ownKeysResult; + }, + }); + + let {...$} = proxy; + compareArray(getOwnKeys, ownKeysResult); + ` + + testScript1(TESTLIB+SCRIPT, valueTrue, t) +} diff --git a/destruct.go b/destruct.go index 0dd77e82..2a7d5552 100644 --- a/destruct.go +++ b/destruct.go @@ -9,7 +9,7 @@ import ( type destructKeyedSource struct { r *Runtime wrapped Value - usedKeys map[unistring.String]struct{} + usedKeys map[Value]struct{} } func newDestructKeyedSource(r *Runtime, wrapped Value) *destructKeyedSource { @@ -30,9 +30,9 @@ func (d *destructKeyedSource) w() objectImpl { return d.wrapped.ToObject(d.r).self } -func (d *destructKeyedSource) recordKey(key unistring.String) { +func (d *destructKeyedSource) recordKey(key Value) { if d.usedKeys == nil { - d.usedKeys = make(map[unistring.String]struct{}) + d.usedKeys = make(map[Value]struct{}) } d.usedKeys[key] = struct{}{} } @@ -54,30 +54,32 @@ func (d *destructKeyedSource) className() string { } func (d *destructKeyedSource) getStr(p unistring.String, receiver Value) Value { - d.recordKey(p) + d.recordKey(stringValueFromRaw(p)) return d.w().getStr(p, receiver) } func (d *destructKeyedSource) getIdx(p valueInt, receiver Value) Value { - d.recordKey(p.string()) + d.recordKey(p.toString()) return d.w().getIdx(p, receiver) } func (d *destructKeyedSource) getSym(p *Symbol, receiver Value) Value { + d.recordKey(p) return d.w().getSym(p, receiver) } func (d *destructKeyedSource) getOwnPropStr(u unistring.String) Value { - d.recordKey(u) + d.recordKey(stringValueFromRaw(u)) return d.w().getOwnPropStr(u) } func (d *destructKeyedSource) getOwnPropIdx(v valueInt) Value { - d.recordKey(v.string()) + d.recordKey(v.toString()) return d.w().getOwnPropIdx(v) } func (d *destructKeyedSource) getOwnPropSym(symbol *Symbol) Value { + d.recordKey(symbol) return d.w().getOwnPropSym(symbol) } @@ -205,7 +207,7 @@ func (i *destructKeyedSourceIter) next() (propIterItem, iterNextFunc) { return item, nil } i.wrapped = next - if _, exists := i.d.usedKeys[item.name]; !exists { + if _, exists := i.d.usedKeys[stringValueFromRaw(item.name)]; !exists { return item, i.next } } @@ -245,12 +247,26 @@ func (d *destructKeyedSource) ownKeys(all bool, accum []Value) []Value { return accum } +func (d *destructKeyedSource) filterUsedKeys(keys []Value) []Value { + k := 0 + for i, key := range keys { + if _, exists := d.usedKeys[key]; exists { + continue + } + if k != i { + keys[k] = key + } + k++ + } + return keys[:k] +} + func (d *destructKeyedSource) ownSymbols(all bool, accum []Value) []Value { - return d.w().ownSymbols(all, accum) + return d.filterUsedKeys(d.w().ownSymbols(all, accum)) } func (d *destructKeyedSource) ownPropertyKeys(all bool, accum []Value) []Value { - return d.ownSymbols(all, d.ownKeys(all, accum)) + return d.filterUsedKeys(d.w().ownPropertyKeys(all, accum)) } func (d *destructKeyedSource) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value { diff --git a/proxy.go b/proxy.go index 6680d82a..808567d5 100644 --- a/proxy.go +++ b/proxy.go @@ -777,6 +777,23 @@ func (p *proxyObject) deleteSym(s *Symbol, throw bool) bool { func (p *proxyObject) ownPropertyKeys(all bool, _ []Value) []Value { if v, ok := p.proxyOwnKeys(); ok { + if !all { + k := 0 + for i, key := range v { + prop := p.val.getOwnProp(key) + if prop == nil { + continue + } + if prop, ok := prop.(*valueProperty); ok && !prop.enumerable { + continue + } + if k != i { + v[k] = v[i] + } + k++ + } + v = v[:k] + } return v } return p.target.self.ownPropertyKeys(all, nil) @@ -995,7 +1012,7 @@ func (p *proxyObject) ownKeys(all bool, _ []Value) []Value { // we can assume ac func (p *proxyObject) ownSymbols(all bool, accum []Value) []Value { if vals, ok := p.proxyOwnKeys(); ok { - res := p.filterKeys(vals, true, true) + res := p.filterKeys(vals, all, true) if accum == nil { return res } diff --git a/runtime_test.go b/runtime_test.go index 5a99d4be..e754bcce 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -2270,6 +2270,18 @@ func TestStrToInt32(t *testing.T) { } } +func TestDestructSymbol(t *testing.T) { + const SCRIPT = ` + var S = Symbol("S"); + var s, rest; + + ({[S]: s, ...rest} = {[S]: true, test: 1}); + assert.sameValue(s, true, "S"); + assert(deepEqual(rest, {test: 1}), "rest"); + ` + testScript1(TESTLIBX+SCRIPT, _undefined, t) +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) diff --git a/tc39_test.go b/tc39_test.go index 6b37137c..334a02cf 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -423,6 +423,7 @@ var ( "sec-functiondeclarations-in-ifstatement-statement-clauses", "sec-evaldeclarationinstantiation", "sec-integer-indexed-exotic-objects-defineownproperty-p-desc", + "sec-destructuring-binding-patterns", } ) diff --git a/vm.go b/vm.go index ccd0aea6..d6ea7ee6 100644 --- a/vm.go +++ b/vm.go @@ -3956,18 +3956,9 @@ func (r *Runtime) copyDataProperties(target, source Value) { return } sourceObj := source.ToObject(r) - iter := &enumerableIter{ - wrapped: sourceObj.self.enumerateOwnKeys(), - } - - for item, next := iter.next(); next != nil; item, next = next() { - v := nilSafe(sourceObj.self.getStr(item.name, nil)) - createDataPropertyOrThrow(targetObj, stringValueFromRaw(item.name), v) - } - - for _, sym := range sourceObj.self.ownSymbols(false, nil) { - v := nilSafe(sourceObj.self.getSym(sym.(*Symbol), nil)) - createDataPropertyOrThrow(targetObj, sym, v) + for _, key := range sourceObj.self.ownPropertyKeys(false, nil) { + v := nilSafe(sourceObj.get(key, nil)) + createDataPropertyOrThrow(targetObj, key, v) } } From 867c110dabc76e3abad70b0f02fef892095afc84 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 26 Jul 2021 13:22:28 +0100 Subject: [PATCH 06/19] Ensure ToPropertyKey happens earlier when assigning computed keys. Fixes #312. Signed-off-by: Gabri (cherry picked from commit cfff9ecbd76eba493691692fe12e6ce2db554f66) --- compiler_expr.go | 4 +++- compiler_test.go | 42 +++++++++++++++++++++++++++++++++++++++--- tc39_test.go | 1 + vm.go | 34 ++++++++++++++++++++++++++++++---- 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/compiler_expr.go b/compiler_expr.go index c9b7d95c..fd529a02 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1695,6 +1695,7 @@ func (e *compiledObjectLiteral) emitGetter(putOnStack bool) { } } if computed { + e.c.emit(_toPropertyKey{}) valueExpr.emitGetter(true) switch prop.Kind { case ast.PropertyKindValue, ast.PropertyKindMethod: @@ -2086,6 +2087,7 @@ func (c *compiler) emitObjectPattern(pattern *ast.ObjectPattern, emitAssign func case *ast.PropertyKeyed: c.emit(dup) c.compileExpression(prop.Key).emitGetter(true) + c.emit(_toPropertyKey{}) var target ast.Expression var initializer ast.Expression if e, ok := prop.Value.(*ast.AssignExpression); ok { @@ -2095,7 +2097,7 @@ func (c *compiler) emitObjectPattern(pattern *ast.ObjectPattern, emitAssign func target = prop.Value } c.emitAssign(target, c.compilePatternInitExpr(func() { - c.emit(getElem) + c.emit(getKey) }, initializer, prop.Idx0()), emitAssign) default: c.throwSyntaxError(int(prop.Idx0()-1), "Unsupported AssignmentProperty type: %T", prop) diff --git a/compiler_test.go b/compiler_test.go index 429ec682..52384463 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -3589,12 +3589,22 @@ func TestObjectAssignmentPatternEvalOrder(t *testing.T) { function prop1() { trace += "prop1()," - return "a"; + return { + toString: function() { + trace += "prop1-to-string(),"; + return "a"; + } + } } function prop2() { trace += "prop2(),"; - return "b"; + return { + toString: function() { + trace += "prop2-to-string(),"; + return "b"; + } + } } function target() { @@ -3610,7 +3620,7 @@ func TestObjectAssignmentPatternEvalOrder(t *testing.T) { } trace; ` - testScript1(SCRIPT, asciiString("src(),prop1(),target(),get a,prop2(),"), t) + testScript1(SCRIPT, asciiString("src(),prop1(),prop1-to-string(),target(),get a,prop2(),prop2-to-string(),"), t) } func TestArrayAssignmentPatternEvalOrder(t *testing.T) { @@ -3734,6 +3744,32 @@ func TestObjLiteralComputedKeys(t *testing.T) { testScript1(SCRIPT, _undefined, t) } +func TestObjLiteralComputedKeysEvalOrder(t *testing.T) { + const SCRIPT = ` + let trace = []; + function key() { + trace.push("key"); + return { + toString: function() { + trace.push("key-toString"); + return "key"; + } + } + } + function val() { + trace.push("val"); + return "val"; + } + + const _ = { + [key()]: val(), + } + + trace.join(","); + ` + testScript1(SCRIPT, asciiString("key,key-toString,val"), t) +} + func TestArrayAssignPattern(t *testing.T) { const SCRIPT = ` let a, b; diff --git a/tc39_test.go b/tc39_test.go index 334a02cf..61c772a3 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -424,6 +424,7 @@ var ( "sec-evaldeclarationinstantiation", "sec-integer-indexed-exotic-objects-defineownproperty-p-desc", "sec-destructuring-binding-patterns", + "sec-runtime-semantics-keyeddestructuringassignmentevaluation", } ) diff --git a/vm.go b/vm.go index d6ea7ee6..11c7153e 100644 --- a/vm.go +++ b/vm.go @@ -1366,6 +1366,14 @@ func (j jump) exec(vm *vm) { vm.pc += int(j) } +type _toPropertyKey struct{} + +func (_toPropertyKey) exec(vm *vm) { + p := vm.sp - 1 + vm.stack[p] = toPropertyKey(vm.stack[p]) + vm.pc++ +} + type _getElemRef struct{} var getElemRef _getElemRef @@ -1419,7 +1427,7 @@ var setElem1 _setElem1 func (_setElem1) exec(vm *vm) { obj := vm.stack[vm.sp-3].ToObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-2]) + propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] obj.setOwn(propName, val, true) @@ -1434,7 +1442,7 @@ var setElem1Named _setElem1Named func (_setElem1Named) exec(vm *vm) { obj := vm.stack[vm.sp-3].ToObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-2]) + propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ Value: propName, @@ -1679,7 +1687,7 @@ var setPropGetter1 _setPropGetter1 func (s _setPropGetter1) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-3]) - propName := toPropertyKey(vm.stack[vm.sp-2]) + propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ Value: asciiString("get ").concat(stringValueFromRaw(val.string())), @@ -1704,7 +1712,7 @@ var setPropSetter1 _setPropSetter1 func (s _setPropSetter1) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-3]) - propName := toPropertyKey(vm.stack[vm.sp-2]) + propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ @@ -1776,6 +1784,24 @@ func (_getElem) exec(vm *vm) { vm.pc++ } +type _getKey struct{} + +var getKey _getKey + +func (_getKey) exec(vm *vm) { + v := vm.stack[vm.sp-2] + obj := v.baseObject(vm.r) + propName := vm.stack[vm.sp-1] + if obj == nil { + panic(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + } + + vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v)) + + vm.sp-- + vm.pc++ +} + type _getElemCallee struct{} var getElemCallee _getElemCallee From d374f34e5d60c8c532d3e5744ab2aab0151b13e6 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 26 Jul 2021 17:04:18 +0100 Subject: [PATCH 07/19] Aligned detached buffer semantics with the latest specs. Fixes #315. Signed-off-by: Gabri (cherry picked from commit cd01c06263e1d223967f89e0455d8856a9319a9f) --- builtin_typedarrays.go | 127 +++++++++++++++--------------- builtin_typedarrays_test.go | 26 +++++++ tc39_test.go | 15 ++++ typedarrays.go | 74 +++++++++--------- typedarrays_test.go | 151 ++++++++++++++++++++++++++++++++++-- 5 files changed, 287 insertions(+), 106 deletions(-) diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index b1247322..3c76f4d4 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -21,7 +21,7 @@ func (ctx *typedArraySortCtx) Len() int { func (ctx *typedArraySortCtx) Less(i, j int) bool { if ctx.needValidate { - ctx.ta.viewedArrayBuf.ensureNotDetached() + ctx.ta.viewedArrayBuf.ensureNotDetached(true) ctx.needValidate = false } offset := ctx.ta.offset @@ -58,7 +58,7 @@ func (ctx *typedArraySortCtx) Less(i, j int) bool { func (ctx *typedArraySortCtx) Swap(i, j int) { if ctx.needValidate { - ctx.ta.viewedArrayBuf.ensureNotDetached() + ctx.ta.viewedArrayBuf.ensureNotDetached(true) ctx.needValidate = false } offset := ctx.ta.offset @@ -92,8 +92,10 @@ func (r *Runtime) builtin_newArrayBuffer(args []Value, newTarget *Object) *Objec func (r *Runtime) arrayBufferProto_getByteLength(call FunctionCall) Value { o := r.toObject(call.This) if b, ok := o.self.(*arrayBufferObject); ok { - b.ensureNotDetached() - return intToValue(int64(len(b.data))) + if b.ensureNotDetached(false) { + return intToValue(int64(len(b.data))) + } + return intToValue(0) } panic(r.NewTypeError("Object is not ArrayBuffer: %s", o)) } @@ -118,16 +120,15 @@ func (r *Runtime) arrayBufferProto_slice(call FunctionCall) Value { newLen := max(stop-start, 0) ret := r.speciesConstructor(o, r.global.ArrayBuffer)([]Value{intToValue(newLen)}, nil) if ab, ok := ret.self.(*arrayBufferObject); ok { - ab.ensureNotDetached() - if ret == o { - panic(r.NewTypeError("Species constructor returned the same ArrayBuffer")) - } - if int64(len(ab.data)) < newLen { - panic(r.NewTypeError("Species constructor returned an ArrayBuffer that is too small: %d", len(ab.data))) - } - b.ensureNotDetached() - - if stop > start { + if newLen > 0 { + b.ensureNotDetached(true) + if ret == o { + panic(r.NewTypeError("Species constructor returned the same ArrayBuffer")) + } + if int64(len(ab.data)) < newLen { + panic(r.NewTypeError("Species constructor returned an ArrayBuffer that is too small: %d", len(ab.data))) + } + ab.ensureNotDetached(true) copy(ab.data, b.data[start:stop]) } return ret @@ -171,7 +172,7 @@ func (r *Runtime) newDataView(args []Value, newTarget *Object) *Object { if len(args) > 1 { offsetArg := nilSafe(args[1]) byteOffset = r.toIndex(offsetArg) - buffer.ensureNotDetached() + buffer.ensureNotDetached(true) if byteOffset > len(buffer.data) { panic(r.newError(r.global.RangeError, "Start offset %s is outside the bounds of the buffer", offsetArg.String())) } @@ -210,7 +211,7 @@ func (r *Runtime) dataViewProto_getBuffer(call FunctionCall) Value { func (r *Runtime) dataViewProto_getByteLen(call FunctionCall) Value { if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { - dv.viewedArrayBuf.ensureNotDetached() + dv.viewedArrayBuf.ensureNotDetached(true) return intToValue(int64(dv.byteLen)) } panic(r.NewTypeError("Method get DataView.prototype.byteLength called on incompatible receiver %s", call.This.String())) @@ -223,7 +224,7 @@ func (r *Runtime) dataViewProto_setByteLen(call FunctionCall) Value { func (r *Runtime) dataViewProto_getByteOffset(call FunctionCall) Value { if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { - dv.viewedArrayBuf.ensureNotDetached() + dv.viewedArrayBuf.ensureNotDetached(true) return intToValue(int64(dv.byteOffset)) } panic(r.NewTypeError("Method get DataView.prototype.byteOffset called on incompatible receiver %s", call.This.String())) @@ -411,7 +412,7 @@ func (r *Runtime) typedArrayProto_getByteOffset(call FunctionCall) Value { func (r *Runtime) typedArrayProto_copyWithin(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) l := int64(ta.length) var relEnd int64 to := toIntStrict(relToIdx(call.Argument(0).ToInteger(), l)) @@ -426,7 +427,7 @@ func (r *Runtime) typedArrayProto_copyWithin(call FunctionCall) Value { offset := ta.offset elemSize := ta.elemSize if final > from { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) copy(data[(offset+to)*elemSize:], data[(offset+from)*elemSize:(offset+final)*elemSize]) } return call.This @@ -436,7 +437,7 @@ func (r *Runtime) typedArrayProto_copyWithin(call FunctionCall) Value { func (r *Runtime) typedArrayProto_entries(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) return r.createArrayIterator(ta.val, iterationKindKeyValue) } panic(r.NewTypeError("Method TypedArray.prototype.entries called on incompatible receiver %s", call.This.String())) @@ -444,7 +445,7 @@ func (r *Runtime) typedArrayProto_entries(call FunctionCall) Value { func (r *Runtime) typedArrayProto_every(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) callbackFn := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -452,7 +453,7 @@ func (r *Runtime) typedArrayProto_every(call FunctionCall) Value { Arguments: []Value{nil, nil, call.This}, } for k := 0; k < ta.length; k++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) fc.Arguments[0] = ta.typedArray.get(ta.offset + k) fc.Arguments[1] = intToValue(int64(k)) if !callbackFn(fc).ToBoolean() { @@ -467,7 +468,7 @@ func (r *Runtime) typedArrayProto_every(call FunctionCall) Value { func (r *Runtime) typedArrayProto_fill(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) l := int64(ta.length) k := toIntStrict(relToIdx(call.Argument(1).ToInteger(), l)) var relEnd int64 @@ -478,7 +479,7 @@ func (r *Runtime) typedArrayProto_fill(call FunctionCall) Value { } final := toIntStrict(relToIdx(relEnd, l)) value := ta.typedArray.toRaw(call.Argument(0)) - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) for ; k < final; k++ { ta.typedArray.setRaw(ta.offset+k, value) } @@ -490,7 +491,7 @@ func (r *Runtime) typedArrayProto_fill(call FunctionCall) Value { func (r *Runtime) typedArrayProto_filter(call FunctionCall) Value { o := r.toObject(call.This) if ta, ok := o.self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) callbackFn := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -500,7 +501,7 @@ func (r *Runtime) typedArrayProto_filter(call FunctionCall) Value { buf := make([]byte, 0, ta.length*ta.elemSize) captured := 0 for k := 0; k < ta.length; k++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) fc.Arguments[0] = ta.typedArray.get(k) fc.Arguments[1] = intToValue(int64(k)) if callbackFn(fc).ToBoolean() { @@ -529,7 +530,7 @@ func (r *Runtime) typedArrayProto_filter(call FunctionCall) Value { func (r *Runtime) typedArrayProto_find(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) predicate := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -537,7 +538,7 @@ func (r *Runtime) typedArrayProto_find(call FunctionCall) Value { Arguments: []Value{nil, nil, call.This}, } for k := 0; k < ta.length; k++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) val := ta.typedArray.get(ta.offset + k) fc.Arguments[0] = val fc.Arguments[1] = intToValue(int64(k)) @@ -552,7 +553,7 @@ func (r *Runtime) typedArrayProto_find(call FunctionCall) Value { func (r *Runtime) typedArrayProto_findIndex(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) predicate := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -560,7 +561,7 @@ func (r *Runtime) typedArrayProto_findIndex(call FunctionCall) Value { Arguments: []Value{nil, nil, call.This}, } for k := 0; k < ta.length; k++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) fc.Arguments[0] = ta.typedArray.get(ta.offset + k) fc.Arguments[1] = intToValue(int64(k)) if predicate(fc).ToBoolean() { @@ -574,7 +575,7 @@ func (r *Runtime) typedArrayProto_findIndex(call FunctionCall) Value { func (r *Runtime) typedArrayProto_forEach(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) callbackFn := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -582,7 +583,7 @@ func (r *Runtime) typedArrayProto_forEach(call FunctionCall) Value { Arguments: []Value{nil, nil, call.This}, } for k := 0; k < ta.length; k++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) if val := ta.typedArray.get(k); val != nil { fc.Arguments[0] = val fc.Arguments[1] = intToValue(int64(k)) @@ -596,7 +597,7 @@ func (r *Runtime) typedArrayProto_forEach(call FunctionCall) Value { func (r *Runtime) typedArrayProto_includes(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) length := int64(ta.length) if length == 0 { return valueFalse @@ -611,7 +612,7 @@ func (r *Runtime) typedArrayProto_includes(call FunctionCall) Value { n = max(length+n, 0) } - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) searchElement := call.Argument(0) if searchElement == _negativeZero { searchElement = _positiveZero @@ -631,7 +632,7 @@ func (r *Runtime) typedArrayProto_includes(call FunctionCall) Value { func (r *Runtime) typedArrayProto_indexOf(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) length := int64(ta.length) if length == 0 { return intToValue(-1) @@ -646,7 +647,7 @@ func (r *Runtime) typedArrayProto_indexOf(call FunctionCall) Value { n = max(length+n, 0) } - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) searchElement := call.Argument(0) if searchElement == _negativeZero { searchElement = _positiveZero @@ -666,7 +667,7 @@ func (r *Runtime) typedArrayProto_indexOf(call FunctionCall) Value { func (r *Runtime) typedArrayProto_join(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) s := call.Argument(0) var sep valueString if s != _undefined { @@ -681,14 +682,14 @@ func (r *Runtime) typedArrayProto_join(call FunctionCall) Value { var buf valueStringBuilder - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) element0 := ta.typedArray.get(0) if element0 != nil && element0 != _undefined && element0 != _null { buf.WriteString(element0.toString()) } for i := 1; i < l; i++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) buf.WriteString(sep) element := ta.typedArray.get(i) if element != nil && element != _undefined && element != _null { @@ -703,7 +704,7 @@ func (r *Runtime) typedArrayProto_join(call FunctionCall) Value { func (r *Runtime) typedArrayProto_keys(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) return r.createArrayIterator(ta.val, iterationKindKey) } panic(r.NewTypeError("Method TypedArray.prototype.keys called on incompatible receiver %s", call.This.String())) @@ -711,7 +712,7 @@ func (r *Runtime) typedArrayProto_keys(call FunctionCall) Value { func (r *Runtime) typedArrayProto_lastIndexOf(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) length := int64(ta.length) if length == 0 { return intToValue(-1) @@ -733,7 +734,7 @@ func (r *Runtime) typedArrayProto_lastIndexOf(call FunctionCall) Value { } } - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) searchElement := call.Argument(0) if searchElement == _negativeZero { searchElement = _positiveZero @@ -754,7 +755,7 @@ func (r *Runtime) typedArrayProto_lastIndexOf(call FunctionCall) Value { func (r *Runtime) typedArrayProto_map(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) callbackFn := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -763,7 +764,7 @@ func (r *Runtime) typedArrayProto_map(call FunctionCall) Value { } dst := r.typedArraySpeciesCreate(ta, []Value{intToValue(int64(ta.length))}) for i := 0; i < ta.length; i++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) fc.Arguments[0] = ta.typedArray.get(ta.offset + i) fc.Arguments[1] = intToValue(int64(i)) dst.typedArray.set(i, callbackFn(fc)) @@ -775,7 +776,7 @@ func (r *Runtime) typedArrayProto_map(call FunctionCall) Value { func (r *Runtime) typedArrayProto_reduce(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) callbackFn := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -795,7 +796,7 @@ func (r *Runtime) typedArrayProto_reduce(call FunctionCall) Value { panic(r.NewTypeError("Reduce of empty array with no initial value")) } for ; k < ta.length; k++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) idx := valueInt(k) fc.Arguments[1] = ta.typedArray.get(ta.offset + k) fc.Arguments[2] = idx @@ -808,7 +809,7 @@ func (r *Runtime) typedArrayProto_reduce(call FunctionCall) Value { func (r *Runtime) typedArrayProto_reduceRight(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) callbackFn := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -828,7 +829,7 @@ func (r *Runtime) typedArrayProto_reduceRight(call FunctionCall) Value { panic(r.NewTypeError("Reduce of empty array with no initial value")) } for ; k >= 0; k-- { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) idx := valueInt(k) fc.Arguments[1] = ta.typedArray.get(ta.offset + k) fc.Arguments[2] = idx @@ -841,7 +842,7 @@ func (r *Runtime) typedArrayProto_reduceRight(call FunctionCall) Value { func (r *Runtime) typedArrayProto_reverse(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) l := ta.length middle := l / 2 for lower := 0; lower != middle; lower++ { @@ -861,10 +862,10 @@ func (r *Runtime) typedArrayProto_set(call FunctionCall) Value { if targetOffset < 0 { panic(r.newError(r.global.RangeError, "offset should be >= 0")) } - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) targetLen := ta.length if src, ok := srcObj.self.(*typedArrayObject); ok { - src.viewedArrayBuf.ensureNotDetached() + src.viewedArrayBuf.ensureNotDetached(true) srcLen := src.length if x := srcLen + targetOffset; x < 0 || x > targetLen { panic(r.newError(r.global.RangeError, "Source is too large")) @@ -920,7 +921,7 @@ func (r *Runtime) typedArrayProto_set(call FunctionCall) Value { } for i := 0; i < srcLen; i++ { val := nilSafe(srcObj.self.getIdx(valueInt(i), nil)) - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) ta.typedArray.set(targetOffset+i, val) } } @@ -931,7 +932,7 @@ func (r *Runtime) typedArrayProto_set(call FunctionCall) Value { func (r *Runtime) typedArrayProto_slice(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) length := int64(ta.length) start := toIntStrict(relToIdx(call.Argument(0).ToInteger(), length)) var e int64 @@ -949,14 +950,14 @@ func (r *Runtime) typedArrayProto_slice(call FunctionCall) Value { dst := r.typedArraySpeciesCreate(ta, []Value{intToValue(int64(count))}) if dst.defaultCtor == ta.defaultCtor { if count > 0 { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) offset := ta.offset elemSize := ta.elemSize copy(dst.viewedArrayBuf.data, ta.viewedArrayBuf.data[(offset+start)*elemSize:(offset+start+count)*elemSize]) } } else { for i := 0; i < count; i++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) dst.typedArray.set(i, ta.typedArray.get(ta.offset+start+i)) } } @@ -967,7 +968,7 @@ func (r *Runtime) typedArrayProto_slice(call FunctionCall) Value { func (r *Runtime) typedArrayProto_some(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) callbackFn := r.toCallable(call.Argument(0)) fc := FunctionCall{ ctx: call.ctx, @@ -975,7 +976,7 @@ func (r *Runtime) typedArrayProto_some(call FunctionCall) Value { Arguments: []Value{nil, nil, call.This}, } for k := 0; k < ta.length; k++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) fc.Arguments[0] = ta.typedArray.get(ta.offset + k) fc.Arguments[1] = intToValue(int64(k)) if callbackFn(fc).ToBoolean() { @@ -989,7 +990,7 @@ func (r *Runtime) typedArrayProto_some(call FunctionCall) Value { func (r *Runtime) typedArrayProto_sort(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) var compareFn func(FunctionCall) Value if arg := call.Argument(0); arg != _undefined { @@ -1032,7 +1033,7 @@ func (r *Runtime) typedArrayProto_toLocaleString(call FunctionCall) Value { length := ta.length var buf valueStringBuilder for i := 0; i < length; i++ { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) if i > 0 { buf.WriteRune(',') } @@ -1046,7 +1047,7 @@ func (r *Runtime) typedArrayProto_toLocaleString(call FunctionCall) Value { func (r *Runtime) typedArrayProto_values(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) return r.createArrayIterator(ta.val, iterationKindValue) } panic(r.NewTypeError("Method TypedArray.prototype.values called on incompatible receiver %s", call.This.String())) @@ -1098,7 +1099,7 @@ func (r *Runtime) typedArraySpeciesCreate(ta *typedArrayObject, args []Value) *t func (r *Runtime) typedArrayCreate(ctor *Object, args []Value) *typedArrayObject { o := r.toConstructor(ctor)(args, ctor) if ta, ok := o.self.(*typedArrayObject); ok { - ta.viewedArrayBuf.ensureNotDetached() + ta.viewedArrayBuf.ensureNotDetached(true) if len(args) == 1 { if l, ok := args[0].(valueInt); ok { if ta.length < int(l) { @@ -1180,7 +1181,7 @@ func (r *Runtime) _newTypedArrayFromArrayBuffer(ab *arrayBufferObject, args []Va panic(r.newError(r.global.RangeError, "Start offset of %s should be a multiple of %d", newTarget.self.getStr("name", nil), ta.elemSize)) } } - ab.ensureNotDetached() + ab.ensureNotDetached(true) var length int if len(args) > 2 && args[2] != nil && args[2] != _undefined { length = r.toIndex(args[2]) @@ -1200,7 +1201,7 @@ func (r *Runtime) _newTypedArrayFromArrayBuffer(ab *arrayBufferObject, args []Va func (r *Runtime) _newTypedArrayFromTypedArray(src *typedArrayObject, newTarget *Object) *Object { dst := r.typedArrayCreate(newTarget, []Value{_positiveZero}) - src.viewedArrayBuf.ensureNotDetached() + src.viewedArrayBuf.ensureNotDetached(true) l := src.length dst.viewedArrayBuf.prototype = r.getPrototypeFromCtor(r.toObject(src.viewedArrayBuf.getStr("constructor", nil)), r.global.ArrayBuffer, r.global.ArrayBufferPrototype) dst.viewedArrayBuf.data = allocByteSlice(toIntStrict(int64(l) * int64(dst.elemSize))) diff --git a/builtin_typedarrays_test.go b/builtin_typedarrays_test.go index 27ad2326..cadc2be2 100644 --- a/builtin_typedarrays_test.go +++ b/builtin_typedarrays_test.go @@ -304,3 +304,29 @@ func TestInt32ArrayNegativeIndex(t *testing.T) { testScript1(SCRIPT, valueTrue, t) } + +func TestTypedArrayDefineProperty(t *testing.T) { + const SCRIPT = ` + var sample = new Uint8Array([42, 42]); + + assert.sameValue( + Reflect.defineProperty(sample, "0", { + value: 8, + configurable: true, + enumerable: true, + writable: true + }), + true + ); + + assert.sameValue(sample[0], 8, "property value was set"); + let descriptor0 = Object.getOwnPropertyDescriptor(sample, "0"); + assert.sameValue(descriptor0.value, 8); + assert.sameValue(descriptor0.configurable, true, "configurable"); + assert.sameValue(descriptor0.enumerable, true); + assert.sameValue(descriptor0.writable, true); + + ` + + testScript1(TESTLIB+SCRIPT, _undefined, t) +} diff --git a/tc39_test.go b/tc39_test.go index 61c772a3..b90a57d6 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -62,6 +62,18 @@ var ( // Fixed in https://github.com/tc39/test262/commit/7d998a098e5420cb4b6ee4a05eb8c386d750c596 "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/detached-buffer.js": true, + // Fixed in https://github.com/tc39/test262/commit/0bb8fe8aba97765aa3a8d4dd8880cd8e3c238f68 + "test/built-ins/TypedArrayConstructors/internals/Get/detached-buffer.js": true, + "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/tonumber-value-detached-buffer.js": true, + // 36c2cd165f93e194b9bcad26e69e8571b1d0e6ed + "test/built-ins/ArrayBuffer/prototype/byteLength/detached-buffer.js": true, + + // 96aff62fb25cf9ef27929a8ab822ee853d99b06e + "test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js": true, + "test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds.js": true, + + // 167e596a649ede35df11d03cb3c093941c9cf396 + "test/built-ins/TypedArrayConstructors/internals/Set/detached-buffer.js": true, "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js": true, // timezone "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js": true, // timezone @@ -261,6 +273,7 @@ var ( "test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js": true, "test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js": true, "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/conversion-operation-consistent-nan.js": true, + "test/built-ins/TypedArrayConstructors/internals/Set/conversion-operation-consistent-nan.js": true, // restricted unicode regexp syntax "test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js": true, @@ -423,6 +436,8 @@ var ( "sec-functiondeclarations-in-ifstatement-statement-clauses", "sec-evaldeclarationinstantiation", "sec-integer-indexed-exotic-objects-defineownproperty-p-desc", + "sec-integer-indexed-exotic-objects-get-p-receiver", + "sec-integer-indexed-exotic-objects-set-p-v-receiver", "sec-destructuring-binding-patterns", "sec-runtime-semantics-keyeddestructuringassignmentevaluation", } diff --git a/typedarrays.go b/typedarrays.go index 39aad261..70a965ff 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -474,7 +474,9 @@ func (a *float64Array) typeMatch(v Value) bool { func (a *typedArrayObject) _getIdx(idx int) Value { if 0 <= idx && idx < a.length { - a.viewedArrayBuf.ensureNotDetached() + if !a.viewedArrayBuf.ensureNotDetached(false) { + return nil + } return a.typedArray.get(idx + a.offset) } return nil @@ -486,9 +488,10 @@ func (a *typedArrayObject) getOwnPropStr(name unistring.String) Value { v := a._getIdx(idx) if v != nil { return &valueProperty{ - value: v, - writable: true, - enumerable: true, + value: v, + writable: true, + enumerable: true, + configurable: true, } } return nil @@ -514,20 +517,11 @@ func (a *typedArrayObject) getOwnPropIdx(idx valueInt) Value { func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value { idx, ok := strToIntNum(name) - if ok || idx == 0 { - var prop Value - if ok { - prop = a._getIdx(idx) - } - if prop == nil { - if a.prototype != nil { - if receiver == nil { - return a.prototype.self.getStr(name, a.val) - } - return a.prototype.self.getStr(name, receiver) - } - } - return prop + if ok { + return a._getIdx(idx) + } + if idx == 0 { + return nil } return a.baseObject.getStr(name, receiver) } @@ -545,35 +539,37 @@ func (a *typedArrayObject) getIdx(idx valueInt, receiver Value) Value { return prop } -func (a *typedArrayObject) _putIdx(idx int, v Value, throw bool) bool { +func (a *typedArrayObject) isValidIntegerIndex(idx int, throw bool) bool { + return a.viewedArrayBuf.ensureNotDetached(throw) && idx >= 0 && idx < a.length +} + +func (a *typedArrayObject) _putIdx(idx int, v Value) { v = v.ToNumber() - if idx >= 0 && idx < a.length { - a.viewedArrayBuf.ensureNotDetached() + if a.isValidIntegerIndex(idx, false) { a.typedArray.set(idx+a.offset, v) - return true } - // As far as I understand the specification this should throw, but neither V8 nor SpiderMonkey does - return false } func (a *typedArrayObject) _hasIdx(idx int) bool { - a.viewedArrayBuf.ensureNotDetached() - return idx >= 0 && idx < a.length + return a.viewedArrayBuf.ensureNotDetached(false) && idx >= 0 && idx < a.length } func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bool { idx, ok := strToIntNum(p) if ok { - return a._putIdx(idx, v, throw) + a._putIdx(idx, v) + return true } if idx == 0 { + v.ToNumber() // make sure it throws return false } return a.baseObject.setOwnStr(p, v, throw) } func (a *typedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool { - return a._putIdx(toIntClamp(int64(p)), v, throw) + a._putIdx(toIntClamp(int64(p)), v) + return true } func (a *typedArrayObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) { @@ -587,8 +583,7 @@ func (a *typedArrayObject) setForeignIdx(p valueInt, v, receiver Value, throw bo func (a *typedArrayObject) hasOwnPropertyStr(name unistring.String) bool { idx, ok := strToIntNum(name) if ok { - a.viewedArrayBuf.ensureNotDetached() - return idx >= 0 && idx < a.length + return a.viewedArrayBuf.ensureNotDetached(false) && idx >= 0 && idx < a.length } if idx == 0 { return false @@ -612,7 +607,11 @@ func (a *typedArrayObject) _defineIdxProperty(idx int, desc PropertyDescriptor, } _, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw) if ok { - return a._putIdx(idx, desc.Value, throw) + if !a.isValidIntegerIndex(idx, throw) { + return false + } + a._putIdx(idx, desc.Value) + return true } return ok } @@ -623,6 +622,7 @@ func (a *typedArrayObject) defineOwnPropertyStr(name unistring.String, desc Prop return a._defineIdxProperty(idx, desc, throw) } if idx == 0 { + a.viewedArrayBuf.ensureNotDetached(throw) return false } return a.baseObject.defineOwnPropertyStr(name, desc, throw) @@ -635,7 +635,7 @@ func (a *typedArrayObject) defineOwnPropertyIdx(name valueInt, desc PropertyDesc func (a *typedArrayObject) deleteStr(name unistring.String, throw bool) bool { idx, ok := strToIntNum(name) if ok { - if idx >= 0 && idx < a.length { + if a.viewedArrayBuf.ensureNotDetached(throw) && idx >= 0 && idx < a.length { a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String()) return false } @@ -648,7 +648,7 @@ func (a *typedArrayObject) deleteStr(name unistring.String, throw bool) bool { } func (a *typedArrayObject) deleteIdx(idx valueInt, throw bool) bool { - if idx >= 0 && int64(idx) < int64(a.length) { + if a.viewedArrayBuf.ensureNotDetached(throw) && idx >= 0 && int64(idx) < int64(a.length) { a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String()) return false } @@ -776,7 +776,7 @@ func (r *Runtime) newFloat64ArrayObject(buf *arrayBufferObject, offset, length i func (o *dataViewObject) getIdxAndByteOrder(idxVal, littleEndianVal Value, size int) (int, byteOrder) { getIdx := o.val.runtime.toIndex(idxVal) - o.viewedArrayBuf.ensureNotDetached() + o.viewedArrayBuf.ensureNotDetached(true) if getIdx+size > o.byteLen { panic(o.val.runtime.newError(o.val.runtime.global.RangeError, "Index %d is out of bounds", getIdx)) } @@ -814,10 +814,12 @@ func (o *dataViewObject) MemUsage(ctx *MemUsageContext) (uint64, error) { return total, err } -func (o *arrayBufferObject) ensureNotDetached() { +func (o *arrayBufferObject) ensureNotDetached(throw bool) bool { if o.detached { - panic(o.val.runtime.NewTypeError("ArrayBuffer is detached")) + o.val.runtime.typeErrorResult(throw, "ArrayBuffer is detached") + return false } + return true } func (o *arrayBufferObject) getFloat32(idx int, byteOrder byteOrder) float32 { diff --git a/typedarrays_test.go b/typedarrays_test.go index a3134589..57ed5a19 100644 --- a/typedarrays_test.go +++ b/typedarrays_test.go @@ -56,13 +56,8 @@ func TestArrayBufferGoWrapper(t *testing.T) { t.Fatal("buf1.Detached() returned false") } _, err = vm.RunString(` - try { - (b[0]); - throw new Error("expected TypeError"); - } catch (e) { - if (!(e instanceof TypeError)) { - throw e; - } + if (b[0] !== undefined) { + throw new Error("b[0] !== undefined"); } `) if err != nil { @@ -146,3 +141,145 @@ func TestTypedArrayIdx(t *testing.T) { testScript1(SCRIPT, _undefined, t) } + +func TestTypedArraySetDetachedBuffer(t *testing.T) { + const SCRIPT = ` + let sample = new Uint8Array([42]); + $DETACHBUFFER(sample.buffer); + sample[0] = 1; + + assert.sameValue(sample[0], undefined, 'sample[0] = 1 is undefined'); + sample['1.1'] = 1; + assert.sameValue(sample['1.1'], undefined, 'sample[\'1.1\'] = 1 is undefined'); + sample['-0'] = 1; + assert.sameValue(sample['-0'], undefined, 'sample[\'-0\'] = 1 is undefined'); + sample['-1'] = 1; + assert.sameValue(sample['-1'], undefined, 'sample[\'-1\'] = 1 is undefined'); + sample['1'] = 1; + assert.sameValue(sample['1'], undefined, 'sample[\'1\'] = 1 is undefined'); + sample['2'] = 1; + assert.sameValue(sample['2'], undefined, 'sample[\'2\'] = 1 is undefined'); + ` + vm := New() + vm.Set("$DETACHBUFFER", func(buf *ArrayBuffer) { + buf.Detach() + }) + _, err := vm.RunString(TESTLIB + SCRIPT) + if err != nil { + t.Fatal(err) + } +} + +func TestTypedArrayDefineDetachedBuffer(t *testing.T) { + const SCRIPT = ` + var desc = { + value: 0, + configurable: false, + enumerable: true, + writable: true + }; + + var obj = { + valueOf: function() { + throw new Error("valueOf() was called"); + } + }; + let sample = new Uint8Array(42); + $DETACHBUFFER(sample.buffer); + + assert.sameValue( + Reflect.defineProperty(sample, "0", desc), + false, + 'Reflect.defineProperty(sample, "0", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "-1", desc), + false, + 'Reflect.defineProperty(sample, "-1", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "1.1", desc), + false, + 'Reflect.defineProperty(sample, "1.1", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "-0", desc), + false, + 'Reflect.defineProperty(sample, "-0", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "2", { + configurable: true, + enumerable: true, + writable: true, + value: obj + }), + false, + 'Reflect.defineProperty(sample, "2", {configurable: true, enumerable: true, writable: true, value: obj}) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "3", { + configurable: false, + enumerable: false, + writable: true, + value: obj + }), + false, + 'Reflect.defineProperty(sample, "3", {configurable: false, enumerable: false, writable: true, value: obj}) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "4", { + writable: false, + configurable: false, + enumerable: true, + value: obj + }), + false, + 'Reflect.defineProperty("new TA(42)", "4", {writable: false, configurable: false, enumerable: true, value: obj}) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "42", desc), + false, + 'Reflect.defineProperty(sample, "42", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "43", desc), + false, + 'Reflect.defineProperty(sample, "43", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "5", { + get: function() {} + }), + false, + 'Reflect.defineProperty(sample, "5", {get: function() {}}) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "6", { + configurable: false, + enumerable: true, + writable: true + }), + false, + 'Reflect.defineProperty(sample, "6", {configurable: false, enumerable: true, writable: true}) must return false' + ); + ` + vm := New() + vm.Set("$DETACHBUFFER", func(buf *ArrayBuffer) { + buf.Detach() + }) + _, err := vm.RunString(TESTLIB + SCRIPT) + if err != nil { + t.Fatal(err) + } +} From 23ea7f2b5b2ccaea3f2e730bab2a5cc681443bbb Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 26 Jul 2021 23:46:56 +0100 Subject: [PATCH 08/19] More typed arrays fixes Signed-off-by: Gabri (cherry picked from commit 9cf7f406dbb7d0c438530c6a3bf2f0a01bfe5c1e) --- builtin_typedarrays_test.go | 26 --------------- typedarrays.go | 35 ++++++++------------ typedarrays_test.go | 65 ++++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 48 deletions(-) diff --git a/builtin_typedarrays_test.go b/builtin_typedarrays_test.go index cadc2be2..27ad2326 100644 --- a/builtin_typedarrays_test.go +++ b/builtin_typedarrays_test.go @@ -304,29 +304,3 @@ func TestInt32ArrayNegativeIndex(t *testing.T) { testScript1(SCRIPT, valueTrue, t) } - -func TestTypedArrayDefineProperty(t *testing.T) { - const SCRIPT = ` - var sample = new Uint8Array([42, 42]); - - assert.sameValue( - Reflect.defineProperty(sample, "0", { - value: 8, - configurable: true, - enumerable: true, - writable: true - }), - true - ); - - assert.sameValue(sample[0], 8, "property value was set"); - let descriptor0 = Object.getOwnPropertyDescriptor(sample, "0"); - assert.sameValue(descriptor0.value, 8); - assert.sameValue(descriptor0.configurable, true, "configurable"); - assert.sameValue(descriptor0.enumerable, true); - assert.sameValue(descriptor0.writable, true); - - ` - - testScript1(TESTLIB+SCRIPT, _undefined, t) -} diff --git a/typedarrays.go b/typedarrays.go index 70a965ff..bb32b30f 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -527,20 +527,17 @@ func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value { } func (a *typedArrayObject) getIdx(idx valueInt, receiver Value) Value { - prop := a._getIdx(toIntClamp(int64(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 prop + return a._getIdx(toIntClamp(int64(idx))) } func (a *typedArrayObject) isValidIntegerIndex(idx int, throw bool) bool { - return a.viewedArrayBuf.ensureNotDetached(throw) && idx >= 0 && idx < a.length + if a.viewedArrayBuf.ensureNotDetached(throw) { + if idx >= 0 && idx < a.length { + return true + } + a.val.runtime.typeErrorResult(throw, "Invalid typed array index") + } + return false } func (a *typedArrayObject) _putIdx(idx int, v Value) { @@ -551,7 +548,7 @@ func (a *typedArrayObject) _putIdx(idx int, v Value) { } func (a *typedArrayObject) _hasIdx(idx int) bool { - return a.viewedArrayBuf.ensureNotDetached(false) && idx >= 0 && idx < a.length + return a.isValidIntegerIndex(idx, false) } func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bool { @@ -583,7 +580,7 @@ func (a *typedArrayObject) setForeignIdx(p valueInt, v, receiver Value, throw bo func (a *typedArrayObject) hasOwnPropertyStr(name unistring.String) bool { idx, ok := strToIntNum(name) if ok { - return a.viewedArrayBuf.ensureNotDetached(false) && idx >= 0 && idx < a.length + return a._hasIdx(idx) } if idx == 0 { return false @@ -596,13 +593,8 @@ func (a *typedArrayObject) hasOwnPropertyIdx(idx valueInt) bool { } func (a *typedArrayObject) _defineIdxProperty(idx int, desc PropertyDescriptor, throw bool) bool { - if desc.Configurable == FLAG_FALSE || desc.Enumerable == FLAG_FALSE { - return false - } - if desc.IsAccessor() { - return false - } - if desc.Writable == FLAG_FALSE { + if desc.Configurable == FLAG_FALSE || desc.Enumerable == FLAG_FALSE || desc.IsAccessor() || desc.Writable == FLAG_FALSE { + a.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", idx) return false } _, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw) @@ -623,6 +615,7 @@ func (a *typedArrayObject) defineOwnPropertyStr(name unistring.String, desc Prop } if idx == 0 { a.viewedArrayBuf.ensureNotDetached(throw) + a.val.runtime.typeErrorResult(throw, "Invalid typed array index") return false } return a.baseObject.defineOwnPropertyStr(name, desc, throw) @@ -635,7 +628,7 @@ func (a *typedArrayObject) defineOwnPropertyIdx(name valueInt, desc PropertyDesc func (a *typedArrayObject) deleteStr(name unistring.String, throw bool) bool { idx, ok := strToIntNum(name) if ok { - if a.viewedArrayBuf.ensureNotDetached(throw) && idx >= 0 && idx < a.length { + if !a.isValidIntegerIndex(idx, false) { a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String()) return false } diff --git a/typedarrays_test.go b/typedarrays_test.go index 57ed5a19..aa1ad1f0 100644 --- a/typedarrays_test.go +++ b/typedarrays_test.go @@ -170,7 +170,7 @@ func TestTypedArraySetDetachedBuffer(t *testing.T) { } } -func TestTypedArrayDefineDetachedBuffer(t *testing.T) { +func TestTypedArrayDefinePropDetachedBuffer(t *testing.T) { const SCRIPT = ` var desc = { value: 0, @@ -283,3 +283,66 @@ func TestTypedArrayDefineDetachedBuffer(t *testing.T) { t.Fatal(err) } } + +func TestTypedArrayDefineProperty(t *testing.T) { + const SCRIPT = ` + var a = new Uint8Array(1); + + assert.throws(TypeError, function() { + Object.defineProperty(a, "1", {value: 1}); + }); + assert.sameValue(Reflect.defineProperty(a, "1", {value: 1}), false, "1"); + + assert.throws(TypeError, function() { + Object.defineProperty(a, "Infinity", {value: 8}); + }); + assert.sameValue(Reflect.defineProperty(a, "Infinity", {value: 8}), false, "Infinity"); + + Object.defineProperty(a, "test", {value: "passed"}); + assert.sameValue(a.test, "passed", "string property"); + + assert.throws(TypeError, function() { + Object.defineProperty(a, "0", {value: 1, writable: false}); + }, "define non-writable"); + + assert.throws(TypeError, function() { + Object.defineProperty(a, "0", {get() { return 1; }}); + }, "define accessor"); + + var sample = new Uint8Array([42, 42]); + + assert.sameValue( + Reflect.defineProperty(sample, "0", { + value: 8, + configurable: true, + enumerable: true, + writable: true + }), + true + ); + + assert.sameValue(sample[0], 8, "property value was set"); + let descriptor0 = Object.getOwnPropertyDescriptor(sample, "0"); + assert.sameValue(descriptor0.value, 8); + assert.sameValue(descriptor0.configurable, true, "configurable"); + assert.sameValue(descriptor0.enumerable, true); + assert.sameValue(descriptor0.writable, true); + ` + testScript1(TESTLIB+SCRIPT, _undefined, t) +} + +func TestTypedArrayGetInvalidIndex(t *testing.T) { + const SCRIPT = ` + var TypedArray = Object.getPrototypeOf(Int8Array); + var proto = TypedArray.prototype; + Object.defineProperty(proto, "1", { + get: function() { + throw new Error("OrdinaryGet was called!"); + } + }); + var a = new Uint8Array(1); + assert.sameValue(a[1], undefined); + assert.sameValue(a["1"], undefined); + ` + testScript1(TESTLIB+SCRIPT, _undefined, t) +} From 77c7d646b698015b2bb640885fbf350d96d95bb4 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Thu, 29 Jul 2021 15:00:03 +0100 Subject: [PATCH 09/19] Fixed accessor and property key function names. Fixes #314. Signed-off-by: Gabri (cherry picked from commit 67780b833a9d1f147e8eb3129e16fac7f010bec9) --- builtin_symbol.go | 2 -- object.go | 4 ++-- runtime_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++ value.go | 32 +++++++++++++++++++++---- vm.go | 10 ++++---- 5 files changed, 94 insertions(+), 14 deletions(-) diff --git a/builtin_symbol.go b/builtin_symbol.go index 9aa5e771..00ba2888 100644 --- a/builtin_symbol.go +++ b/builtin_symbol.go @@ -21,8 +21,6 @@ func (r *Runtime) builtin_symbol(call FunctionCall) Value { var desc valueString if arg := call.Argument(0); !IsUndefined(arg) { desc = arg.toString() - } else { - desc = stringEmpty } return newSymbol(desc) } diff --git a/object.go b/object.go index 393ad01c..153ce47d 100644 --- a/object.go +++ b/object.go @@ -483,7 +483,7 @@ func (o *baseObject) deleteIdx(idx valueInt, throw bool) bool { func (o *baseObject) deleteSym(s *Symbol, throw bool) bool { if o.symValues != nil { if val := o.symValues.get(s); val != nil { - if !o.checkDelete(s.desc.string(), val, throw) { + if !o.checkDelete(s.descriptiveString().string(), val, throw) { return false } o.symValues.remove(s) @@ -828,7 +828,7 @@ func (o *baseObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, t if o.symValues != nil { existingVal = o.symValues.get(s) } - if v, ok := o._defineOwnProperty(s.desc.string(), existingVal, descr, throw); ok { + if v, ok := o._defineOwnProperty(s.descriptiveString().string(), existingVal, descr, throw); ok { if o.symValues == nil { o.symValues = newOrderedMap(nil) } diff --git a/runtime_test.go b/runtime_test.go index e754bcce..9699b7da 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -2282,6 +2282,66 @@ func TestDestructSymbol(t *testing.T) { testScript1(TESTLIBX+SCRIPT, _undefined, t) } +func TestAccessorFuncName(t *testing.T) { + const SCRIPT = ` + const namedSym = Symbol('test262'); + const emptyStrSym = Symbol(""); + const anonSym = Symbol(); + + const o = { + get id() {}, + get [anonSym]() {}, + get [namedSym]() {}, + get [emptyStrSym]() {}, + set id(v) {}, + set [anonSym](v) {}, + set [namedSym](v) {}, + set [emptyStrSym](v) {} + }; + + let prop; + prop = Object.getOwnPropertyDescriptor(o, 'id'); + assert.sameValue(prop.get.name, 'get id'); + assert.sameValue(prop.set.name, 'set id'); + + prop = Object.getOwnPropertyDescriptor(o, anonSym); + assert.sameValue(prop.get.name, 'get '); + assert.sameValue(prop.set.name, 'set '); + + prop = Object.getOwnPropertyDescriptor(o, emptyStrSym); + assert.sameValue(prop.get.name, 'get []'); + assert.sameValue(prop.set.name, 'set []'); + + prop = Object.getOwnPropertyDescriptor(o, namedSym); + assert.sameValue(prop.get.name, 'get [test262]'); + assert.sameValue(prop.set.name, 'set [test262]'); + ` + testScript1(TESTLIB+SCRIPT, _undefined, t) +} + +func TestCoverFuncName(t *testing.T) { + const SCRIPT = ` + var namedSym = Symbol(''); + var anonSym = Symbol(); + var o; + + o = { + xId: (0, function() {}), + id: (function() {}), + id1: function x() {}, + [anonSym]: (function() {}), + [namedSym]: (function() {}) + }; + + assert(o.xId.name !== 'xId'); + assert.sameValue(o.id1.name, 'x'); + assert.sameValue(o.id.name, 'id', 'via IdentifierName'); + assert.sameValue(o[anonSym].name, '', 'via anonymous Symbol'); + assert.sameValue(o[namedSym].name, '[]', 'via Symbol'); + ` + testScript1(TESTLIB+SCRIPT, _undefined, t) +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) diff --git a/value.go b/value.go index 1533bd1e..f92cbf34 100644 --- a/value.go +++ b/value.go @@ -1511,11 +1511,17 @@ func (s *Symbol) ToString() Value { } func (s *Symbol) String() string { - return s.desc.String() + if s.desc != nil { + return s.desc.String() + } + return "" } func (s *Symbol) string() unistring.String { - return s.desc.string() + if s.desc != nil { + return s.desc.string() + } + return "" } func (s *Symbol) ToFloat() float64 { @@ -1597,10 +1603,26 @@ func NewSymbol(s string) *Symbol { } func (s *Symbol) descriptiveString() valueString { - if s.desc == nil { - return stringEmpty + desc := s.desc + if desc == nil { + desc = stringEmpty + } + return asciiString("Symbol(").concat(desc).concat(asciiString(")")) +} + +func funcName(prefix string, n Value) valueString { + var b valueStringBuilder + b.WriteString(asciiString(prefix)) + if sym, ok := n.(*Symbol); ok { + if sym.desc != nil { + b.WriteRune('[') + b.WriteString(sym.desc) + b.WriteRune(']') + } + } else { + b.WriteString(n.toString()) } - return asciiString("Symbol(").concat(s.desc).concat(asciiString(")")) + return b.String() } func init() { diff --git a/vm.go b/vm.go index 11c7153e..e181c2df 100644 --- a/vm.go +++ b/vm.go @@ -1445,7 +1445,7 @@ func (_setElem1Named) exec(vm *vm) { propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ - Value: propName, + Value: funcName("", propName), Configurable: FLAG_TRUE, }, true) obj.setOwn(propName, val, true) @@ -1642,7 +1642,7 @@ func (s setPropGetter) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-2]) val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ - Value: asciiString("get ").concat(stringValueFromRaw(val.string())), + Value: asciiString("get ").concat(stringValueFromRaw(unistring.String(s))), Configurable: FLAG_TRUE, }, true) @@ -1665,7 +1665,7 @@ func (s setPropSetter) exec(vm *vm) { val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ - Value: asciiString("set ").concat(stringValueFromRaw(val.string())), + Value: asciiString("set ").concat(stringValueFromRaw(unistring.String(s))), Configurable: FLAG_TRUE, }, true) @@ -1690,7 +1690,7 @@ func (s _setPropGetter1) exec(vm *vm) { propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ - Value: asciiString("get ").concat(stringValueFromRaw(val.string())), + Value: funcName("get ", propName), Configurable: FLAG_TRUE, }, true) @@ -1716,7 +1716,7 @@ func (s _setPropSetter1) exec(vm *vm) { val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ - Value: asciiString("set ").concat(stringValueFromRaw(val.string())), + Value: funcName("set ", propName), Configurable: FLAG_TRUE, }, true) From 3c275b34e0c5b8454e1c2ddfac4a82c048829dfa Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 2 Aug 2021 12:49:14 +0100 Subject: [PATCH 10/19] Fixed possible panic when sorting non-standard arrays. Signed-off-by: Gabri (cherry picked from commit b54ccd1613219b5262594f7aaa4f72bb41f84ff6) --- builtin_array.go | 36 ++++++++++++++++++++++++++++++------ builtin_arrray_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/builtin_array.go b/builtin_array.go index 95b040db..cf3762ba 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -364,13 +364,37 @@ func (r *Runtime) arrayproto_sort(call FunctionCall) Value { } } - ctx := arraySortCtx{ - ctx: call.ctx, - obj: o.self, - compare: compareFn, - } + if r.checkStdArrayObj(o) != nil { + ctx := arraySortCtx{ + ctx: call.ctx, + obj: o.self, + compare: compareFn, + } - sort.Stable(&ctx) + sort.Stable(&ctx) + } else { + length := toLength(o.self.getStr("length", nil)) + a := make([]Value, 0, length) + for i := int64(0); i < length; i++ { + idx := valueInt(i) + if o.self.hasPropertyIdx(idx) { + a = append(a, o.self.getIdx(idx, nil)) + } + } + ar := r.newArrayValues(a) + ctx := arraySortCtx{ + obj: ar.self, + compare: compareFn, + } + + sort.Stable(&ctx) + for i := 0; i < len(a); i++ { + o.self.setOwnIdx(valueInt(i), a[i], true) + } + for i := int64(len(a)); i < length; i++ { + o.self.deleteIdx(valueInt(i), true) + } + } return o } diff --git a/builtin_arrray_test.go b/builtin_arrray_test.go index 3b4a50ea..9d922635 100644 --- a/builtin_arrray_test.go +++ b/builtin_arrray_test.go @@ -231,6 +231,38 @@ func TestArraySort(t *testing.T) { testScript1(TESTLIB+SCRIPT, _undefined, t) } +func TestArraySortNonStdArray(t *testing.T) { + const SCRIPT = ` + const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd']; + + Object.defineProperty(array, '2', { + get() { + array.pop(); + array.pop(); + return this.foo; + }, + set(v) { + this.foo = v; + } + }); + + array.sort(); + + assert.sameValue(array[0], 'b'); + assert.sameValue(array[1], 'c'); + assert.sameValue(array[3], undefined); + assert.sameValue(array[4], undefined); + assert.sameValue('5' in array, false); + assert.sameValue(array.hasOwnProperty('5'), false); + assert.sameValue(array.length, 6); + assert.sameValue(array.foo, undefined); + + assert.sameValue(array[2], undefined); + assert.sameValue(array.length, 4); + ` + testScript1(TESTLIB+SCRIPT, _undefined, t) +} + func TestArrayConcat(t *testing.T) { const SCRIPT = ` var concat = Array.prototype.concat; From 1110c7a8df3943c84b4f064e9e477e2aab2ab32d Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 2 Aug 2021 16:09:13 +0100 Subject: [PATCH 11/19] Added nil-safety checks for values returned by get*() methods Signed-off-by: Gabri (cherry picked from commit ccd9128a1c5331cd59bd2093fd65a2df02e2a2c9) --- array.go | 2 +- builtin_array.go | 14 +++++++------- builtin_function.go | 4 ++-- builtin_json.go | 16 +++++----------- builtin_object.go | 4 ++-- builtin_regexp.go | 2 +- builtin_string.go | 2 +- builtin_typedarrays.go | 2 +- 8 files changed, 20 insertions(+), 26 deletions(-) diff --git a/array.go b/array.go index 480bb704..02768f0a 100644 --- a/array.go +++ b/array.go @@ -31,7 +31,7 @@ func (ai *arrayIterObject) next() Value { if ai.kind == iterationKindKey { return ai.val.runtime.createIterResultObject(idxVal, false) } - elementValue := ai.obj.self.getIdx(idxVal, nil) + elementValue := nilSafe(ai.obj.self.getIdx(idxVal, nil)) var result Value if ai.kind == iterationKindValue { result = elementValue diff --git a/builtin_array.go b/builtin_array.go index cf3762ba..1464d2e1 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -378,7 +378,7 @@ func (r *Runtime) arrayproto_sort(call FunctionCall) Value { for i := int64(0); i < length; i++ { idx := valueInt(i) if o.self.hasPropertyIdx(idx) { - a = append(a, o.self.getIdx(idx, nil)) + a = append(a, nilSafe(o.self.getIdx(idx, nil))) } } ar := r.newArrayValues(a) @@ -454,7 +454,7 @@ func (r *Runtime) arrayproto_splice(call FunctionCall) Value { for k := int64(0); k < actualDeleteCount; k++ { from := valueInt(k + actualStart) if o.self.hasPropertyIdx(from) { - createDataPropertyOrThrow(a, valueInt(k), o.self.getIdx(from, nil)) + createDataPropertyOrThrow(a, valueInt(k), nilSafe(o.self.getIdx(from, nil))) } } @@ -463,7 +463,7 @@ func (r *Runtime) arrayproto_splice(call FunctionCall) Value { from := valueInt(k + actualDeleteCount) to := valueInt(k + itemCount) if o.self.hasPropertyIdx(from) { - o.self.setOwnIdx(to, o.self.getIdx(from, nil), true) + o.self.setOwnIdx(to, nilSafe(o.self.getIdx(from, nil)), true) } else { o.self.deleteIdx(to, true) } @@ -477,7 +477,7 @@ func (r *Runtime) arrayproto_splice(call FunctionCall) Value { 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) + o.self.setOwnIdx(to, nilSafe(o.self.getIdx(from, nil)), true) } else { o.self.deleteIdx(to, true) } @@ -518,7 +518,7 @@ func (r *Runtime) arrayproto_unshift(call FunctionCall) Value { from := valueInt(k) to := valueInt(k + argCount) if o.self.hasPropertyIdx(from) { - o.self.setOwnIdx(to, o.self.getIdx(from, nil), true) + o.self.setOwnIdx(to, nilSafe(o.self.getIdx(from, nil)), true) } else { o.self.deleteIdx(to, true) } @@ -1012,7 +1012,7 @@ func (r *Runtime) arrayproto_copyWithin(call FunctionCall) Value { } for count > 0 { if o.self.hasPropertyIdx(valueInt(from)) { - o.self.setOwnIdx(valueInt(to), o.self.getIdx(valueInt(from), nil), true) + o.self.setOwnIdx(valueInt(to), nilSafe(o.self.getIdx(valueInt(from), nil)), true) } else { o.self.deleteIdx(valueInt(to), true) } @@ -1111,7 +1111,7 @@ func (r *Runtime) flattenIntoArray(ctx context.Context, target, source *Object, for sourceIndex < sourceLen { p := intToValue(sourceIndex) if source.hasProperty(p.toString()) { - element := source.get(p, source) + element := nilSafe(source.get(p, source)) if mapperFunction != nil { element = mapperFunction(FunctionCall{ ctx: ctx, diff --git a/builtin_function.go b/builtin_function.go index 956410f5..8e60b53f 100644 --- a/builtin_function.go +++ b/builtin_function.go @@ -82,7 +82,7 @@ func (r *Runtime) createListFromArrayLike(a Value) []Value { 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)) + res = append(res, nilSafe(o.self.getIdx(valueInt(k), nil))) } return res } @@ -159,7 +159,7 @@ func (r *Runtime) functionproto_bind(call FunctionCall) Value { fcall := r.toCallable(call.This) construct := obj.self.assertConstructor() - l := int(toUint32(obj.self.getStr("length", nil))) + l := int(toUint32(nilSafe(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 1fff3b71..75e705c9 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -132,14 +132,11 @@ func (r *Runtime) builtinJSON_decodeArray(d *json.Decoder) (*Object, error) { } func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holder *Object, name Value) Value { - value := holder.get(name, nil) - if value == nil { - value = _undefined - } + value := nilSafe(holder.get(name, nil)) if object, ok := value.(*Object); ok { if isArray(object) { - length := object.self.getStr("length", nil).ToInteger() + length := toLength(object.self.getStr("length", nil)) for index := int64(0); index < length; index++ { name := intToValue(index) value := r.builtinJSON_reviveWalk(reviver, object, name) @@ -187,7 +184,7 @@ func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value { replacer, _ := call.Argument(1).(*Object) if replacer != nil { if isArray(replacer) { - length := replacer.self.getStr("length", nil).ToInteger() + length := toLength(replacer.self.getStr("length", nil)) seen := map[string]bool{} propertyList := make([]Value, length) length = 0 @@ -268,10 +265,7 @@ func (ctx *_builtinJSON_stringifyContext) do(v Value) bool { } func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { - value := holder.get(key, nil) - if value == nil { - value = _undefined - } + value := nilSafe(holder.get(key, nil)) if object, ok := value.(*Object); ok { if toJSON, ok := object.self.getStr("toJSON", nil).(*Object); ok { @@ -384,7 +378,7 @@ func (ctx *_builtinJSON_stringifyContext) ja(array *Object) { stepback = ctx.indent ctx.indent += ctx.gap } - length := array.self.getStr("length", nil).ToInteger() + length := toLength(array.self.getStr("length", nil)) if length == 0 { ctx.buf.WriteString("[]") return diff --git a/builtin_object.go b/builtin_object.go index 58d2d44d..ed2d0717 100644 --- a/builtin_object.go +++ b/builtin_object.go @@ -392,7 +392,7 @@ func (r *Runtime) object_entries(call FunctionCall) Value { } for item, next := iter.next(); next != nil; item, next = next() { - v := obj.self.getStr(item.name, nil) + v := nilSafe(obj.self.getStr(item.name, nil)) values = append(values, r.newArrayValues([]Value{stringValueFromRaw(item.name), v})) } @@ -408,7 +408,7 @@ func (r *Runtime) object_values(call FunctionCall) Value { } for item, next := iter.next(); next != nil; item, next = next() { - values = append(values, obj.self.getStr(item.name, nil)) + values = append(values, nilSafe(obj.self.getStr(item.name, nil))) } return r.newArrayValues(values) diff --git a/builtin_regexp.go b/builtin_regexp.go index 9862094e..2fd926c4 100644 --- a/builtin_regexp.go +++ b/builtin_regexp.go @@ -909,7 +909,7 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString } numberOfCaptures := max(toLength(z.self.getStr("length", nil))-1, 0) for i := int64(1); i <= numberOfCaptures; i++ { - a = append(a, z.self.getIdx(valueInt(i), nil)) + a = append(a, nilSafe(z.self.getIdx(valueInt(i), nil))) if int64(len(a)) == lim { return r.newArrayValues(a) } diff --git a/builtin_string.go b/builtin_string.go index 1333a21a..97e54dee 100644 --- a/builtin_string.go +++ b/builtin_string.go @@ -385,7 +385,7 @@ func (r *Runtime) stringproto_matchAll(call FunctionCall) Value { if regexp != _undefined && regexp != _null { if isRegexp(regexp) { if o, ok := regexp.(*Object); ok { - flags := o.self.getStr("flags", nil) + flags := nilSafe(o.self.getStr("flags", nil)) r.checkObjectCoercible(flags) if !strings.Contains(flags.toString().String(), "g") { panic(r.NewTypeError("RegExp doesn't have global flag set")) diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 3c76f4d4..1edd2c4e 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -1056,7 +1056,7 @@ func (r *Runtime) typedArrayProto_values(call FunctionCall) Value { func (r *Runtime) typedArrayProto_toStringTag(call FunctionCall) Value { if obj, ok := call.This.(*Object); ok { if ta, ok := obj.self.(*typedArrayObject); ok { - return ta.defaultCtor.self.getStr("name", nil) + return nilSafe(ta.defaultCtor.self.getStr("name", nil)) } } From 9b4f9917c117f2b8b065957dccf8cbf02e4a3e32 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 2 Aug 2021 22:26:25 +0100 Subject: [PATCH 12/19] Fixed panics in parser on some invalid inputs. Fixes #318. Signed-off-by: Gabri (cherry picked from commit 4fb1f700b866bcac41ec19a067359eadb2956bb8) --- parser/expression.go | 24 ++++++++++++------------ parser/parser_test.go | 1 + 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/parser/expression.go b/parser/expression.go index 21ac15d8..c96e3572 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -931,14 +931,14 @@ func (self *_parser) checkComma(from, to file.Idx) { } } -func (self *_parser) reinterpretAsArrayAssignmentPattern(left *ast.ArrayLiteral) *ast.ArrayPattern { +func (self *_parser) reinterpretAsArrayAssignmentPattern(left *ast.ArrayLiteral) ast.Expression { value := left.Value var rest ast.Expression for i, item := range value { if spread, ok := item.(*ast.SpreadElement); ok { if i != len(value)-1 { self.error(item.Idx0(), "Rest element must be last element") - return nil + return &ast.BadExpression{From: left.Idx0(), To: left.Idx1()} } self.checkComma(spread.Expression.Idx1(), left.RightBracket) rest = self.reinterpretAsDestructAssignTarget(spread.Expression) @@ -965,14 +965,14 @@ func (self *_parser) reinterpretArrayAssignPatternAsBinding(pattern *ast.ArrayPa return pattern } -func (self *_parser) reinterpretAsArrayBindingPattern(left *ast.ArrayLiteral) *ast.ArrayPattern { +func (self *_parser) reinterpretAsArrayBindingPattern(left *ast.ArrayLiteral) ast.BindingTarget { value := left.Value var rest ast.Expression for i, item := range value { if spread, ok := item.(*ast.SpreadElement); ok { if i != len(value)-1 { self.error(item.Idx0(), "Rest element must be last element") - return nil + return &ast.BadExpression{From: left.Idx0(), To: left.Idx1()} } self.checkComma(spread.Expression.Idx1(), left.RightBracket) rest = self.reinterpretAsDestructBindingTarget(spread.Expression) @@ -989,11 +989,11 @@ func (self *_parser) reinterpretAsArrayBindingPattern(left *ast.ArrayLiteral) *a } } -func (self *_parser) parseArrayBindingPattern() *ast.ArrayPattern { +func (self *_parser) parseArrayBindingPattern() ast.BindingTarget { return self.reinterpretAsArrayBindingPattern(self.parseArrayLiteral()) } -func (self *_parser) parseObjectBindingPattern() *ast.ObjectPattern { +func (self *_parser) parseObjectBindingPattern() ast.BindingTarget { return self.reinterpretAsObjectBindingPattern(self.parseObjectLiteral()) } @@ -1009,7 +1009,7 @@ func (self *_parser) reinterpretArrayObjectPatternAsBinding(pattern *ast.ObjectP return pattern } -func (self *_parser) reinterpretAsObjectBindingPattern(expr *ast.ObjectLiteral) *ast.ObjectPattern { +func (self *_parser) reinterpretAsObjectBindingPattern(expr *ast.ObjectLiteral) ast.BindingTarget { var rest ast.Expression value := expr.Value for i, prop := range value { @@ -1025,7 +1025,7 @@ func (self *_parser) reinterpretAsObjectBindingPattern(expr *ast.ObjectLiteral) case *ast.SpreadElement: if i != len(expr.Value)-1 { self.error(prop.Idx0(), "Rest element must be last element") - return nil + return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()} } // TODO make sure there is no trailing comma rest = self.reinterpretAsBindingRestElement(prop.Expression) @@ -1034,7 +1034,7 @@ func (self *_parser) reinterpretAsObjectBindingPattern(expr *ast.ObjectLiteral) } if !ok { self.error(prop.Idx0(), "Invalid destructuring binding target") - return nil + return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()} } } return &ast.ObjectPattern{ @@ -1045,7 +1045,7 @@ func (self *_parser) reinterpretAsObjectBindingPattern(expr *ast.ObjectLiteral) } } -func (self *_parser) reinterpretAsObjectAssignmentPattern(l *ast.ObjectLiteral) *ast.ObjectPattern { +func (self *_parser) reinterpretAsObjectAssignmentPattern(l *ast.ObjectLiteral) ast.Expression { var rest ast.Expression value := l.Value for i, prop := range value { @@ -1061,7 +1061,7 @@ func (self *_parser) reinterpretAsObjectAssignmentPattern(l *ast.ObjectLiteral) case *ast.SpreadElement: if i != len(l.Value)-1 { self.error(prop.Idx0(), "Rest element must be last element") - return nil + return &ast.BadExpression{From: l.Idx0(), To: l.Idx1()} } // TODO make sure there is no trailing comma rest = prop.Expression @@ -1070,7 +1070,7 @@ func (self *_parser) reinterpretAsObjectAssignmentPattern(l *ast.ObjectLiteral) } if !ok { self.error(prop.Idx0(), "Invalid destructuring assignment target") - return nil + return &ast.BadExpression{From: l.Idx0(), To: l.Idx1()} } } return &ast.ObjectPattern{ diff --git a/parser/parser_test.go b/parser/parser_test.go index 99f47944..c55db46e 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -486,6 +486,7 @@ func TestParserErr(t *testing.T) { test(`var yield;`, nil) } test(`0, { get a(param = null) {} };`, "(anonymous): Line 1:11 Getter must not have any formal parameters.") + test(`let{f(`, "(anonymous): Line 1:7 Unexpected end of input") }) } From 44fbc769f668c661a07ef0aa11bf2e6b0fa303a3 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Wed, 4 Aug 2021 11:13:10 +0100 Subject: [PATCH 13/19] Arrow function (#319) * Implemented arrow functions. See #304. * Define the name property for anonymous functions (including arrow functions) * Treat "arguments" as a lexical binding to match the latest specification Signed-off-by: Gabri (cherry picked from commit 9981850c251f5726c476d408a8371c8a6b393c23) --- ast/node.go | 26 ++++++++ builtin_function.go | 4 ++ compiler.go | 6 +- compiler_expr.go | 124 +++++++++++++++++++++++++------------ compiler_test.go | 17 ++++++ func.go | 48 +++++++++++---- memory_test.go | 2 +- parser/expression.go | 138 +++++++++++++++++++++++++++++++++++++++--- parser/lexer.go | 33 +++++++++- parser/parser_test.go | 4 ++ parser/statement.go | 32 ++++++---- runtime.go | 45 ++++++++++++-- runtime_test.go | 8 +++ tc39_test.go | 94 +++++++++++----------------- vm.go | 31 ++++++++-- 15 files changed, 468 insertions(+), 144 deletions(-) diff --git a/ast/node.go b/ast/node.go index 9ae2836c..a64b61e6 100644 --- a/ast/node.go +++ b/ast/node.go @@ -128,6 +128,23 @@ type ( DeclarationList []*VariableDeclaration } + ConciseBody interface { + Node + _conciseBody() + } + + ExpressionBody struct { + Expression Expression + } + + ArrowFunctionLiteral struct { + Start file.Idx + ParameterList *ParameterList + Body ConciseBody + Source string + DeclarationList []*VariableDeclaration + } + Identifier struct { Name unistring.String Idx file.Idx @@ -238,6 +255,7 @@ func (*CallExpression) _expressionNode() {} func (*ConditionalExpression) _expressionNode() {} func (*DotExpression) _expressionNode() {} func (*FunctionLiteral) _expressionNode() {} +func (*ArrowFunctionLiteral) _expressionNode() {} func (*Identifier) _expressionNode() {} func (*NewExpression) _expressionNode() {} func (*NullLiteral) _expressionNode() {} @@ -497,6 +515,9 @@ func (*SpreadElement) _property() {} func (*Identifier) _bindingTarget() {} +func (*BlockStatement) _conciseBody() {} +func (*ExpressionBody) _conciseBody() {} + // ==== // // Node // // ==== // @@ -525,6 +546,7 @@ func (self *CallExpression) Idx0() file.Idx { return self.Callee.Idx0() } func (self *ConditionalExpression) Idx0() file.Idx { return self.Test.Idx0() } func (self *DotExpression) Idx0() file.Idx { return self.Left.Idx0() } func (self *FunctionLiteral) Idx0() file.Idx { return self.Function } +func (self *ArrowFunctionLiteral) Idx0() file.Idx { return self.Start } func (self *Identifier) Idx0() file.Idx { return self.Idx } func (self *NewExpression) Idx0() file.Idx { return self.New } func (self *NullLiteral) Idx0() file.Idx { return self.Idx } @@ -566,6 +588,7 @@ func (self *Binding) Idx0() file.Idx { return self.Target.Idx0() } func (self *ForLoopInitializerVarDeclList) Idx0() file.Idx { return self.List[0].Idx0() } func (self *PropertyShort) Idx0() file.Idx { return self.Name.Idx } func (self *PropertyKeyed) Idx0() file.Idx { return self.Key.Idx0() } +func (self *ExpressionBody) Idx0() file.Idx { return self.Expression.Idx0() } // ==== // // Idx1 // @@ -582,6 +605,7 @@ func (self *CallExpression) Idx1() file.Idx { return self.RightParenthesi func (self *ConditionalExpression) Idx1() file.Idx { return self.Test.Idx1() } func (self *DotExpression) Idx1() file.Idx { return self.Identifier.Idx1() } func (self *FunctionLiteral) Idx1() file.Idx { return self.Body.Idx1() } +func (self *ArrowFunctionLiteral) Idx1() file.Idx { return self.Body.Idx1() } func (self *Identifier) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Name)) } func (self *NewExpression) Idx1() file.Idx { return self.RightParenthesis + 1 } func (self *NullLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + 4) } // "null" @@ -656,3 +680,5 @@ func (self *PropertyShort) Idx1() file.Idx { } func (self *PropertyKeyed) Idx1() file.Idx { return self.Value.Idx1() } + +func (self *ExpressionBody) Idx1() file.Idx { return self.Expression.Idx1() } diff --git a/builtin_function.go b/builtin_function.go index 8e60b53f..b2700cda 100644 --- a/builtin_function.go +++ b/builtin_function.go @@ -34,6 +34,8 @@ repeat: switch f := obj.self.(type) { case *funcObject: return newStringValue(f.src) + case *arrowFuncObject: + return newStringValue(f.src) case *nativeFuncObject: return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString())) case *boundFuncObject: @@ -47,6 +49,8 @@ repeat: switch c := f.target.self.(type) { case *funcObject: name = c.src + case *arrowFuncObject: + name = c.src case *nativeFuncObject: name = nilSafe(f.getStr("name", nil)).toString().String() case *boundFuncObject: diff --git a/compiler.go b/compiler.go index 26cd2a8d..ed97b62d 100644 --- a/compiler.go +++ b/compiler.go @@ -248,6 +248,8 @@ type scope struct { // is a function or a top-level lexical environment function bool + // is an arrow function's top-level lexical environment (functions only) + arrow bool // is a variable environment, i.e. the target for dynamically created var bindings variable bool // a function scope that has at least one direct eval() and non-strict, so the variables can be added dynamically @@ -365,6 +367,8 @@ func (p *Program) _dumpCode(indent string, logger func(format string, args ...in logger("%s %d: %T(%v)", indent, pc, ins, ins) if f, ok := ins.(*newFunc); ok { f.prg._dumpCode(indent+">", logger) + } else if f, ok := ins.(*newArrowFunc); ok { + f.prg._dumpCode(indent+">", logger) } } } @@ -416,7 +420,7 @@ func (s *scope) lookupName(name unistring.String) (binding *binding, noDynamics return } } - if name == "arguments" && curScope.function { + if name == "arguments" && curScope.function && !curScope.arrow { curScope.argsNeeded = true binding, _ = curScope.bindName(name) return diff --git a/compiler_expr.go b/compiler_expr.go index fd529a02..128c6feb 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -115,10 +115,15 @@ type compiledIdentifierExpr struct { type compiledFunctionLiteral struct { baseCompiledExpr - expr *ast.FunctionLiteral - lhsName unistring.String - strict *ast.StringLiteral - isExpr bool + name *ast.Identifier + parameterList *ast.ParameterList + body []ast.Statement + source string + declarationList []*ast.VariableDeclaration + lhsName unistring.String + strict *ast.StringLiteral + isExpr bool + isArrow bool } type compiledBracketExpr struct { @@ -225,6 +230,8 @@ func (c *compiler) compileExpression(v ast.Expression) compiledExpr { return c.compileConditionalExpression(v) case *ast.FunctionLiteral: return c.compileFunctionLiteral(v, true) + case *ast.ArrowFunctionLiteral: + return c.compileArrowFunctionLiteral(v) case *ast.DotExpression: r := &compiledDotExpr{ left: c.compileExpression(v.Left), @@ -740,7 +747,7 @@ func (e *compiledAssignExpr) emitGetter(putOnStack bool) { switch e.operator { case token.ASSIGN: if fn, ok := e.right.(*compiledFunctionLiteral); ok { - if fn.expr.Name == nil { + if fn.name == nil { if id, ok := e.left.(*compiledIdentifierExpr); ok { fn.lhsName = id.name } @@ -847,11 +854,13 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { src: e.c.p.src, } e.c.newScope() - e.c.scope.function = true + s := e.c.scope + s.function = true + s.arrow = e.isArrow var name unistring.String - if e.expr.Name != nil { - name = e.expr.Name.Name + if e.name != nil { + name = e.name.Name } else { name = e.lhsName } @@ -868,8 +877,8 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { typ: blockScope, } - if !e.c.scope.strict { - e.c.scope.strict = e.strict != nil + if !s.strict { + s.strict = e.strict != nil } hasPatterns := false @@ -877,12 +886,12 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { firstDupIdx := -1 length := 0 - if e.expr.ParameterList.Rest != nil { + if e.parameterList.Rest != nil { hasPatterns = true // strictly speaking not, but we need to activate all the checks } // First, make sure that the first bindings correspond to the formal parameters - for _, item := range e.expr.ParameterList.List { + for _, item := range e.parameterList.List { switch tgt := item.Target.(type) { case *ast.Identifier: offset := int(tgt.Idx) - 1 @@ -892,7 +901,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { } b.isArg = true case ast.Pattern: - b := e.c.scope.addBinding(int(item.Idx0()) - 1) + b := s.addBinding(int(item.Idx0()) - 1) b.isArg = true hasPatterns = true default: @@ -902,7 +911,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { if item.Initializer != nil { hasInits = true } - if hasPatterns || hasInits { + if hasPatterns || hasInits || e.isArrow { if firstDupIdx >= 0 { e.c.throwSyntaxError(firstDupIdx, "Duplicate parameter name not allowed in this context") return @@ -919,7 +928,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { // create pattern bindings if hasPatterns { - for _, item := range e.expr.ParameterList.List { + for _, item := range e.parameterList.List { switch tgt := item.Target.(type) { case *ast.Identifier: // we already created those in the previous loop, skipping @@ -927,24 +936,23 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { e.c.compileParameterPatternBinding(tgt) } } - if rest := e.expr.ParameterList.Rest; rest != nil { + if rest := e.parameterList.Rest; rest != nil { e.c.compileParameterPatternBinding(rest) } } - paramsCount := len(e.expr.ParameterList.List) + paramsCount := len(e.parameterList.List) - e.c.scope.numArgs = paramsCount - body := e.expr.Body.List + s.numArgs = paramsCount + body := e.body funcs := e.c.extractFunctions(body) - s := e.c.scope var calleeBinding *binding preambleLen := 4 // enter, boxThis, createArgs, set e.c.p.code = make([]instruction, preambleLen, 8) if hasPatterns || hasInits { - if e.isExpr && e.expr.Name != nil { - if b, created := s.bindNameLexical(e.expr.Name.Name, false, 0); created { + if e.isExpr && e.name != nil { + if b, created := s.bindNameLexical(e.name.Name, false, 0); created { b.isConst = true calleeBinding = b } @@ -960,8 +968,8 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { enterFunc2Mark := -1 if hasPatterns || hasInits { - if e.isExpr && e.expr.Name != nil { - if b, created := s.bindNameLexical(e.expr.Name.Name, false, 0); created { + if e.isExpr && e.name != nil { + if b, created := s.bindNameLexical(e.name.Name, false, 0); created { b.isConst = true calleeBinding = b } @@ -970,7 +978,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { e.c.emit(loadCallee) calleeBinding.emitInit() } - for i, item := range e.expr.ParameterList.List { + for i, item := range e.parameterList.List { if pattern, ok := item.Target.(ast.Pattern); ok { i := i e.c.compilePatternInitExpr(func() { @@ -1009,7 +1017,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { } } } - if rest := e.expr.ParameterList.Rest; rest != nil { + if rest := e.parameterList.Rest; rest != nil { e.c.emitAssign(rest, e.c.compileEmitterExpr( func() { emitArgsRestMark = len(e.c.p.code) @@ -1032,7 +1040,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { varScope.variable = true enterFunc2Mark = len(e.c.p.code) e.c.emit(nil) - e.c.compileDeclList(e.expr.DeclarationList, true) + e.c.compileDeclList(e.declarationList, false) e.c.createFunctionBindings(funcs) e.c.compileLexicalDeclarationsFuncBody(body, calleeBinding) for _, b := range varScope.bindings { @@ -1049,11 +1057,11 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { for _, b := range s.bindings[:paramsCount] { b.isVar = true } - e.c.compileDeclList(e.expr.DeclarationList, true) + e.c.compileDeclList(e.declarationList, true) e.c.createFunctionBindings(funcs) e.c.compileLexicalDeclarations(body, true) - if e.isExpr && e.expr.Name != nil { - if b, created := s.bindNameLexical(e.expr.Name.Name, false, 0); created { + if e.isExpr && e.name != nil { + if b, created := s.bindNameLexical(e.name.Name, false, 0); created { b.isConst = true calleeBinding = b } @@ -1095,7 +1103,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { if s.strict { b.isConst = true } else { - b.isVar = true + b.isVar = e.c.scope.function } pos := preambleLen - 2 delta += 2 @@ -1192,7 +1200,11 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { } e.c.popScope() e.c.p = savedPrg - e.c.emit(&newFunc{prg: p, length: uint32(length), name: name, srcStart: uint32(e.expr.Idx0() - 1), srcEnd: uint32(e.expr.Idx1() - 1), strict: strict}) + if e.isArrow { + e.c.emit(&newArrowFunc{newFunc: newFunc{prg: p, length: uint32(length), name: name, source: e.source, strict: strict}}) + } else { + e.c.emit(&newFunc{prg: p, length: uint32(length), name: name, source: e.source, strict: strict}) + } if !putOnStack { e.c.emit(pop) } @@ -1204,9 +1216,42 @@ func (c *compiler) compileFunctionLiteral(v *ast.FunctionLiteral, isExpr bool) * c.checkIdentifierLName(v.Name.Name, int(v.Name.Idx)-1) } r := &compiledFunctionLiteral{ - expr: v, - isExpr: isExpr, - strict: strictBody, + name: v.Name, + parameterList: v.ParameterList, + body: v.Body.List, + source: v.Source, + declarationList: v.DeclarationList, + isExpr: isExpr, + strict: strictBody, + } + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) compileArrowFunctionLiteral(v *ast.ArrowFunctionLiteral) *compiledFunctionLiteral { + var strictBody *ast.StringLiteral + var body []ast.Statement + switch b := v.Body.(type) { + case *ast.BlockStatement: + strictBody = c.isStrictStatement(b) + body = b.List + case *ast.ExpressionBody: + body = []ast.Statement{ + &ast.ReturnStatement{ + Argument: b.Expression, + }, + } + default: + c.throwSyntaxError(int(b.Idx0())-1, "Unsupported ConciseBody type: %T", b) + } + r := &compiledFunctionLiteral{ + parameterList: v.ParameterList, + body: body, + source: v.Source, + declarationList: v.DeclarationList, + isExpr: true, + isArrow: true, + strict: strictBody, } r.init(c, v.Idx0()) return r @@ -1220,7 +1265,9 @@ func (e *compiledThisExpr) emitGetter(putOnStack bool) { } if scope != nil { - scope.thisNeeded = true + if !scope.arrow { + scope.thisNeeded = true + } e.c.emit(loadStack(0)) } else { e.c.emit(loadGlobalObject) @@ -1684,12 +1731,11 @@ func (e *compiledObjectLiteral) emitGetter(putOnStack bool) { default: keyExpr.emitGetter(true) computed = true - //e.c.throwSyntaxError(e.offset, "non-literal properties in object literal are not supported yet") } valueExpr := e.c.compileExpression(prop.Value) var anonFn *compiledFunctionLiteral if fn, ok := valueExpr.(*compiledFunctionLiteral); ok { - if fn.expr.Name == nil { + if fn.name == nil { anonFn = fn fn.lhsName = key } @@ -1850,7 +1896,7 @@ func (e *compiledCallExpr) emitGetter(putOnStack bool) { if calleeName == "eval" { foundFunc, foundVar := false, false for sc := e.c.scope; sc != nil; sc = sc.outer { - if !foundFunc && sc.function { + if !foundFunc && sc.function && !sc.arrow { foundFunc = true sc.thisNeeded, sc.argsNeeded = true, true } diff --git a/compiler_test.go b/compiler_test.go index 52384463..4abb35b0 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -1693,6 +1693,23 @@ func TestArgumentsRedeclareInEval(t *testing.T) { testScript1(TESTLIB+SCRIPT, _undefined, t) } +func TestArgumentsRedeclareArrow(t *testing.T) { + const SCRIPT = ` + const oldArguments = globalThis.arguments; + let count = 0; + const f = (p = eval("var arguments = 'param'"), q = () => arguments) => { + var arguments = "local"; + assert.sameValue(arguments, "local", "arguments"); + assert.sameValue(q(), "param", "q"); + count++; + } + f(); + assert.sameValue(count, 1); + assert.sameValue(globalThis.arguments, oldArguments, "globalThis.arguments unchanged"); + ` + testScript1(TESTLIB+SCRIPT, _undefined, t) +} + func TestEvalParamWithDef(t *testing.T) { const SCRIPT = ` function f(param = 0) { diff --git a/func.go b/func.go index eadec134..9ba3adff 100644 --- a/func.go +++ b/func.go @@ -15,12 +15,23 @@ type baseFuncObject struct { lenProp valueProperty } -type funcObject struct { +type baseJsFuncObject struct { baseFuncObject - stash *stash - prg *Program - src string + stash *stash + prg *Program + src string + strict bool +} + +type funcObject struct { + baseJsFuncObject +} + +type arrowFuncObject struct { + baseJsFuncObject + this Value + newTarget Value } type nativeFuncObject struct { @@ -134,18 +145,18 @@ func (f *funcObject) Call(call FunctionCall) Value { return f.call(call, nil) } -func (f *funcObject) call(call FunctionCall, newTarget Value) Value { +func (f *arrowFuncObject) Call(call FunctionCall) Value { + return f._call(call, f.newTarget, f.this) +} + +func (f *baseJsFuncObject) _call(call FunctionCall, newTarget, this Value) Value { vm := f.val.runtime.vm pc := vm.pc vm.stack.expand(vm.sp + len(call.Arguments) + 1) vm.stack[vm.sp] = f.val vm.sp++ - if call.This != nil { - vm.stack[vm.sp] = call.This - } else { - vm.stack[vm.sp] = _undefined - } + vm.stack[vm.sp] = this vm.sp++ for _, arg := range call.Arguments { if arg != nil { @@ -168,6 +179,11 @@ func (f *funcObject) call(call FunctionCall, newTarget Value) Value { vm.pc = pc vm.halt = false return vm.pop() + +} + +func (f *funcObject) call(call FunctionCall, newTarget Value) Value { + return f._call(call, newTarget, nilSafe(call.This)) } func (f *funcObject) export(*objectExportCtx) interface{} { @@ -186,12 +202,18 @@ func (f *funcObject) assertConstructor() func(args []Value, newTarget *Object) * return f.construct } +func (f *arrowFuncObject) exportType() reflect.Type { + return reflect.TypeOf(f.Call) +} + +func (f *arrowFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + func (f *baseFuncObject) init(name unistring.String, length int) { f.baseObject.init() - if name != "" { - f._putProp("name", stringValueFromRaw(name), false, false, true) - } + f._putProp("name", stringValueFromRaw(name), false, false, true) f.lenProp.configurable = true f.lenProp.value = valueInt(length) diff --git a/memory_test.go b/memory_test.go index df3c7b49..7f477e2d 100644 --- a/memory_test.go +++ b/memory_test.go @@ -25,7 +25,7 @@ func (muc TestNativeMemUsageChecker) NativeMemUsage(value interface{}) (uint64, func TestMemCheck(t *testing.T) { // This is the sum of property names allocated at each new (empty) scope var emptyFunctionScopeOverhead uint64 = 8 - var functionStackOverhead uint64 = 30 + var functionStackOverhead uint64 = 34 for _, tc := range []struct { description string diff --git a/parser/expression.go b/parser/expression.go index c96e3572..c70ef1cc 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -85,10 +85,11 @@ func (self *_parser) parsePrimaryExpression() ast.Expression { case token.LEFT_BRACKET: return self.parseArrayLiteral() case token.LEFT_PARENTHESIS: - self.expect(token.LEFT_PARENTHESIS) + /*self.expect(token.LEFT_PARENTHESIS) expression := self.parseExpression() self.expect(token.RIGHT_PARENTHESIS) - return expression + return expression*/ + return self.parseParenthesisedExpression() case token.THIS: self.next() return &ast.ThisExpression{ @@ -103,6 +104,76 @@ func (self *_parser) parsePrimaryExpression() ast.Expression { return &ast.BadExpression{From: idx, To: self.idx} } +func (self *_parser) reinterpretSequenceAsArrowFuncParams(seq *ast.SequenceExpression) *ast.ParameterList { + firstRestIdx := -1 + params := make([]*ast.Binding, 0, len(seq.Sequence)) + for i, item := range seq.Sequence { + if _, ok := item.(*ast.SpreadElement); ok { + if firstRestIdx == -1 { + firstRestIdx = i + continue + } + } + if firstRestIdx != -1 { + self.error(seq.Sequence[firstRestIdx].Idx0(), "Rest parameter must be last formal parameter") + return &ast.ParameterList{} + } + params = append(params, self.reinterpretAsBinding(item)) + } + var rest ast.Expression + if firstRestIdx != -1 { + rest = self.reinterpretAsBindingRestElement(seq.Sequence[firstRestIdx]) + } + return &ast.ParameterList{ + List: params, + Rest: rest, + } +} + +func (self *_parser) parseParenthesisedExpression() ast.Expression { + opening := self.idx + self.expect(token.LEFT_PARENTHESIS) + var list []ast.Expression + if self.token != token.RIGHT_PARENTHESIS { + for { + if self.token == token.ELLIPSIS { + start := self.idx + self.errorUnexpectedToken(token.ELLIPSIS) + self.next() + expr := self.parseAssignmentExpression() + list = append(list, &ast.BadExpression{ + From: start, + To: expr.Idx1(), + }) + } else { + list = append(list, self.parseAssignmentExpression()) + } + if self.token != token.COMMA { + break + } + self.next() + if self.token == token.RIGHT_PARENTHESIS { + self.errorUnexpectedToken(token.RIGHT_PARENTHESIS) + break + } + } + } + self.expect(token.RIGHT_PARENTHESIS) + if len(list) == 1 && len(self.errors) == 0 { + return list[0] + } + if len(list) == 0 { + self.errorUnexpectedToken(token.RIGHT_PARENTHESIS) + return &ast.BadExpression{ + From: opening, + To: self.idx, + } + } + return &ast.SequenceExpression{ + Sequence: list, + } +} + func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral { offset := self.chrOffset - 1 // Opening slash already gotten @@ -271,7 +342,7 @@ func (self *_parser) parseObjectProperty() ast.Property { Function: idx, ParameterList: parameterList, } - self.parseFunctionBlock(node) + node.Body, node.DeclarationList = self.parseFunctionBlock() return &ast.PropertyKeyed{ Key: value, @@ -307,7 +378,7 @@ func (self *_parser) parseObjectProperty() ast.Property { Function: idx, ParameterList: parameterList, } - self.parseFunctionBlock(node) + node.Body, node.DeclarationList = self.parseFunctionBlock() return &ast.PropertyKeyed{ Key: value, Kind: ast.PropertyKindGet, @@ -323,7 +394,7 @@ func (self *_parser) parseObjectProperty() ast.Property { ParameterList: parameterList, } - self.parseFunctionBlock(node) + node.Body, node.DeclarationList = self.parseFunctionBlock() return &ast.PropertyKeyed{ Key: value, @@ -815,7 +886,7 @@ func (self *_parser) parseLogicalOrExpression() ast.Expression { return left } -func (self *_parser) parseConditionlExpression() ast.Expression { +func (self *_parser) parseConditionalExpression() ast.Expression { left := self.parseLogicalOrExpression() if self.token == token.QUESTION_MARK { @@ -833,13 +904,16 @@ func (self *_parser) parseConditionlExpression() ast.Expression { } func (self *_parser) parseAssignmentExpression() ast.Expression { + start := self.idx parenthesis := false + var state parserState if self.token == token.LET { self.token = token.IDENTIFIER } else if self.token == token.LEFT_PARENTHESIS { + self.mark(&state) parenthesis = true } - left := self.parseConditionlExpression() + left := self.parseConditionalExpression() var operator token.Token switch self.token { case token.ASSIGN: @@ -866,6 +940,35 @@ func (self *_parser) parseAssignmentExpression() ast.Expression { operator = token.SHIFT_RIGHT case token.UNSIGNED_SHIFT_RIGHT_ASSIGN: operator = token.UNSIGNED_SHIFT_RIGHT + case token.ARROW: + var paramList *ast.ParameterList + if id, ok := left.(*ast.Identifier); ok { + paramList = &ast.ParameterList{ + Opening: id.Idx, + Closing: id.Idx1(), + List: []*ast.Binding{{ + Target: id, + }}, + } + } else if parenthesis { + if seq, ok := left.(*ast.SequenceExpression); ok && len(self.errors) == 0 { + paramList = self.reinterpretSequenceAsArrowFuncParams(seq) + } else { + self.restore(&state) + paramList = self.parseFunctionParameterList() + } + } else { + self.error(left.Idx0(), "Malformed arrow function parameter list") + return &ast.BadExpression{From: left.Idx0(), To: left.Idx1()} + } + self.expect(token.ARROW) + node := &ast.ArrowFunctionLiteral{ + Start: start, + ParameterList: paramList, + } + node.Body, node.DeclarationList = self.parseArrowFunctionBody() + node.Source = self.slice(node.Start, node.Body.Idx1()) + return node } if operator != 0 { @@ -1111,6 +1214,27 @@ func (self *_parser) reinterpretAsBindingElement(expr ast.Expression) ast.Expres } } +func (self *_parser) reinterpretAsBinding(expr ast.Expression) *ast.Binding { + switch expr := expr.(type) { + case *ast.AssignExpression: + if expr.Operator == token.ASSIGN { + return &ast.Binding{ + Target: self.reinterpretAsDestructBindingTarget(expr.Left), + Initializer: expr.Right, + } + } else { + self.error(expr.Idx0(), "Invalid destructuring assignment target") + return &ast.Binding{ + Target: &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()}, + } + } + default: + return &ast.Binding{ + Target: self.reinterpretAsDestructBindingTarget(expr), + } + } +} + func (self *_parser) reinterpretAsDestructAssignTarget(item ast.Expression) ast.Expression { switch item := item.(type) { case nil: diff --git a/parser/lexer.go b/parser/lexer.go index ab6fed7f..92e7eeb8 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -184,6 +184,33 @@ func isId(tkn token.Token) bool { return false } +type parserState struct { + tok token.Token + literal string + parsedLiteral unistring.String + implicitSemicolon, insertSemicolon bool + chr rune + chrOffset, offset int + errorCount int +} + +func (self *_parser) mark(state *parserState) *parserState { + if state == nil { + state = &parserState{} + } + state.tok, state.literal, state.parsedLiteral, state.implicitSemicolon, state.insertSemicolon, state.chr, state.chrOffset, state.offset = + self.token, self.literal, self.parsedLiteral, self.implicitSemicolon, self.insertSemicolon, self.chr, self.chrOffset, self.offset + + state.errorCount = len(self.errors) + return state +} + +func (self *_parser) restore(state *parserState) { + self.token, self.literal, self.parsedLiteral, self.implicitSemicolon, self.insertSemicolon, self.chr, self.chrOffset, self.offset = + state.tok, state.literal, state.parsedLiteral, state.implicitSemicolon, state.insertSemicolon, state.chr, state.chrOffset, state.offset + self.errors = self.errors[:state.errorCount] +} + func (self *_parser) peek() token.Token { implicitSemicolon, insertSemicolon, chr, chrOffset, offset := self.implicitSemicolon, self.insertSemicolon, self.chr, self.chrOffset, self.offset tok, _, _, _ := self.scan() @@ -363,7 +390,11 @@ func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unis case '=': if self.chr == '>' { self.read() - tkn = token.ARROW + if self.implicitSemicolon { + tkn = token.ILLEGAL + } else { + tkn = token.ARROW + } } else { tkn = self.switch2(token.ASSIGN, token.EQUAL) if tkn == token.EQUAL && self.chr == '=' { diff --git a/parser/parser_test.go b/parser/parser_test.go index c55db46e..217b8186 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -881,6 +881,10 @@ func TestParser(t *testing.T) { test(`[a, b] = [1, 2]`, nil) test(`({"a b": {}} = {})`, nil) + + test(`ref = (a, b = 39,) => { + };`, nil) + test(`(a,) => {}`, nil) }) } diff --git a/parser/statement.go b/parser/statement.go index 4870c653..381675e1 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -195,24 +195,32 @@ func (self *_parser) parseFunction(declaration bool) *ast.FunctionLiteral { } node.Name = name node.ParameterList = self.parseFunctionParameterList() - self.parseFunctionBlock(node) + node.Body, node.DeclarationList = self.parseFunctionBlock() node.Source = self.slice(node.Idx0(), node.Idx1()) return node } -func (self *_parser) parseFunctionBlock(node *ast.FunctionLiteral) { - { - self.openScope() - inFunction := self.scope.inFunction - self.scope.inFunction = true - defer func() { - self.scope.inFunction = inFunction - self.closeScope() - }() - node.Body = self.parseBlockStatement() - node.DeclarationList = self.scope.declarationList +func (self *_parser) parseFunctionBlock() (body *ast.BlockStatement, declarationList []*ast.VariableDeclaration) { + self.openScope() + inFunction := self.scope.inFunction + self.scope.inFunction = true + defer func() { + self.scope.inFunction = inFunction + self.closeScope() + }() + body = self.parseBlockStatement() + declarationList = self.scope.declarationList + return +} + +func (self *_parser) parseArrowFunctionBody() (ast.ConciseBody, []*ast.VariableDeclaration) { + if self.token == token.LEFT_BRACE { + return self.parseFunctionBlock() } + return &ast.ExpressionBody{ + Expression: self.parseAssignmentExpression(), + }, nil } func (self *_parser) parseDebuggerStatement() ast.Statement { diff --git a/runtime.go b/runtime.go index 556ed6e0..c0809c07 100644 --- a/runtime.go +++ b/runtime.go @@ -402,9 +402,11 @@ func (r *Runtime) init() { } r.vm.init() - r.global.FunctionPrototype = r.newNativeFunc(func(FunctionCall) Value { + funcProto := r.newNativeFunc(func(FunctionCall) Value { return _undefined }, nil, " ", nil, 0) + r.global.FunctionPrototype = funcProto + funcProtoObj := funcProto.self.(*nativeFuncObject) r.global.IteratorPrototype = r.newLazyObject(r.createIterProto) @@ -441,6 +443,9 @@ func (r *Runtime) init() { setterFunc: r.global.thrower, accessor: true, } + + funcProtoObj._put("caller", r.global.throwerProperty) + funcProtoObj._put("arguments", r.global.throwerProperty) } func (r *Runtime) typeErrorResult(throw bool, args ...interface{}) { @@ -576,13 +581,35 @@ func (r *Runtime) newFunc(name unistring.String, len int, strict bool) (f *funcO f.class = classFunction f.val = v f.extensible = true + f.strict = strict v.self = f f.prototype = r.global.FunctionPrototype f.init(name, len) - if strict { - f._put("caller", r.global.throwerProperty) - f._put("arguments", r.global.throwerProperty) + return +} + +func (r *Runtime) newArrowFunc(name unistring.String, len int, strict bool) (f *arrowFuncObject) { + v := &Object{runtime: r} + + f = &arrowFuncObject{} + f.class = classFunction + f.val = v + f.extensible = true + f.strict = strict + + vm := r.vm + var this Value + if vm.sb >= 0 { + this = vm.stack[vm.sb] + } else { + this = vm.r.globalObject } + + f.this = this + f.newTarget = vm.newTarget + v.self = f + f.prototype = r.global.FunctionPrototype + f.init(name, len) return } @@ -864,8 +891,14 @@ func (r *Runtime) throw(e Value) { panic(e) } -func (r *Runtime) builtin_thrower(FunctionCall) Value { - r.typeErrorResult(true, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them") +func (r *Runtime) builtin_thrower(call FunctionCall) Value { + obj := r.toObject(call.This) + strict := true + switch fn := obj.self.(type) { + case *funcObject: + strict = fn.strict + } + r.typeErrorResult(strict, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them") return nil } diff --git a/runtime_test.go b/runtime_test.go index 9699b7da..dd7e996b 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -2342,6 +2342,14 @@ func TestCoverFuncName(t *testing.T) { testScript1(TESTLIB+SCRIPT, _undefined, t) } +func TestAnonFuncName(t *testing.T) { + const SCRIPT = ` + const d = Object.getOwnPropertyDescriptor((function() {}), 'name'); + d !== undefined && d.value === ''; + ` + testScript1(SCRIPT, valueTrue, t) +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) diff --git a/tc39_test.go b/tc39_test.go index b90a57d6..53733d5d 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -55,6 +55,8 @@ var ( "test/language/expressions/arrow-function/scope-param-rest-elem-var-close.js": true, "test/language/expressions/arrow-function/scope-param-elem-var-close.js": true, "test/language/expressions/arrow-function/scope-param-elem-var-open.js": true, + "test/language/function-code/each-param-has-own-scope.js": true, + "test/language/function-code/each-param-has-own-non-shared-eval-scope.js": true, // These tests are out of date (fixed in https://github.com/tc39/test262/commit/7d998a098e5420cb4b6ee4a05eb8c386d750c596) "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/key-is-numericindex.js": true, @@ -75,6 +77,10 @@ var ( // 167e596a649ede35df11d03cb3c093941c9cf396 "test/built-ins/TypedArrayConstructors/internals/Set/detached-buffer.js": true, + // 59a1a016b7cf5cf43f66b274c7d1db4ec6066935 + "test/language/expressions/function/name.js": true, + "test/built-ins/Proxy/revocable/revocation-function-name.js": true, + "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js": true, // timezone "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js": true, // timezone "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone @@ -204,65 +210,22 @@ var ( "test/language/statements/for-of/dstr/let-ary-ptrn-elem-id-init-fn-name-class.js": true, "test/language/statements/try/dstr/obj-ptrn-id-init-fn-name-class.js": true, "test/language/statements/try/dstr/ary-ptrn-elem-id-init-fn-name-class.js": true, - - // arrow-function - "test/built-ins/Object/prototype/toString/proxy-function.js": true, - "test/built-ins/Array/prototype/pop/throws-with-string-receiver.js": true, - "test/built-ins/Array/prototype/push/throws-with-string-receiver.js": true, - "test/built-ins/Array/prototype/shift/throws-with-string-receiver.js": true, - "test/built-ins/Array/prototype/unshift/throws-with-string-receiver.js": true, - "test/built-ins/Date/prototype/toString/non-date-receiver.js": true, - "test/built-ins/Number/prototype/toExponential/range.js": true, - "test/built-ins/Number/prototype/toFixed/range.js": true, - "test/built-ins/Number/prototype/toPrecision/range.js": true, - "test/built-ins/TypedArray/prototype/sort/stability.js": true, - "test/built-ins/RegExp/named-groups/functional-replace-global.js": true, - "test/built-ins/RegExp/named-groups/functional-replace-non-global.js": true, - "test/built-ins/Array/prototype/sort/stability-513-elements.js": true, - "test/built-ins/Array/prototype/sort/stability-5-elements.js": true, - "test/built-ins/Array/prototype/sort/stability-2048-elements.js": true, - "test/built-ins/Array/prototype/sort/stability-11-elements.js": true, - "test/language/statements/variable/fn-name-arrow.js": true, - "test/language/statements/let/fn-name-arrow.js": true, - "test/language/statements/const/fn-name-arrow.js": true, - "test/built-ins/Proxy/getPrototypeOf/instanceof-target-not-extensible-not-same-proto-throws.js": true, - "test/language/statements/let/dstr/obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/let/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/for/dstr/var-obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/for/dstr/var-ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/for/dstr/let-ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/for/dstr/let-obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/const/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/for/dstr/const-obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/const/dstr/obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/for/dstr/const-ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/variable/dstr/obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/variable/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/expressions/assignment/dstr/obj-prop-elem-init-fn-name-arrow.js": true, - "test/language/expressions/assignment/dstr/obj-id-init-fn-name-arrow.js": true, - "test/language/expressions/assignment/dstr/obj-rest-order.js": true, - "test/language/expressions/assignment/dstr/array-elem-init-fn-name-arrow.js": true, - "test/language/expressions/function/dstr/obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/expressions/function/dstr/dflt-obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/expressions/function/dstr/dflt-ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/expressions/function/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/function/dstr/dflt-ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/function/dstr/obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/function/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/function/dstr/dflt-obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/var-obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/var-ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/obj-prop-elem-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/obj-rest-order.js": true, - "test/language/statements/for-of/dstr/obj-id-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/let-ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/const-obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/const-ary-ptrn-elem-id-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/let-obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/for-of/dstr/array-elem-init-fn-name-arrow.js": true, - "test/language/expressions/call/spread-obj-spread-order.js": true, - "test/language/statements/try/dstr/obj-ptrn-id-init-fn-name-arrow.js": true, - "test/language/statements/try/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js": true, + "test/language/expressions/arrow-function/dstr/ary-ptrn-elem-id-init-fn-name-class.js": true, + "test/language/expressions/arrow-function/dstr/dflt-obj-ptrn-id-init-fn-name-class.js": true, + "test/language/expressions/arrow-function/dstr/obj-ptrn-id-init-fn-name-class.js": true, + "test/language/expressions/arrow-function/dstr/dflt-ary-ptrn-elem-id-init-fn-name-class.js": true, + "test/language/statements/class/static-method-length-dflt.js": true, + "test/language/statements/class/setter-length-dflt.js": true, + "test/language/statements/class/restricted-properties.js": true, + "test/language/statements/class/method-length-dflt.js": true, + "test/language/statements/class/definition/methods-restricted-properties.js": true, + "test/language/expressions/class/static-method-length-dflt.js": true, + "test/language/expressions/class/setter-length-dflt.js": true, + "test/language/expressions/class/restricted-properties.js": true, + "test/language/expressions/class/method-length-dflt.js": true, + "test/language/expressions/arrow-function/lexical-super-property-from-within-constructor.js": true, + "test/language/expressions/arrow-function/lexical-super-property.js": true, + "test/language/expressions/arrow-function/lexical-supercall-from-immediately-invoked-arrow.js": true, // template strings "test/built-ins/String/raw/zero-literal-segments.js": true, @@ -274,6 +237,8 @@ var ( "test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js": true, "test/built-ins/TypedArrayConstructors/internals/DefineOwnProperty/conversion-operation-consistent-nan.js": true, "test/built-ins/TypedArrayConstructors/internals/Set/conversion-operation-consistent-nan.js": true, + "test/built-ins/RegExp/named-groups/functional-replace-non-global.js": true, + "test/built-ins/RegExp/named-groups/functional-replace-global.js": true, // restricted unicode regexp syntax "test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js": true, @@ -332,12 +297,12 @@ var ( } featuresBlackList = []string{ - "arrow-function", "async-iteration", "BigInt", "class", "generators", "String.prototype.replaceAll", + "String.prototype.at", "super", } @@ -374,7 +339,10 @@ var ( "13.13", "13.14", "13.15", + "14.1", + "14.2", "14.3.8", + "16.1", "18", "19", "20", @@ -434,6 +402,9 @@ var ( "sec-function-definitions-static-semantics-early-errors", "sec-functiondeclarationinstantiation", "sec-functiondeclarations-in-ifstatement-statement-clauses", + "sec-arrow-function-definitions", + "sec-arrow-function-definitions-runtime-semantics-evaluation", + "sec-arrow-function-definitions-static-semantics-early-errors", "sec-evaldeclarationinstantiation", "sec-integer-indexed-exotic-objects-defineownproperty-p-desc", "sec-integer-indexed-exotic-objects-get-p-receiver", @@ -625,6 +596,9 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { t.Errorf("Could not parse %s: %v", name, err) return } + if meta.hasFlag("async") { + t.Skip("async") + } if meta.Es5id == "" { skip := true //t.Logf("%s: Not ES5, skipped", name) diff --git a/vm.go b/vm.go index e181c2df..00debb01 100644 --- a/vm.go +++ b/vm.go @@ -2751,6 +2751,16 @@ repeat: vm.pc = 0 vm.stack[vm.sp-n-1], vm.stack[vm.sp-n-2] = vm.stack[vm.sp-n-2], vm.stack[vm.sp-n-1] return + case *arrowFuncObject: + vm.pc++ + vm.pushCtx() + vm.args = n + vm.prg = f.prg + vm.stash = f.stash + vm.pc = 0 + vm.stack[vm.sp-n-1], vm.stack[vm.sp-n-2] = f.this, vm.stack[vm.sp-n-1] + vm.newTarget = f.newTarget + return case *nativeFuncObject: vm._nativeCall(f, n) case *boundFuncObject: @@ -3097,17 +3107,30 @@ func (e *enterFuncStashless) exec(vm *vm) { type newFunc struct { prg *Program name unistring.String + source string + length uint32 strict bool - - srcStart, srcEnd uint32 } func (n *newFunc) exec(vm *vm) { obj := vm.r.newFunc(n.name, int(n.length), n.strict) obj.prg = n.prg obj.stash = vm.stash - obj.src = n.prg.src.Source()[n.srcStart:n.srcEnd] + obj.src = n.source + vm.push(obj.val) + vm.pc++ +} + +type newArrowFunc struct { + newFunc +} + +func (n *newArrowFunc) exec(vm *vm) { + obj := vm.r.newArrowFunc(n.name, int(n.length), n.strict) + obj.prg = n.prg + obj.stash = vm.stash + obj.src = n.source vm.push(obj.val) vm.pc++ } @@ -3725,7 +3748,7 @@ func (_typeof) exec(vm *vm) { break } switch s := v.self.(type) { - case *funcObject, *nativeFuncObject, *boundFuncObject: + case *funcObject, *nativeFuncObject, *boundFuncObject, *arrowFuncObject: r = stringFunction case *lazyObject: v.self = s.create(v) From 78b20b64dad3d0ebb96b66dbed8c066bb71bf8bc Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Tue, 10 Aug 2021 16:03:49 +0100 Subject: [PATCH 14/19] Allow arrow functions to contain 'use strict' for simple parameter lists. Fixes #323. Signed-off-by: Gabri (cherry picked from commit f2b020dc6f3e5336410526ae9ab399ec206a9c18) --- compiler_expr.go | 26 +++++++++++--------------- compiler_test.go | 13 +++++++++++++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/compiler_expr.go b/compiler_expr.go index 128c6feb..0372f14f 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -830,12 +830,7 @@ func (c *compiler) compileParameterBindingIdentifier(name unistring.String, offs c.checkIdentifierName(name, offset) c.checkIdentifierLName(name, offset) } - b, unique := c.scope.bindNameShadow(name) - if !unique && c.scope.strict { - c.throwSyntaxError(offset, "Strict mode function may not have duplicate parameter names (%s)", name) - return nil, false - } - return b, unique + return c.scope.bindNameShadow(name) } func (c *compiler) compileParameterPatternIdBinding(name unistring.String, offset int) { @@ -911,16 +906,17 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { if item.Initializer != nil { hasInits = true } - if hasPatterns || hasInits || e.isArrow { - if firstDupIdx >= 0 { - e.c.throwSyntaxError(firstDupIdx, "Duplicate parameter name not allowed in this context") - return - } - if e.strict != nil { - e.c.throwSyntaxError(int(e.strict.Idx)-1, "Illegal 'use strict' directive in function with non-simple parameter list") - return - } + + if firstDupIdx >= 0 && (hasPatterns || hasInits || s.strict || e.isArrow) { + e.c.throwSyntaxError(firstDupIdx, "Duplicate parameter name not allowed in this context") + return } + + if (hasPatterns || hasInits) && e.strict != nil { + e.c.throwSyntaxError(int(e.strict.Idx)-1, "Illegal 'use strict' directive in function with non-simple parameter list") + return + } + if !hasInits { length++ } diff --git a/compiler_test.go b/compiler_test.go index 4abb35b0..37eb8df2 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -4139,6 +4139,19 @@ func TestCatchParamPattern(t *testing.T) { testScript1(SCRIPT, asciiString("1 2 3"), t) } +func TestArrowUseStrict(t *testing.T) { + // simple parameter list -- ok + _, err := Compile("", "(a) => {'use strict';}", false) + if err != nil { + t.Fatal(err) + } + // non-simple parameter list -- syntax error + _, err = Compile("", "(a=0) => {'use strict';}", false) + if err == nil { + t.Fatal("expected error") + } +} + /* func TestBabel(t *testing.T) { src, err := ioutil.ReadFile("babel7.js") From a5b4da66b86f4c0d17e84621f51fced22475ab2a Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Tue, 17 Aug 2021 16:10:38 +0100 Subject: [PATCH 15/19] Fixed argument variable reference resolution in stashless functions Signed-off-by: Gabri (cherry picked from commit f3b82bd9422d0d09091e13715e6ffb5d98fc0524) --- compiler_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler_test.go b/compiler_test.go index 37eb8df2..6031f5e0 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -4152,6 +4152,17 @@ func TestArrowUseStrict(t *testing.T) { } } +func TestParameterOverride(t *testing.T) { + const SCRIPT = ` + function f(arg) { + var arg = arg || "default" + return arg + } + f() + ` + testScript1(SCRIPT, asciiString("default"), t) +} + /* func TestBabel(t *testing.T) { src, err := ioutil.ReadFile("babel7.js") From e1aedbe0df7fc78c8f70b27f9465c1d6e40fcb13 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 4 Sep 2021 11:26:40 +0100 Subject: [PATCH 16/19] Treat date-only formats as UTC and date-time as local timezone. Added support for additional datetime formats. Fixes #281, fixes #292. Signed-off-by: Gabri (cherry picked from commit f605f7b047393cdef74bd7b9f4e536a741501e39) --- compiler_test.go | 11 ----- date.go | 95 ++++++++++++++++++++++++------------- date_parser.go | 9 +++- date_test.go | 120 ++++++++++++++++++++--------------------------- 4 files changed, 120 insertions(+), 115 deletions(-) diff --git a/compiler_test.go b/compiler_test.go index 6031f5e0..5d576a6f 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -4039,17 +4039,6 @@ func TestFuncParamScope(t *testing.T) { testScript1(SCRIPT, asciiString("inside inside"), t) } -func TestParameterOverride(t *testing.T) { - const SCRIPT = ` - function f(arg) { - var arg = arg || "default" - return arg - } - f() - ` - testScript1(SCRIPT, asciiString("default"), t) -} - func TestDuplicateGlobalFunc(t *testing.T) { const SCRIPT = ` function a(){} diff --git a/date.go b/date.go index a75c67fb..d7431db8 100644 --- a/date.go +++ b/date.go @@ -23,51 +23,80 @@ type dateObject struct { invalid bool } +type dateLayoutDesc struct { + layout string + dateOnly bool +} + var ( - dateLayoutList = []string{ - "2006-01-02T15:04:05Z0700", - "2006-01-02T15:04:05", - "2006-01-02", - "2006-01-02 15:04:05", - time.RFC1123, - time.RFC1123Z, - dateTimeLayout, - time.UnixDate, - time.ANSIC, - time.RubyDate, - "Mon, 02 Jan 2006 15:04:05 GMT-0700 (MST)", - "Mon, 02 Jan 2006 15:04:05 -0700 (MST)", - - "2006", - "2006-01", - - "2006T15:04", - "2006-01T15:04", - "2006-01-02T15:04", - - "2006T15:04:05", - "2006-01T15:04:05", - - "2006T15:04Z0700", - "2006-01T15:04Z0700", - "2006-01-02T15:04Z0700", - - "2006T15:04:05Z0700", - "2006-01T15:04:05Z0700", + dateLayoutsNumeric = []dateLayoutDesc{ + {layout: "2006-01-02T15:04:05Z0700"}, + {layout: "2006-01-02T15:04:05"}, + {layout: "2006-01-02", dateOnly: true}, + {layout: "2006-01-02 15:04:05"}, + + {layout: "2006", dateOnly: true}, + {layout: "2006-01", dateOnly: true}, + + {layout: "2006T15:04"}, + {layout: "2006-01T15:04"}, + {layout: "2006-01-02T15:04"}, + + {layout: "2006T15:04:05"}, + {layout: "2006-01T15:04:05"}, + + {layout: "2006T15:04Z0700"}, + {layout: "2006-01T15:04Z0700"}, + {layout: "2006-01-02T15:04Z0700"}, + + {layout: "2006T15:04:05Z0700"}, + {layout: "2006-01T15:04:05Z0700"}, + } + + dateLayoutsAlpha = []dateLayoutDesc{ + {layout: time.RFC1123}, + {layout: time.RFC1123Z}, + {layout: dateTimeLayout}, + {layout: time.UnixDate}, + {layout: time.ANSIC}, + {layout: time.RubyDate}, + {layout: "Mon, _2 Jan 2006 15:04:05 GMT-0700 (MST)"}, + {layout: "Mon, _2 Jan 2006 15:04:05 -0700 (MST)"}, + {layout: "Jan _2, 2006", dateOnly: true}, } ) func dateParse(date string) (time.Time, bool) { var t time.Time var err error - for _, layout := range dateLayoutList { - t, err = parseDate(layout, date, time.UTC) + var layouts []dateLayoutDesc + if len(date) > 0 { + first := date[0] + if first <= '9' && (first >= '0' || first == '-' || first == '+') { + layouts = dateLayoutsNumeric + } else { + layouts = dateLayoutsAlpha + } + } else { + return time.Time{}, false + } + for _, desc := range layouts { + var defLoc *time.Location + if desc.dateOnly { + defLoc = time.UTC + } else { + defLoc = time.Local + } + t, err = parseDate(desc.layout, date, defLoc) if err == nil { break } } + if err != nil { + return time.Time{}, false + } unix := timeToMsec(t) - return t, err == nil && unix >= -maxTime && unix <= maxTime + return t, unix >= -maxTime && unix <= maxTime } func (r *Runtime) newDateObject(t time.Time, isSet bool, proto *Object) *Object { diff --git a/date_parser.go b/date_parser.go index 0841cf40..f8360532 100644 --- a/date_parser.go +++ b/date_parser.go @@ -5,6 +5,8 @@ package goja // - 6-digit extended years are supported in place of long year (2006) in the form of +123456 // - Timezone formats tolerate colons, e.g. -0700 will parse -07:00 // - Short week day will also parse long week day +// - Short month ("Jan") will also parse long month ("January") +// - Long day ("02") will also parse short day ("2"). // - Timezone in brackets, "(MST)", will match any string in brackets (e.g. "(GMT Standard Time)") // - If offset is not set and timezone name is unknown, an error is returned // - If offset and timezone name are both set the offset takes precedence and the resulting Location will be FixedZone("", offset) @@ -133,7 +135,10 @@ func parseDate(layout, value string, defaultLocation *time.Location) (time.Time, } case stdMonth: - month, value, err = lookup(shortMonthNames, value) + month, value, err = lookup(longMonthNames, value) + if err != nil { + month, value, err = lookup(shortMonthNames, value) + } month++ case stdLongMonth: month, value, err = lookup(longMonthNames, value) @@ -155,7 +160,7 @@ func parseDate(layout, value string, defaultLocation *time.Location) (time.Time, if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { value = value[1:] } - day, value, err = getnum(value, std == stdZeroDay) + day, value, err = getnum(value, false) if day < 0 { // Note that we allow any one- or two-digit day here. rangeErrString = "day" diff --git a/date_test.go b/date_test.go index 8905ece8..6a45c98d 100644 --- a/date_test.go +++ b/date_test.go @@ -304,91 +304,73 @@ func TestDateSetters(t *testing.T) { func TestDateParse(t *testing.T) { const SCRIPT = ` -var zero = new Date(0); + var zero = new Date(0); -assert.sameValue(zero.valueOf(), Date.parse(zero.toString()), - "Date.parse(zeroDate.toString())"); -assert.sameValue(zero.valueOf(), Date.parse(zero.toUTCString()), - "Date.parse(zeroDate.toUTCString())"); -assert.sameValue(zero.valueOf(), Date.parse(zero.toISOString()), - "Date.parse(zeroDate.toISOString())"); + assert.sameValue(zero.valueOf(), Date.parse(zero.toString()), + "Date.parse(zeroDate.toString())"); + assert.sameValue(zero.valueOf(), Date.parse(zero.toUTCString()), + "Date.parse(zeroDate.toUTCString())"); + assert.sameValue(zero.valueOf(), Date.parse(zero.toISOString()), + "Date.parse(zeroDate.toISOString())"); -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 MST"), 1136239445000, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 MST\")"); - -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)"), 1136239445000, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)\")"); - -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 -07:00 (MST)"), 1136239445000, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 -07:00 (MST)\")"); - -assert.sameValue(Date.parse("Monday, 02 Jan 2006 15:04:05 -0700 (MST)"), 1136239445000, - "Date.parse(\"Monday, 02 Jan 2006 15:04:05 -0700 (MST)\")"); - -assert.sameValue(Date.parse("Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)"), 1136239445000, - "Date.parse(\"Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)\")"); - -assert.sameValue(Date.parse("2006-01-02T15:04:05.000Z"), 1136214245000, - "Date.parse(\"2006-01-02T15:04:05.000Z\")"); - -assert.sameValue(Date.parse("2006-06-02T15:04:05.000"), 1149260645000, - "Date.parse(\"2006-01-02T15:04:05.000\")"); - -assert.sameValue(Date.parse("2006-01-02T15:04:05"), 1136214245000, - "Date.parse(\"2006-01-02T15:04:05\")"); - -assert.sameValue(Date.parse("2006-01-02"), 1136160000000, - "Date.parse(\"2006-01-02\")"); - -assert.sameValue(Date.parse("2006T15:04-0700"), 1136153040000, - "Date.parse(\"2006T15:04-0700\")"); - -assert.sameValue(Date.parse("2006T15:04Z"), 1136127840000, - "Date.parse(\"2006T15:04Z\")"); - -assert.sameValue(Date.parse("Mon Jan 2 15:04:05 MST 2006"), 1136239445000, - "Date.parse(\"Mon Jan 2 15:04:05 MST 2006\")"); + function testParse(str, expected) { + assert.sameValue(Date.parse(str), expected, str); + } -assert.sameValue(Date.parse("Mon Jan 02 15:04:05 MST 2006"), 1136239445000, - "Date.parse(\"Mon Jan 02 15:04:05 MST 2006\")"); + testParse("Mon, 02 Jan 2006 15:04:05 MST", 1136239445000); + testParse("Tue, 22 Jun 2021 13:54:40 GMT", 1624370080000); + testParse("Tuesday, 22 Jun 2021 13:54:40 GMT", 1624370080000); + testParse("Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)", 1136239445000); + testParse("Mon, 02 Jan 2006 15:04:05 -07:00 (MST)", 1136239445000); + testParse("Monday, 02 Jan 2006 15:04:05 -0700 (MST)", 1136239445000); + testParse("Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)", 1136239445000); + testParse("Mon Jan 2 15:04:05 MST 2006", 1136239445000); + testParse("Mon Jan 02 15:04:05 MST 2006", 1136239445000); + testParse("Mon Jan 02 15:04:05 -0700 2006", 1136239445000); -assert.sameValue(Date.parse("Mon Jan 02 15:04:05 -0700 2006"), 1136239445000, - "Date.parse(\"Mon Jan 02 15:04:05 -0700 2006\")"); + testParse("December 04, 1986", 534038400000); + testParse("Dec 04, 1986", 534038400000); + testParse("Dec 4, 1986", 534038400000); -assert.sameValue(Date.parse("2019-01-01T12:00:00.52Z"), 1546344000520, - "Date.parse(\"2019-01-01T12:00:00.52\")"); + testParse("2006-01-02T15:04:05.000Z", 1136214245000); + testParse("2006-06-02T15:04:05.000", 1149275045000); + testParse("2006-01-02T15:04:05", 1136232245000); + testParse("2006-01-02", 1136160000000); + testParse("2006T15:04-0700", 1136153040000); + testParse("2006T15:04Z", 1136127840000); + testParse("2019-01-01T12:00:00.52Z", 1546344000520); -var d = new Date("Mon, 02 Jan 2006 15:04:05 MST"); + var d = new Date("Mon, 02 Jan 2006 15:04:05 MST"); -assert.sameValue(d.getUTCHours(), 22, - "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getUTCHours()"); + assert.sameValue(d.getUTCHours(), 22, + "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getUTCHours()"); -assert.sameValue(d.getHours(), 17, - "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getHours()"); + assert.sameValue(d.getHours(), 17, + "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getHours()"); -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 zzz"), NaN, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 zzz\")"); + assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 zzz"), NaN, + "Date.parse(\"Mon, 02 Jan 2006 15:04:05 zzz\")"); -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 ZZZ"), NaN, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 ZZZ\")"); + assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 ZZZ"), NaN, + "Date.parse(\"Mon, 02 Jan 2006 15:04:05 ZZZ\")"); -var minDateStr = "-271821-04-20T00:00:00.000Z"; -var minDate = new Date(-8640000000000000); + var minDateStr = "-271821-04-20T00:00:00.000Z"; + var minDate = new Date(-8640000000000000); -assert.sameValue(minDate.toISOString(), minDateStr, "minDateStr"); -assert.sameValue(Date.parse(minDateStr), minDate.valueOf(), "parse minDateStr"); + assert.sameValue(minDate.toISOString(), minDateStr, "minDateStr"); + assert.sameValue(Date.parse(minDateStr), minDate.valueOf(), "parse minDateStr"); -var maxDateStr = "+275760-09-13T00:00:00.000Z"; -var maxDate = new Date(8640000000000000); + var maxDateStr = "+275760-09-13T00:00:00.000Z"; + var maxDate = new Date(8640000000000000); -assert.sameValue(maxDate.toISOString(), maxDateStr, "maxDateStr"); -assert.sameValue(Date.parse(maxDateStr), maxDate.valueOf(), "parse maxDateStr"); + assert.sameValue(maxDate.toISOString(), maxDateStr, "maxDateStr"); + assert.sameValue(Date.parse(maxDateStr), maxDate.valueOf(), "parse maxDateStr"); -var belowRange = "-271821-04-19T23:59:59.999Z"; -var aboveRange = "+275760-09-13T00:00:00.001Z"; + var belowRange = "-271821-04-19T23:59:59.999Z"; + var aboveRange = "+275760-09-13T00:00:00.001Z"; -assert.sameValue(Date.parse(belowRange), NaN, "parse below minimum time value"); -assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value"); + assert.sameValue(Date.parse(belowRange), NaN, "parse below minimum time value"); + assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value"); ` l := time.Local From 7cdbcc8daa5bdd7f42f897f56cae3a9388a7aca8 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 11 Sep 2021 13:20:46 +0100 Subject: [PATCH 17/19] Use correct createArgsRest variant when arguments are not in stash. Fixes #327 Signed-off-by: Gabri (cherry picked from commit f08849b4743131442de647c6e7f486d27343310b) --- compiler_expr.go | 2 +- compiler_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/compiler_expr.go b/compiler_expr.go index 0372f14f..79e7cdb4 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1163,7 +1163,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { e.c.p.code[enterFunc2Mark] = ef2 } } - if emitArgsRestMark != -1 { + if emitArgsRestMark != -1 && s.argsInStash { e.c.p.code[emitArgsRestMark] = createArgsRestStash } } else { diff --git a/compiler_test.go b/compiler_test.go index 5d576a6f..792d4ce7 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -3943,6 +3943,41 @@ func TestFuncParamRestStashSimple(t *testing.T) { testScript1(SCRIPT, asciiString("2,3"), t) } +func TestRestArgsNotInStash(t *testing.T) { + const SCRIPT = ` + function f(...rest) { + () => rest; + return rest.length; + } + f(1,2); + ` + testScript1(SCRIPT, valueInt(2), t) +} + +func TestRestArgsInStash(t *testing.T) { + const SCRIPT = ` + function f(first, ...rest) { + () => first; + () => rest; + return rest.length; + } + f(1,2); + ` + testScript1(SCRIPT, valueInt(1), t) +} + +func TestRestArgsInStashFwdRef(t *testing.T) { + const SCRIPT = ` + function f(first = eval(), ...rest) { + () => first; + () => rest; + return rest.length === 1 && rest[0] === 2; + } + f(1,2); + ` + testScript1(SCRIPT, valueTrue, t) +} + func TestFuncParamRestPattern(t *testing.T) { const SCRIPT = ` function f(arg1, ...{0: rest1, 1: rest2}) { From 18cd651703826f490d9ee182cb1d91a09998864e Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 11 Sep 2021 13:26:42 +0100 Subject: [PATCH 18/19] Fixed formatting for go 1.17 Signed-off-by: Gabri (cherry picked from commit 797091df626cbad52df60c27a952b5860eb8a41f) --- tc39_norace_test.go | 1 + tc39_race_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/tc39_norace_test.go b/tc39_norace_test.go index 7e6014e2..89bc066e 100644 --- a/tc39_norace_test.go +++ b/tc39_norace_test.go @@ -1,3 +1,4 @@ +//go:build !race // +build !race package goja diff --git a/tc39_race_test.go b/tc39_race_test.go index 5cc6d15d..e17bd121 100644 --- a/tc39_race_test.go +++ b/tc39_race_test.go @@ -1,3 +1,4 @@ +//go:build race // +build race package goja From 2c4edae107ab4d47bb5b232d0c3fb2fcfe74cf5d Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sun, 12 Sep 2021 15:07:21 +0100 Subject: [PATCH 19/19] Report 'length' as own property for Go slices. Fixes #328. Signed-off-by: Gabri (cherry picked from commit 5513673db09d6e3d10cbe7f453093b02885e42b6) --- object_goslice.go | 2 +- object_goslice_reflect.go | 4 ++-- object_goslice_reflect_test.go | 40 ++++++++++++++++++++++++++++++++++ object_goslice_test.go | 17 +++++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/object_goslice.go b/object_goslice.go index 669c26cc..dcd90e3c 100644 --- a/object_goslice.go +++ b/object_goslice.go @@ -187,7 +187,7 @@ func (o *objectGoSlice) hasOwnPropertyStr(name unistring.String) bool { if idx := strToIdx64(name); idx >= 0 { return idx < int64(len(*o.data)) } - return false + return name == "length" } func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { diff --git a/object_goslice_reflect.go b/object_goslice_reflect.go index 9e7f6257..ca84653d 100644 --- a/object_goslice_reflect.go +++ b/object_goslice_reflect.go @@ -196,7 +196,7 @@ func (o *objectGoSliceReflect) setForeignIdx(idx valueInt, val, receiver Value, } func (o *objectGoSliceReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { - return o._setForeignStr(name, trueValIfPresent(o._hasStr(name)), val, receiver, throw) + return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw) } func (o *objectGoSliceReflect) hasOwnPropertyIdx(idx valueInt) bool { @@ -204,7 +204,7 @@ func (o *objectGoSliceReflect) hasOwnPropertyIdx(idx valueInt) bool { } func (o *objectGoSliceReflect) hasOwnPropertyStr(name unistring.String) bool { - if o._hasStr(name) { + if o._hasStr(name) || name == "length" { return true } return o.objectGoReflect._has(name.String()) diff --git a/object_goslice_reflect_test.go b/object_goslice_reflect_test.go index 112996ec..424b3fe0 100644 --- a/object_goslice_reflect_test.go +++ b/object_goslice_reflect_test.go @@ -334,3 +334,43 @@ func TestGoSliceReflectPopNoPtr(t *testing.T) { t.Fatal(v) } } + +func TestGoSliceReflectLengthProperty(t *testing.T) { + vm := New() + vm.Set("s", []int{2, 3, 4}) + _, err := vm.RunString(` + if (!s.hasOwnProperty("length")) { + throw new Error("hasOwnProperty() returned false"); + } + let desc = Object.getOwnPropertyDescriptor(s, "length"); + if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) { + throw new Error("incorrect property descriptor: " + JSON.stringify(desc)); + } + `) + if err != nil { + t.Fatal(err) + } +} + +type testCustomSliceWithMethods []int + +func (a testCustomSliceWithMethods) Method() bool { + return true +} + +func TestGoSliceReflectMethods(t *testing.T) { + vm := New() + vm.Set("s", testCustomSliceWithMethods{1, 2, 3}) + _, err := vm.RunString(` + if (!s.hasOwnProperty("Method")) { + throw new Error("hasOwnProperty() returned false"); + } + let desc = Object.getOwnPropertyDescriptor(s, "Method"); + if (desc.value() !== true || desc.writable || !desc.enumerable || desc.configurable) { + throw new Error("incorrect property descriptor: " + JSON.stringify(desc)); + } + `) + if err != nil { + t.Fatal(err) + } +} diff --git a/object_goslice_test.go b/object_goslice_test.go index 1c23cb27..f83471fe 100644 --- a/object_goslice_test.go +++ b/object_goslice_test.go @@ -229,3 +229,20 @@ func TestGoSliceShift(t *testing.T) { t.Fatal(v) } } + +func TestGoSliceLengthProperty(t *testing.T) { + vm := New() + vm.Set("s", []interface{}{2, 3, 4}) + _, err := vm.RunString(` + if (!s.hasOwnProperty("length")) { + throw new Error("hasOwnProperty() returned false"); + } + let desc = Object.getOwnPropertyDescriptor(s, "length"); + if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) { + throw new Error("incorrect property descriptor: " + JSON.stringify(desc)); + } + `) + if err != nil { + t.Fatal(err) + } +}