diff --git a/.tc39_test262_checkout.sh b/.tc39_test262_checkout.sh index 65dedfc6..1500daa9 100755 --- a/.tc39_test262_checkout.sh +++ b/.tc39_test262_checkout.sh @@ -1,6 +1,6 @@ -#!/bin/sh +#!/bin/sh -e # this is just the commit it was last tested with -sha=926b0960d737b9f1dfd0ec0c1dfd95d836016d33 +sha=cb4a6c8074671c00df8cbc17a620c0f9462b312a mkdir -p testdata/test262 cd testdata/test262 diff --git a/builtin_regexp.go b/builtin_regexp.go index 2d02b667..14d875bf 100644 --- a/builtin_regexp.go +++ b/builtin_regexp.go @@ -688,8 +688,7 @@ func (r *Runtime) regExpExec(execFn func(FunctionCall) Value, rxObj *Object, arg return res } -func (r *Runtime) getGlobalRegexpMatches(rxObj *Object, s String) []Value { - fullUnicode := nilSafe(rxObj.self.getStr("unicode", nil)).ToBoolean() +func (r *Runtime) getGlobalRegexpMatches(rxObj *Object, s String, fullUnicode bool) []Value { rxObj.self.setOwnStr("lastIndex", intToValue(0), true) execFn, ok := r.toObject(rxObj.self.getStr("exec", nil)).self.assertCallable() if !ok { @@ -714,9 +713,10 @@ func (r *Runtime) getGlobalRegexpMatches(rxObj *Object, s String) []Value { func (r *Runtime) regexpproto_stdMatcherGeneric(rxObj *Object, s String) Value { rx := rxObj.self - global := rx.getStr("global", nil) - if global != nil && global.ToBoolean() { - a := r.getGlobalRegexpMatches(rxObj, s) + flags := nilSafe(rx.getStr("flags", nil)).String() + global := strings.ContainsRune(flags, 'g') + if global { + a := r.getGlobalRegexpMatches(rxObj, s, strings.ContainsRune(flags, 'u')) if len(a) == 0 { return _null } @@ -1092,8 +1092,11 @@ RETURN: func (r *Runtime) regexpproto_stdReplacerGeneric(rxObj *Object, s, replaceStr String, rcall func(FunctionCall) Value) Value { var results []Value - if nilSafe(rxObj.self.getStr("global", nil)).ToBoolean() { - results = r.getGlobalRegexpMatches(rxObj, s) + flags := nilSafe(rxObj.self.getStr("flags", nil)).String() + isGlobal := strings.ContainsRune(flags, 'g') + isUnicode := strings.ContainsRune(flags, 'u') + if isGlobal { + results = r.getGlobalRegexpMatches(rxObj, s, isUnicode) } else { execFn := toMethod(rxObj.self.getStr("exec", nil)) // must be non-nil result := r.regExpExec(execFn, rxObj, s) diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 14431342..b9274604 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -14,17 +14,25 @@ type typedArraySortCtx struct { ta *typedArrayObject compare func(FunctionCall) Value needValidate bool + detached bool } func (ctx *typedArraySortCtx) Len() int { return ctx.ta.length } -func (ctx *typedArraySortCtx) Less(i, j int) bool { - if ctx.needValidate { - ctx.ta.viewedArrayBuf.ensureNotDetached(true) +func (ctx *typedArraySortCtx) checkDetached() { + if !ctx.detached && ctx.needValidate { + ctx.detached = !ctx.ta.viewedArrayBuf.ensureNotDetached(false) ctx.needValidate = false } +} + +func (ctx *typedArraySortCtx) Less(i, j int) bool { + ctx.checkDetached() + if ctx.detached { + return false + } offset := ctx.ta.offset if ctx.compare != nil { x := ctx.ta.typedArray.get(offset + i) @@ -54,9 +62,9 @@ func (ctx *typedArraySortCtx) Less(i, j int) bool { } func (ctx *typedArraySortCtx) Swap(i, j int) { - if ctx.needValidate { - ctx.ta.viewedArrayBuf.ensureNotDetached(true) - ctx.needValidate = false + ctx.checkDetached() + if ctx.detached { + return } offset := ctx.ta.offset ctx.ta.typedArray.swap(offset+i, offset+j) @@ -146,7 +154,6 @@ func (r *Runtime) newDataView(args []Value, newTarget *Object) *Object { if newTarget == nil { panic(r.needNew("DataView")) } - proto := r.getPrototypeFromCtor(newTarget, r.getDataView(), r.getDataViewPrototype()) var bufArg Value if len(args) > 0 { bufArg = args[0] @@ -177,6 +184,14 @@ func (r *Runtime) newDataView(args []Value, newTarget *Object) *Object { } else { byteLen = len(buffer.data) - byteOffset } + proto := r.getPrototypeFromCtor(newTarget, r.getDataView(), r.getDataViewPrototype()) + buffer.ensureNotDetached(true) + if byteOffset > len(buffer.data) { + panic(r.newError(r.getRangeError(), "Start offset %d is outside the bounds of the buffer", byteOffset)) + } + if byteOffset+byteLen > len(buffer.data) { + panic(r.newError(r.getRangeError(), "Invalid DataView length %d", byteLen)) + } o := &Object{runtime: r} b := &dataViewObject{ baseObject: baseObject{ @@ -1007,7 +1022,6 @@ 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(true) if ta.isValidIntegerIndex(i) { ta.typedArray.set(targetOffset+i, val) } @@ -1270,7 +1284,7 @@ func (r *Runtime) typedArray_from(call FunctionCall) Value { for idx, val := range values { fc.Arguments[0], fc.Arguments[1] = val, intToValue(int64(idx)) val = mapFc(fc) - ta.typedArray.set(idx, val) + ta._putIdx(idx, val) } } return ta.val @@ -1417,8 +1431,6 @@ func (r *Runtime) _newTypedArrayFromTypedArray(src *typedArrayObject, newTarget src.viewedArrayBuf.ensureNotDetached(true) l := src.length - arrayBuffer := r.getArrayBuffer() - dst.viewedArrayBuf.prototype = r.getPrototypeFromCtor(r.speciesConstructorObj(src.viewedArrayBuf.val, arrayBuffer), arrayBuffer, r.getArrayBufferPrototype()) dst.viewedArrayBuf.data = allocByteSlice(toIntStrict(int64(l) * int64(dst.elemSize))) src.viewedArrayBuf.ensureNotDetached(true) if src.defaultCtor == dst.defaultCtor { diff --git a/compiler_test.go b/compiler_test.go index 05d82ca4..c9ba80e3 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -3135,18 +3135,6 @@ func TestDeleteGlobalEval(t *testing.T) { testScript(SCRIPT, valueTrue, t) } -func TestGlobalVarNames(t *testing.T) { - vm := New() - _, err := vm.RunString("(0,eval)('var x')") - if err != nil { - t.Fatal(err) - } - _, err = vm.RunString("let x") - if err == nil { - t.Fatal("Expected error") - } -} - func TestTryResultEmpty(t *testing.T) { const SCRIPT = ` 1; try { } finally { } diff --git a/date_parser.go b/date_parser.go index f8360532..762888c2 100644 --- a/date_parser.go +++ b/date_parser.go @@ -123,6 +123,10 @@ func parseDate(layout, value string, defaultLocation *time.Location) (time.Time, p, value = value[1:7], value[7:] year, err = atoi(p) if neg { + if year == 0 { + err = errBad + break + } year = -year } } else { diff --git a/runtime.go b/runtime.go index ff7f7e6f..ca869f68 100644 --- a/runtime.go +++ b/runtime.go @@ -45,8 +45,7 @@ const ( ) type global struct { - stash stash - varNames map[unistring.String]struct{} + stash stash Object *Object Array *Object diff --git a/runtime_test.go b/runtime_test.go index 708ce7f0..f2992d5f 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -2968,6 +2968,17 @@ func ExampleRuntime_ForOf() { // Output: a=1,b=2, } +func TestDestructAssignToSymbol(t *testing.T) { + const SCRIPT = ` + const s = Symbol('s'); + const target = {}; + + ({a: target[s]} = {a: 42}); + assert.sameValue(target[s], 42); +` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) diff --git a/string_ascii.go b/string_ascii.go index 5ff21bf7..a74dd638 100644 --- a/string_ascii.go +++ b/string_ascii.go @@ -114,7 +114,7 @@ func isRangeErr(err error) bool { } func (s asciiString) _toFloat() (float64, error) { - ss := strings.TrimSpace(string(s)) + ss := strings.ToLower(strings.TrimSpace(string(s))) if ss == "" { return 0, nil } @@ -122,7 +122,14 @@ func (s asciiString) _toFloat() (float64, error) { var f float64 return -f, nil } + f, err := strconv.ParseFloat(ss, 64) + if err == nil && math.IsInf(f, 0) { + if strings.HasPrefix(ss, "inf") || strings.HasPrefix(ss, "-inf") || strings.HasPrefix(ss, "+inf") { + // We handle "Infinity" separately, prevent from being parsed as Infinity due to strconv.ParseFloat() permissive syntax + return 0, strconv.ErrSyntax + } + } if isRangeErr(err) { err = nil } diff --git a/tc39_test.go b/tc39_test.go index 841feae1..16d3a267 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -185,6 +185,17 @@ var ( "test/built-ins/TypedArray/prototype/toReversed/this-value-invalid.js": true, "test/built-ins/TypedArray/prototype/toSorted/comparefn-not-a-function.js": true, "test/built-ins/TypedArray/prototype/toSorted/this-value-invalid.js": true, + "test/built-ins/RegExp/prototype/sticky/this-val-non-obj.js": true, + "test/built-ins/RegExp/prototype/source/this-val-non-obj.js": true, + "test/built-ins/RegExp/prototype/multiline/this-val-non-obj.js": true, + "test/built-ins/RegExp/prototype/ignoreCase/this-val-non-obj.js": true, + "test/built-ins/RegExp/prototype/unicode/this-val-non-obj.js": true, + "test/built-ins/RegExp/prototype/dotAll/this-val-non-obj.js": true, + "test/built-ins/RegExp/prototype/global/this-val-non-obj.js": true, + "test/built-ins/RegExp/prototype/flags/this-val-non-obj.js": true, + "test/built-ins/Iterator/prototype/Symbol.iterator/return-val.js": true, + "test/built-ins/DataView/prototype/setBigUint64/not-a-constructor.js": true, + "test/built-ins/DataView/prototype/getBigUint64/not-a-constructor.js": true, // Regexp "test/language/literals/regexp/invalid-range-negative-lookbehind.js": true, @@ -192,6 +203,9 @@ var ( "test/language/literals/regexp/invalid-optional-negative-lookbehind.js": true, "test/language/literals/regexp/invalid-optional-lookbehind.js": true, + // unicode full case folding + "test/built-ins/RegExp/unicode_full_case_folding.js": true, + // FIXME bugs // Left-hand side as a CoverParenthesizedExpression @@ -203,6 +217,9 @@ var ( // Skip due to regexp named groups "test/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js": true, + + "test/built-ins/RegExp/nullable-quantifier.js": true, + "test/built-ins/RegExp/lookahead-quantifier-match-groups.js": true, } featuresBlackList = []string{ @@ -211,8 +228,11 @@ var ( "BigInt", "resizable-arraybuffer", "regexp-named-groups", + "regexp-duplicate-named-groups", "regexp-unicode-property-escapes", "regexp-match-indices", + "regexp-modifiers", + "RegExp.escape", "legacy-regexp", "tail-call-optimization", "Temporal", @@ -222,6 +242,7 @@ var ( "import.meta", "Atomics", "Atomics.waitAsync", + "Atomics.pause", "FinalizationRegistry", "WeakRef", "numeric-separator-literal", @@ -231,6 +252,20 @@ var ( "SharedArrayBuffer", "decorators", "regexp-v-flag", + "iterator-helpers", + "symbols-as-weakmap-keys", + "uint8array-base64", + "String.prototype.toWellFormed", + "explicit-resource-management", + "set-methods", + "promise-try", + "promise-with-resolvers", + "array-grouping", + "Math.sumPrecise", + "Float16Array", + "arraybuffer-transfer", + "Array.fromAsync", + "String.prototype.isWellFormed", } ) diff --git a/vm.go b/vm.go index 30b79331..c09e00da 100644 --- a/vm.go +++ b/vm.go @@ -208,6 +208,48 @@ func (r *stashRefConst) set(v Value) { } type objRef struct { + base *Object + name Value + this Value + strict bool + + nameConverted bool +} + +func (r *objRef) getKey() Value { + if !r.nameConverted { + r.name = toPropertyKey(r.name) + r.nameConverted = true + } + return r.name +} + +func (r *objRef) get() Value { + return r.base.get(r.getKey(), r.this) +} + +func (r *objRef) set(v Value) { + key := r.getKey() + if r.this != nil { + r.base.set(key, v, r.this, r.strict) + } else { + r.base.setOwn(key, v, r.strict) + } +} + +func (r *objRef) init(v Value) { + if r.this != nil { + r.base.set(r.getKey(), v, r.this, r.strict) + } else { + r.base.setOwn(r.getKey(), v, r.strict) + } +} + +func (r *objRef) refname() unistring.String { + return r.getKey().string() +} + +type objStrRef struct { base *Object name unistring.String this Value @@ -215,11 +257,11 @@ type objRef struct { binding bool } -func (r *objRef) get() Value { +func (r *objStrRef) get() Value { return r.base.self.getStr(r.name, r.this) } -func (r *objRef) set(v Value) { +func (r *objStrRef) set(v Value) { if r.strict && r.binding && !r.base.self.hasOwnPropertyStr(r.name) { panic(referenceError(fmt.Sprintf("%s is not defined", r.name))) } @@ -230,7 +272,7 @@ func (r *objRef) set(v Value) { } } -func (r *objRef) init(v Value) { +func (r *objStrRef) init(v Value) { if r.this != nil { r.base.setStr(r.name, v, r.this, r.strict) } else { @@ -238,7 +280,7 @@ func (r *objRef) init(v Value) { } } -func (r *objRef) refname() unistring.String { +func (r *objStrRef) refname() unistring.String { return r.name } @@ -465,7 +507,7 @@ func (s *stash) getByName(name unistring.String) (v Value, exists bool) { func (s *stash) getRefByName(name unistring.String, strict bool) ref { if obj := s.obj; obj != nil { if stashObjHas(obj, name) { - return &objRef{ + return &objStrRef{ base: obj, name: name, strict: strict, @@ -1588,10 +1630,10 @@ var getElemRef _getElemRef func (_getElemRef) exec(vm *vm) { obj := vm.stack[vm.sp-2].ToObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-1]) + propName := vm.stack[vm.sp-1] vm.refStack = append(vm.refStack, &objRef{ base: obj, - name: propName.string(), + name: propName, }) vm.sp -= 2 vm.pc++ @@ -1603,10 +1645,10 @@ var getElemRefRecv _getElemRefRecv func (_getElemRefRecv) exec(vm *vm) { obj := vm.stack[vm.sp-1].ToObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-2]) + propName := vm.stack[vm.sp-2] vm.refStack = append(vm.refStack, &objRef{ base: obj, - name: propName.string(), + name: propName, this: vm.stack[vm.sp-3], }) vm.sp -= 3 @@ -1619,10 +1661,10 @@ var getElemRefStrict _getElemRefStrict func (_getElemRefStrict) exec(vm *vm) { obj := vm.stack[vm.sp-2].ToObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-1]) + propName := vm.stack[vm.sp-1] vm.refStack = append(vm.refStack, &objRef{ base: obj, - name: propName.string(), + name: propName, strict: true, }) vm.sp -= 2 @@ -1635,10 +1677,10 @@ var getElemRefRecvStrict _getElemRefRecvStrict func (_getElemRefRecvStrict) exec(vm *vm) { obj := vm.stack[vm.sp-1].ToObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-2]) + propName := vm.stack[vm.sp-2] vm.refStack = append(vm.refStack, &objRef{ base: obj, - name: propName.string(), + name: propName, this: vm.stack[vm.sp-3], strict: true, }) @@ -1908,7 +1950,7 @@ func (d deletePropStrict) exec(vm *vm) { type getPropRef unistring.String func (p getPropRef) exec(vm *vm) { - vm.refStack = append(vm.refStack, &objRef{ + vm.refStack = append(vm.refStack, &objStrRef{ base: vm.stack[vm.sp-1].ToObject(vm.r), name: unistring.String(p), }) @@ -1919,7 +1961,7 @@ func (p getPropRef) exec(vm *vm) { type getPropRefRecv unistring.String func (p getPropRefRecv) exec(vm *vm) { - vm.refStack = append(vm.refStack, &objRef{ + vm.refStack = append(vm.refStack, &objStrRef{ this: vm.stack[vm.sp-2], base: vm.stack[vm.sp-1].ToObject(vm.r), name: unistring.String(p), @@ -1931,7 +1973,7 @@ func (p getPropRefRecv) exec(vm *vm) { type getPropRefStrict unistring.String func (p getPropRefStrict) exec(vm *vm) { - vm.refStack = append(vm.refStack, &objRef{ + vm.refStack = append(vm.refStack, &objStrRef{ base: vm.stack[vm.sp-1].ToObject(vm.r), name: unistring.String(p), strict: true, @@ -1943,7 +1985,7 @@ func (p getPropRefStrict) exec(vm *vm) { type getPropRefRecvStrict unistring.String func (p getPropRefRecvStrict) exec(vm *vm) { - vm.refStack = append(vm.refStack, &objRef{ + vm.refStack = append(vm.refStack, &objStrRef{ this: vm.stack[vm.sp-2], base: vm.stack[vm.sp-1].ToObject(vm.r), name: unistring.String(p), @@ -2330,11 +2372,11 @@ var getElem _getElem func (_getElem) exec(vm *vm) { v := vm.stack[vm.sp-2] obj := v.baseObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-1]) if obj == nil { - vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1])) return } + propName := toPropertyKey(vm.stack[vm.sp-1]) vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v)) @@ -2348,13 +2390,13 @@ var getElemRecv _getElemRecv func (_getElemRecv) exec(vm *vm) { recv := vm.stack[vm.sp-3] - propName := toPropertyKey(vm.stack[vm.sp-2]) v := vm.stack[vm.sp-1] obj := v.baseObject(vm.r) if obj == nil { - vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-2])) return } + propName := toPropertyKey(vm.stack[vm.sp-2]) vm.stack[vm.sp-3] = nilSafe(obj.get(propName, recv)) @@ -2388,12 +2430,12 @@ var getElemCallee _getElemCallee func (_getElemCallee) exec(vm *vm) { v := vm.stack[vm.sp-2] obj := v.baseObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-1]) if obj == nil { - vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1])) return } + propName := toPropertyKey(vm.stack[vm.sp-1]) prop := obj.get(propName, v) if prop == nil { prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}} @@ -2411,12 +2453,12 @@ func (_getElemRecvCallee) exec(vm *vm) { recv := vm.stack[vm.sp-3] v := vm.stack[vm.sp-2] obj := v.baseObject(vm.r) - propName := toPropertyKey(vm.stack[vm.sp-1]) if obj == nil { - vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1])) return } + propName := toPropertyKey(vm.stack[vm.sp-1]) prop := obj.get(propName, recv) if prop == nil { prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}} @@ -2654,7 +2696,7 @@ func (s resolveVar1) exec(vm *vm) { } } - ref = &objRef{ + ref = &objStrRef{ base: vm.r.globalObject, name: name, binding: true, @@ -2708,9 +2750,6 @@ func (d deleteGlobal) exec(vm *vm) { var ret bool if vm.r.globalObject.self.hasPropertyStr(name) { ret = vm.r.globalObject.self.deleteStr(name, false) - if ret { - delete(vm.r.global.varNames, name) - } } else { ret = true } @@ -2735,7 +2774,7 @@ func (s resolveVar1Strict) exec(vm *vm) { } if vm.r.globalObject.self.hasPropertyStr(name) { - ref = &objRef{ + ref = &objStrRef{ base: vm.r.globalObject, name: name, binding: true, @@ -3876,18 +3915,12 @@ func (vm *vm) checkBindVarsGlobal(names []unistring.String) { } func (vm *vm) createGlobalVarBindings(names []unistring.String, d bool) { - globalVarNames := vm.r.global.varNames - if globalVarNames == nil { - globalVarNames = make(map[unistring.String]struct{}) - vm.r.global.varNames = globalVarNames - } o := vm.r.globalObject.self - if bo, ok := o.(*baseObject); ok { + if bo, ok := o.(*templatedObject); ok { for _, name := range names { if !bo.hasOwnPropertyStr(name) && bo.extensible { bo._putProp(name, _undefined, true, true, d) } - globalVarNames[name] = struct{}{} } } else { var cf Flag @@ -3906,21 +3939,15 @@ func (vm *vm) createGlobalVarBindings(names []unistring.String, d bool) { }, true) o.setOwnStr(name, _undefined, false) } - globalVarNames[name] = struct{}{} } } } func (vm *vm) createGlobalFuncBindings(names []unistring.String, d bool) { - globalVarNames := vm.r.global.varNames - if globalVarNames == nil { - globalVarNames = make(map[unistring.String]struct{}) - vm.r.global.varNames = globalVarNames - } o := vm.r.globalObject.self b := vm.sp - len(names) - var shortcutObj *baseObject - if o, ok := o.(*baseObject); ok { + var shortcutObj *templatedObject + if o, ok := o.(*templatedObject); ok { shortcutObj = o } for i, name := range names { @@ -3948,7 +3975,6 @@ func (vm *vm) createGlobalFuncBindings(names []unistring.String, d bool) { o.setOwnStr(name, desc.Value, false) // not a bug, see https://262.ecma-international.org/#sec-createglobalfunctionbinding } } - globalVarNames[name] = struct{}{} } vm.sp = b } @@ -3978,9 +4004,6 @@ func (vm *vm) checkBindLexGlobal(names []unistring.String) { o := vm.r.globalObject.self s := &vm.r.global.stash for _, name := range names { - if _, exists := vm.r.global.varNames[name]; exists { - goto fail - } if _, exists := s.names[name]; exists { goto fail }