From 5df89c3d318663e84856824b8ce098c61543bd90 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 11 Apr 2020 14:25:58 +0100 Subject: [PATCH] Support for malformed UTF-16 strings and property keys. Missing String methods. (#146) --- array.go | 36 +-- array_sparse.go | 30 +-- ast/node.go | 9 +- builtin_array.go | 6 +- builtin_global.go | 16 +- builtin_json.go | 10 +- builtin_object.go | 13 +- builtin_proxy.go | 18 +- builtin_regexp.go | 26 +- builtin_string.go | 444 ++++++++++++++++++++++++++++++++--- builtin_string_test.go | 22 ++ builtin_symbol.go | 55 +++-- builtin_typedarrays.go | 4 +- compiler.go | 32 +-- compiler_expr.go | 26 +- compiler_stmt.go | 58 +++-- func.go | 36 +-- map.go | 8 +- object.go | 159 +++++++------ object_args.go | 14 +- object_gomap.go | 42 ++-- object_gomap_reflect.go | 43 ++-- object_gomap_reflect_test.go | 29 +++ object_gomap_test.go | 29 +++ object_goreflect.go | 49 ++-- object_goreflect_test.go | 17 ++ object_goslice.go | 33 +-- object_goslice_reflect.go | 28 ++- object_lazy.go | 28 ++- object_test.go | 12 + parser/expression.go | 47 ++-- parser/lexer.go | 305 ++++++++++++++++-------- parser/lexer_test.go | 10 +- parser/parser.go | 10 +- parser/parser_test.go | 91 +++---- parser/scope.go | 5 +- proxy.go | 53 +++-- regexp.go | 8 +- runtime.go | 78 +++--- runtime_test.go | 46 +++- string.go | 179 ++++++++++---- string_ascii.go | 44 ++-- string_unicode.go | 171 ++++++++++---- tc39_test.go | 118 +++++++--- typedarrays.go | 24 +- unistring/string.go | 122 ++++++++++ value.go | 137 +++++++---- vm.go | 146 ++++++------ 48 files changed, 2040 insertions(+), 886 deletions(-) create mode 100644 unistring/string.go diff --git a/array.go b/array.go index c5844f0e..fb69e096 100644 --- a/array.go +++ b/array.go @@ -5,6 +5,8 @@ import ( "math/bits" "reflect" "strconv" + + "github.com/dop251/goja/unistring" ) type arrayIterObject struct { @@ -158,7 +160,7 @@ func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value { return prop } -func (a *arrayObject) getOwnPropStr(name string) Value { +func (a *arrayObject) getOwnPropStr(name unistring.String) Value { if i := strToIdx(name); i != math.MaxUint32 { if i < uint32(len(a.values)) { return a.values[i] @@ -178,7 +180,7 @@ func (a *arrayObject) getOwnPropIdx(idx valueInt) Value { return nil } - return a.baseObject.getOwnPropStr(idx.String()) + return a.baseObject.getOwnPropStr(idx.string()) } func (a *arrayObject) sortLen() int64 { @@ -197,7 +199,7 @@ func (a *arrayObject) swap(i, j int64) { a.values[i], a.values[j] = a.values[j], a.values[i] } -func (a *arrayObject) getStr(name string, receiver Value) Value { +func (a *arrayObject) getStr(name unistring.String, receiver Value) Value { return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver) } @@ -210,7 +212,7 @@ func (a *arrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { if i := toIdx(idx); i != math.MaxUint32 { return a._setOwnIdx(i, val, throw) } else { - return a.baseObject.setOwnStr(idx.String(), val, throw) + return a.baseObject.setOwnStr(idx.string(), val, throw) } } @@ -259,7 +261,7 @@ func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool { return true } -func (a *arrayObject) setOwnStr(name string, val Value, throw bool) bool { +func (a *arrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool { if idx := strToIdx(name); idx != math.MaxUint32 { return a._setOwnIdx(idx, val, throw) } else { @@ -275,7 +277,7 @@ func (a *arrayObject) setForeignIdx(idx valueInt, val, receiver Value, throw boo return a._setForeignIdx(idx, a.getOwnPropIdx(idx), val, receiver, throw) } -func (a *arrayObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (a *arrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw) } @@ -286,7 +288,7 @@ type arrayPropIter struct { func (i *arrayPropIter) next() (propIterItem, iterNextFunc) { for i.idx < len(i.a.values) { - name := strconv.Itoa(i.idx) + name := unistring.String(strconv.Itoa(i.idx)) prop := i.a.values[i.idx] i.idx++ if prop != nil { @@ -318,7 +320,7 @@ func (a *arrayObject) ownKeys(all bool, accum []Value) []Value { return a.baseObject.ownKeys(all, accum) } -func (a *arrayObject) hasOwnPropertyStr(name string) bool { +func (a *arrayObject) hasOwnPropertyStr(name unistring.String) bool { if idx := strToIdx(name); idx != math.MaxUint32 { return idx < uint32(len(a.values)) && a.values[idx] != nil } else { @@ -330,7 +332,7 @@ func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool { if idx := toIdx(idx); idx != math.MaxUint32 { return idx < uint32(len(a.values)) && a.values[idx] != nil } - return a.baseObject.hasOwnPropertyStr(idx.String()) + return a.baseObject.hasOwnPropertyStr(idx.string()) } func (a *arrayObject) expand(idx uint32) bool { @@ -420,7 +422,7 @@ func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, th if idx < uint32(len(a.values)) { existing = a.values[idx] } - prop, ok := a.baseObject._defineOwnProperty(strconv.FormatUint(uint64(idx), 10), existing, desc, throw) + prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw) if ok { if idx >= a.length { if !a.setLengthInt(int64(idx)+1, throw) { @@ -440,7 +442,7 @@ func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, th return ok } -func (a *arrayObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if idx := strToIdx(name); idx != math.MaxUint32 { return a._defineIdxProperty(idx, descr, throw) } @@ -454,7 +456,7 @@ func (a *arrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescripto if idx := toIdx(idx); idx != math.MaxUint32 { return a._defineIdxProperty(idx, descr, throw) } - return a.baseObject.defineOwnPropertyStr(idx.String(), descr, throw) + return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw) } func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool { @@ -474,7 +476,7 @@ func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool { return true } -func (a *arrayObject) deleteStr(name string, throw bool) bool { +func (a *arrayObject) deleteStr(name unistring.String, throw bool) bool { if idx := strToIdx(name); idx != math.MaxUint32 { return a._deleteIdxProp(idx, throw) } @@ -485,7 +487,7 @@ func (a *arrayObject) deleteIdx(idx valueInt, throw bool) bool { if idx := toIdx(idx); idx != math.MaxUint32 { return a._deleteIdxProp(idx, throw) } - return a.baseObject.deleteStr(idx.String(), throw) + return a.baseObject.deleteStr(idx.string(), throw) } func (a *arrayObject) export() interface{} { @@ -518,7 +520,7 @@ func toIdx(v valueInt) uint32 { return math.MaxUint32 } -func strToIdx64(s string) int64 { +func strToIdx64(s unistring.String) int64 { if s == "" { return -1 } @@ -567,7 +569,7 @@ func strToIdx64(s string) int64 { return n1 } -func strToIdx(s string) uint32 { +func strToIdx(s unistring.String) uint32 { if s == "" { return math.MaxUint32 } @@ -617,7 +619,7 @@ func strToIdx(s string) uint32 { return n1 } -func strToGoIdx(s string) int { +func strToGoIdx(s unistring.String) int { if bits.UintSize == 64 { return int(strToIdx64(s)) } diff --git a/array_sparse.go b/array_sparse.go index 69456b6d..17b64c5b 100644 --- a/array_sparse.go +++ b/array_sparse.go @@ -6,6 +6,8 @@ import ( "reflect" "sort" "strconv" + + "github.com/dop251/goja/unistring" ) type sparseArrayItem struct { @@ -109,7 +111,7 @@ func (a *sparseArrayObject) _getIdx(idx uint32) Value { return nil } -func (a *sparseArrayObject) getStr(name string, receiver Value) Value { +func (a *sparseArrayObject) getStr(name unistring.String, receiver Value) Value { return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver) } @@ -137,7 +139,7 @@ func (a *sparseArrayObject) getLengthProp() Value { return &a.lengthProp } -func (a *sparseArrayObject) getOwnPropStr(name string) Value { +func (a *sparseArrayObject) getOwnPropStr(name unistring.String) Value { if idx := strToIdx(name); idx != math.MaxUint32 { return a._getIdx(idx) } @@ -151,7 +153,7 @@ func (a *sparseArrayObject) getOwnPropIdx(idx valueInt) Value { if idx := toIdx(idx); idx != math.MaxUint32 { return a._getIdx(idx) } - return a.baseObject.getOwnPropStr(idx.String()) + return a.baseObject.getOwnPropStr(idx.string()) } func (a *sparseArrayObject) add(idx uint32, val Value) { @@ -218,7 +220,7 @@ func (a *sparseArrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool { return true } -func (a *sparseArrayObject) setOwnStr(name string, val Value, throw bool) bool { +func (a *sparseArrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool { if idx := strToIdx(name); idx != math.MaxUint32 { return a._setOwnIdx(idx, val, throw) } else { @@ -235,10 +237,10 @@ func (a *sparseArrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool return a._setOwnIdx(idx, val, throw) } - return a.baseObject.setOwnStr(idx.String(), val, throw) + return a.baseObject.setOwnStr(idx.string(), val, throw) } -func (a *sparseArrayObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (a *sparseArrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw) } @@ -253,7 +255,7 @@ type sparseArrayPropIter struct { func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) { for i.idx < len(i.a.items) { - name := strconv.Itoa(int(i.a.items[i.idx].idx)) + name := unistring.String(strconv.Itoa(int(i.a.items[i.idx].idx))) prop := i.a.items[i.idx].value i.idx++ if prop != nil { @@ -299,7 +301,7 @@ func (a *sparseArrayObject) setValues(values []Value, objCount int) { } } -func (a *sparseArrayObject) hasOwnPropertyStr(name string) bool { +func (a *sparseArrayObject) hasOwnPropertyStr(name unistring.String) bool { if idx := strToIdx(name); idx != math.MaxUint32 { i := a.findIdx(idx) return i < len(a.items) && a.items[i].idx == idx @@ -314,7 +316,7 @@ func (a *sparseArrayObject) hasOwnPropertyIdx(idx valueInt) bool { return i < len(a.items) && a.items[i].idx == idx } - return a.baseObject.hasOwnPropertyStr(idx.String()) + return a.baseObject.hasOwnPropertyStr(idx.string()) } func (a *sparseArrayObject) expand(idx uint32) bool { @@ -345,7 +347,7 @@ func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescript if i < len(a.items) && a.items[i].idx == idx { existing = a.items[i].value } - prop, ok := a.baseObject._defineOwnProperty(strconv.FormatUint(uint64(idx), 10), existing, desc, throw) + prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw) if ok { if idx >= a.length { if !a.setLengthInt(int64(idx)+1, throw) { @@ -376,7 +378,7 @@ func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescript return ok } -func (a *sparseArrayObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (a *sparseArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if idx := strToIdx(name); idx != math.MaxUint32 { return a._defineIdxProperty(idx, descr, throw) } @@ -390,7 +392,7 @@ func (a *sparseArrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDes if idx := toIdx(idx); idx != math.MaxUint32 { return a._defineIdxProperty(idx, descr, throw) } - return a.baseObject.defineOwnPropertyStr(idx.String(), descr, throw) + return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw) } func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool { @@ -410,7 +412,7 @@ func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool { return true } -func (a *sparseArrayObject) deleteStr(name string, throw bool) bool { +func (a *sparseArrayObject) deleteStr(name unistring.String, throw bool) bool { if idx := strToIdx(name); idx != math.MaxUint32 { return a._deleteIdxProp(idx, throw) } @@ -421,7 +423,7 @@ func (a *sparseArrayObject) deleteIdx(idx valueInt, throw bool) bool { if idx := toIdx(idx); idx != math.MaxUint32 { return a._deleteIdxProp(idx, throw) } - return a.baseObject.deleteStr(idx.String(), throw) + return a.baseObject.deleteStr(idx.string(), throw) } func (a *sparseArrayObject) sortLen() int64 { diff --git a/ast/node.go b/ast/node.go index 8968fc5c..02460205 100644 --- a/ast/node.go +++ b/ast/node.go @@ -12,6 +12,7 @@ package ast import ( "github.com/dop251/goja/file" "github.com/dop251/goja/token" + "github.com/dop251/goja/unistring" "github.com/go-sourcemap/sourcemap" ) @@ -98,7 +99,7 @@ type ( } Identifier struct { - Name string + Name unistring.String Idx file.Idx } @@ -134,7 +135,7 @@ type ( } Property struct { - Key string + Key unistring.String Kind string Value Expression } @@ -153,7 +154,7 @@ type ( StringLiteral struct { Idx file.Idx Literal string - Value string + Value unistring.String } ThisExpression struct { @@ -168,7 +169,7 @@ type ( } VariableExpression struct { - Name string + Name unistring.String Idx file.Idx Initializer Expression } diff --git a/builtin_array.go b/builtin_array.go index 307e019a..0fadea6a 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -1289,7 +1289,7 @@ type arraySortCtx struct { compare func(FunctionCall) Value } -func (ctx *arraySortCtx) sortCompare(x, y Value) int { +func (a *arraySortCtx) sortCompare(x, y Value) int { if x == nil && y == nil { return 0 } @@ -1314,8 +1314,8 @@ func (ctx *arraySortCtx) sortCompare(x, y Value) int { return -1 } - if ctx.compare != nil { - f := ctx.compare(FunctionCall{ + if a.compare != nil { + f := a.compare(FunctionCall{ This: _undefined, Arguments: []Value{x, y}, }).ToFloat() diff --git a/builtin_global.go b/builtin_global.go index 2f22d62d..68853d1c 100644 --- a/builtin_global.go +++ b/builtin_global.go @@ -2,12 +2,12 @@ package goja import ( "errors" + "github.com/dop251/goja/unistring" "io" "math" "regexp" "strconv" "strings" - "unicode/utf16" "unicode/utf8" ) @@ -95,7 +95,7 @@ func (r *Runtime) _encode(uriString valueString, unescaped *[256]bool) valueStri reader = uriString.reader(0) for { rn, _, err := reader.ReadRune() - if err != nil { + if err == io.EOF { break } @@ -189,7 +189,7 @@ func (r *Runtime) _decode(sv valueString, reservedSet *[256]bool) valueString { us = append(us, rn) t = t[size:] } - return unicodeString(utf16.Encode(us)) + return unicodeStringFromRunes(us) } func ishex(c byte) bool { @@ -240,7 +240,7 @@ func (r *Runtime) builtin_escape(call FunctionCall) Value { s := call.Argument(0).toString() var sb strings.Builder l := s.length() - for i := int64(0); i < l; i++ { + for i := 0; i < l; i++ { r := uint16(s.charAt(i)) if r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '@' || r == '*' || r == '_' || r == '+' || r == '-' || r == '.' || r == '/' { @@ -267,11 +267,12 @@ func (r *Runtime) builtin_unescape(call FunctionCall) Value { var asciiBuf []byte var unicodeBuf []uint16 if unicode { - unicodeBuf = make([]uint16, 0, l) + unicodeBuf = make([]uint16, 1, l+1) + unicodeBuf[0] = unistring.BOM } else { asciiBuf = make([]byte, 0, l) } - for i := int64(0); i < l; { + for i := 0; i < l; { r := s.charAt(i) if r == '%' { if i <= l-6 && s.charAt(i+1) == 'u' { @@ -303,7 +304,8 @@ func (r *Runtime) builtin_unescape(call FunctionCall) Value { } out: if r >= utf8.RuneSelf && !unicode { - unicodeBuf = make([]uint16, 0, l) + unicodeBuf = make([]uint16, 1, l+1) + unicodeBuf[0] = unistring.BOM for _, b := range asciiBuf { unicodeBuf = append(unicodeBuf, uint16(b)) } diff --git a/builtin_json.go b/builtin_json.go index dea31878..c4de85fb 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -7,9 +7,11 @@ import ( "io" "math" "strings" + + "github.com/dop251/goja/unistring" ) -var hex = "0123456789abcdef" +const hex = "0123456789abcdef" func (r *Runtime) builtinJSON_parse(call FunctionCall) Value { d := json.NewDecoder(bytes.NewBufferString(call.Argument(0).String())) @@ -85,7 +87,7 @@ func (r *Runtime) builtinJSON_decodeObject(d *json.Decoder) (*Object, error) { return nil, err } - object.self._putProp(key, value, true, true, true) + object.self._putProp(unistring.NewFromString(key), value, true, true, true) } return object, nil } @@ -150,9 +152,9 @@ func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holde for _, itemName := range object.self.ownKeys(false, nil) { value := r.builtinJSON_reviveWalk(reviver, object, name) if value == _undefined { - object.self.deleteStr(itemName.String(), false) + object.self.deleteStr(itemName.string(), false) } else { - object.self.setOwnStr(itemName.String(), value, false) + object.self.setOwnStr(itemName.string(), value, false) } } } diff --git a/builtin_object.go b/builtin_object.go index bd679026..71010e21 100644 --- a/builtin_object.go +++ b/builtin_object.go @@ -82,7 +82,7 @@ func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value { func (r *Runtime) object_getOwnPropertySymbols(call FunctionCall) Value { obj := call.Argument(0).ToObject(r) - return r.newArrayValues(obj.self.ownSymbols()) + return r.newArrayValues(obj.self.ownSymbols(true, nil)) } func (r *Runtime) toValueProp(v Value) *valueProperty { @@ -179,21 +179,20 @@ func (r *Runtime) toPropertyDescriptor(v Value) (ret PropertyDescriptor) { func (r *Runtime) _defineProperties(o *Object, p Value) { type propItem struct { - name string + name Value prop PropertyDescriptor } props := p.ToObject(r) - names := props.self.ownKeys(false, nil) + names := props.self.ownPropertyKeys(false, nil) list := make([]propItem, 0, len(names)) for _, itemName := range names { - itemNameStr := itemName.String() list = append(list, propItem{ - name: itemNameStr, - prop: r.toPropertyDescriptor(props.self.getStr(itemNameStr, nil)), + name: itemName, + prop: r.toPropertyDescriptor(props.get(itemName, nil)), }) } for _, prop := range list { - o.self.defineOwnPropertyStr(prop.name, prop.prop, true) + o.defineOwnProperty(prop.name, prop.prop, true) } } diff --git a/builtin_proxy.go b/builtin_proxy.go index 7f2f479a..2a3b87c7 100644 --- a/builtin_proxy.go +++ b/builtin_proxy.go @@ -1,6 +1,10 @@ package goja -import "fmt" +import ( + "fmt" + + "github.com/dop251/goja/unistring" +) func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) *Object { handler := r.NewObject() @@ -22,14 +26,14 @@ func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) *Object func (r *Runtime) proxyproto_nativehandler_gen_obj_obj(name proxyTrap, native func(*Object) *Object, handler *Object) { if native != nil { - handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value { + handler.self._putProp(unistring.String(name), r.newNativeFunc(func(call FunctionCall) Value { if len(call.Arguments) >= 1 { if t, ok := call.Argument(0).(*Object); ok { return native(t) } } panic(r.NewTypeError("%s needs to be called with target as Object", name)) - }, nil, fmt.Sprintf("[native %s]", name), nil, 1), true, true, true) + }, nil, unistring.String(fmt.Sprintf("[native %s]", name)), nil, 1), true, true, true) } } @@ -51,7 +55,7 @@ func (r *Runtime) proxyproto_nativehandler_setPrototypeOf(native func(*Object, * func (r *Runtime) proxyproto_nativehandler_gen_obj_bool(name proxyTrap, native func(*Object) bool, handler *Object) { if native != nil { - handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value { + handler.self._putProp(unistring.String(name), r.newNativeFunc(func(call FunctionCall) Value { if len(call.Arguments) >= 1 { if t, ok := call.Argument(0).(*Object); ok { s := native(t) @@ -59,7 +63,7 @@ func (r *Runtime) proxyproto_nativehandler_gen_obj_bool(name proxyTrap, native f } } panic(r.NewTypeError("%s needs to be called with target as Object", name)) - }, nil, fmt.Sprintf("[native %s]", name), nil, 1), true, true, true) + }, nil, unistring.String(fmt.Sprintf("[native %s]", name)), nil, 1), true, true, true) } } @@ -98,7 +102,7 @@ func (r *Runtime) proxyproto_nativehandler_defineProperty(native func(*Object, s func (r *Runtime) proxyproto_nativehandler_gen_obj_string_bool(name proxyTrap, native func(*Object, string) bool, handler *Object) { if native != nil { - handler.self._putProp(string(name), r.newNativeFunc(func(call FunctionCall) Value { + handler.self._putProp(unistring.String(name), r.newNativeFunc(func(call FunctionCall) Value { if len(call.Arguments) >= 2 { if t, ok := call.Argument(0).(*Object); ok { if p, ok := call.Argument(1).(valueString); ok { @@ -108,7 +112,7 @@ func (r *Runtime) proxyproto_nativehandler_gen_obj_string_bool(name proxyTrap, n } } panic(r.NewTypeError("%s needs to be called with target as Object and property as string", name)) - }, nil, fmt.Sprintf("[native %s]", name), nil, 2), true, true, true) + }, nil, unistring.String(fmt.Sprintf("[native %s]", name)), nil, 2), true, true, true) } } diff --git a/builtin_regexp.go b/builtin_regexp.go index 573dc765..e3a54da0 100644 --- a/builtin_regexp.go +++ b/builtin_regexp.go @@ -389,7 +389,7 @@ func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value { } else { previousLastIndex = thisIndex } - a = append(a, s.substring(int64(result[0]), int64(result[1]))) + a = append(a, s.substring(result[0], result[1])) } if len(a) == 0 { return _null @@ -447,11 +447,11 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString } else { lim = toLength(limit) } - size := s.length() - p := int64(0) if lim == 0 { return r.newArrayValues(a) } + size := s.length() + p := 0 execFn := toMethod(splitter.ToObject(r).self.getStr("exec", nil)) // must be non-nil if size == 0 { @@ -463,21 +463,25 @@ func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s valueString q := p for q < size { - splitter.self.setOwnStr("lastIndex", intToValue(q), true) + splitter.self.setOwnStr("lastIndex", intToValue(int64(q)), true) z := r.regExpExec(execFn, splitter, s) if z == _null { q++ } else { z := r.toObject(z) e := toLength(splitter.self.getStr("lastIndex", nil)) - if e == p { + if e == int64(p) { q++ } else { a = append(a, s.substring(p, q)) if int64(len(a)) == lim { return r.newArrayValues(a) } - p = e + if e > int64(size) { + p = size + } else { + p = int(e) + } 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)) @@ -529,13 +533,13 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value { for _, match := range result { if match[0] == match[1] { // FIXME Ugh, this is a hack - if match[0] == 0 || int64(match[0]) == targetLength { + if match[0] == 0 || match[0] == targetLength { continue } } if lastIndex != match[0] { - valueArray = append(valueArray, s.substring(int64(lastIndex), int64(match[0]))) + valueArray = append(valueArray, s.substring(lastIndex, match[0])) found++ } else if lastIndex == match[0] { if lastIndex != -1 { @@ -554,7 +558,7 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value { offset := index * 2 var value Value if match[offset] != -1 { - value = s.substring(int64(match[offset]), int64(match[offset+1])) + value = s.substring(match[offset], match[offset+1]) } else { value = _undefined } @@ -567,8 +571,8 @@ func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value { } if found != limit { - if int64(lastIndex) != targetLength { - valueArray = append(valueArray, s.substring(int64(lastIndex), targetLength)) + if lastIndex != targetLength { + valueArray = append(valueArray, s.substring(lastIndex, targetLength)) } else { valueArray = append(valueArray, stringEmpty) } diff --git a/builtin_string.go b/builtin_string.go index cfb39d37..6373520c 100644 --- a/builtin_string.go +++ b/builtin_string.go @@ -2,13 +2,16 @@ package goja import ( "bytes" + "github.com/dop251/goja/unistring" + "math" + "strings" + "unicode/utf16" + "unicode/utf8" + "github.com/dop251/goja/parser" "golang.org/x/text/collate" "golang.org/x/text/language" "golang.org/x/text/unicode/norm" - "math" - "strings" - "unicode/utf8" ) func (r *Runtime) collator() *collate.Collator { @@ -25,7 +28,7 @@ func toString(arg Value) valueString { return s } if s, ok := arg.(*valueSymbol); ok { - return newStringValue(s.descString()) + return s.desc } return arg.toString() } @@ -57,7 +60,7 @@ func (r *Runtime) _newString(s valueString, proto *Object) *Object { func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object { var s valueString if len(args) > 0 { - s = toString(args[0]) + s = args[0].toString() } else { s = stringEmpty } @@ -99,19 +102,26 @@ func (r *Runtime) stringproto_valueOf(call FunctionCall) Value { return r.stringproto_toStringValueOf(call.This, "valueOf") } +func (r *Runtime) stringproto_iterator(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + return r.createStringIterator(call.This.toString()) +} + func (r *Runtime) string_fromcharcode(call FunctionCall) Value { b := make([]byte, len(call.Arguments)) for i, arg := range call.Arguments { chr := toUint16(arg) if chr >= utf8.RuneSelf { - bb := make([]uint16, len(call.Arguments)) + bb := make([]uint16, len(call.Arguments)+1) + bb[0] = unistring.BOM + bb1 := bb[1:] for j := 0; j < i; j++ { - bb[j] = uint16(b[j]) + bb1[j] = uint16(b[j]) } - bb[i] = chr + bb1[i] = chr i++ for j, arg := range call.Arguments[i:] { - bb[i+j] = toUint16(arg) + bb1[i+j] = toUint16(arg) } return unicodeString(bb) } @@ -121,24 +131,107 @@ func (r *Runtime) string_fromcharcode(call FunctionCall) Value { return asciiString(b) } +func (r *Runtime) string_fromcodepoint(call FunctionCall) Value { + var b []byte + var sb unicodeStringBuilder + unicode := false + for i, arg := range call.Arguments { + num := arg.ToNumber() + var c rune + if numInt, ok := num.(valueInt); ok { + if numInt < 0 || numInt > utf8.MaxRune { + panic(r.newError(r.global.RangeError, "Invalid code point %d", numInt)) + } + c = rune(numInt) + } else { + panic(r.newError(r.global.RangeError, "Invalid code point %s", num)) + } + if c >= utf8.RuneSelf { + if !unicode { + unicode = true + sb.Grow(len(call.Arguments)) + sb.writeASCII(b[:i]) + b = nil + } + } + if unicode { + sb.writeRune(c) + } else { + if b == nil { + b = make([]byte, 0, len(call.Arguments)) + } + b = append(b, byte(c)) + } + } + if !unicode { + return asciiString(b) + } + return sb.string() +} + +func (r *Runtime) string_raw(call FunctionCall) Value { + cooked := call.Argument(0).ToObject(r) + raw := nilSafe(cooked.self.getStr("raw", nil)).ToObject(r) + literalSegments := toLength(raw.self.getStr("length", nil)) + if literalSegments <= 0 { + return stringEmpty + } + var stringElements unicodeStringBuilder + nextIndex := int64(0) + numberOfSubstitutions := int64(len(call.Arguments) - 1) + for { + nextSeg := nilSafe(raw.self.getIdx(valueInt(nextIndex), nil)).toString() + stringElements.writeString(nextSeg) + if nextIndex+1 == literalSegments { + return stringElements.string() + } + if nextIndex < numberOfSubstitutions { + stringElements.writeString(nilSafe(call.Arguments[nextIndex+1]).toString()) + } + nextIndex++ + } +} + func (r *Runtime) stringproto_charAt(call FunctionCall) Value { r.checkObjectCoercible(call.This) s := call.This.toString() pos := call.Argument(0).ToInteger() - if pos < 0 || pos >= s.length() { + if pos < 0 || pos >= int64(s.length()) { return stringEmpty } - return newStringValue(string(s.charAt(pos))) + return newStringValue(string(s.charAt(toInt(pos)))) } func (r *Runtime) stringproto_charCodeAt(call FunctionCall) Value { r.checkObjectCoercible(call.This) s := call.This.toString() pos := call.Argument(0).ToInteger() - if pos < 0 || pos >= s.length() { + if pos < 0 || pos >= int64(s.length()) { return _NaN } - return intToValue(int64(s.charAt(pos) & 0xFFFF)) + return intToValue(int64(s.charAt(toInt(pos)) & 0xFFFF)) +} + +func (r *Runtime) stringproto_codePointAt(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + p := call.Argument(0).ToInteger() + size := s.length() + if p < 0 || p >= int64(size) { + return _undefined + } + pos := toInt(p) + first := s.charAt(pos) + if isUTF16FirstSurrogate(first) { + pos++ + if pos < size { + second := s.charAt(pos) + if isUTF16SecondSurrogate(second) { + return intToValue(int64(utf16.DecodeRune(first, second))) + } + } + } + return intToValue(int64(first & 0xFFFF)) } func (r *Runtime) stringproto_concat(call FunctionCall) Value { @@ -163,8 +256,9 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value { } return asciiString(buf.String()) } else { - buf := make([]uint16, totalLen) - pos := int64(0) + buf := make([]uint16, totalLen+1) + buf[0] = unistring.BOM + pos := 1 for _, s := range strs { switch s := s.(type) { case asciiString: @@ -173,7 +267,7 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value { pos++ } case unicodeString: - copy(buf[pos:], s) + copy(buf[pos:], s[1:]) pos += s.length() } } @@ -181,6 +275,56 @@ func (r *Runtime) stringproto_concat(call FunctionCall) Value { } } +func (r *Runtime) stringproto_endsWith(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + searchString := call.Argument(0) + if isRegexp(searchString) { + panic(r.NewTypeError("First argument to String.prototype.endsWith must not be a regular expression")) + } + searchStr := searchString.toString() + l := int64(s.length()) + var pos int64 + if posArg := call.Argument(1); posArg != _undefined { + pos = posArg.ToInteger() + } else { + pos = l + } + end := toInt(min(max(pos, 0), l)) + searchLength := searchStr.length() + start := end - searchLength + if start < 0 { + return valueFalse + } + for i := 0; i < searchLength; i++ { + if s.charAt(start+i) != searchStr.charAt(i) { + return valueFalse + } + } + return valueTrue +} + +func (r *Runtime) stringproto_includes(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + searchString := call.Argument(0) + if isRegexp(searchString) { + panic(r.NewTypeError("First argument to String.prototype.includes must not be a regular expression")) + } + searchStr := searchString.toString() + var pos int64 + if posArg := call.Argument(1); posArg != _undefined { + pos = posArg.ToInteger() + } else { + pos = 0 + } + start := toInt(min(max(pos, 0), int64(s.length()))) + if s.index(searchStr, start) != -1 { + return valueTrue + } + return valueFalse +} + func (r *Runtime) stringproto_indexOf(call FunctionCall) Value { r.checkObjectCoercible(call.This) value := call.This.toString() @@ -190,13 +334,13 @@ func (r *Runtime) stringproto_indexOf(call FunctionCall) Value { if pos < 0 { pos = 0 } else { - l := value.length() + l := int64(value.length()) if pos > l { pos = l } } - return intToValue(value.index(target, pos)) + return intToValue(int64(value.index(target, toInt(pos)))) } func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value { @@ -207,20 +351,20 @@ func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value { var pos int64 if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) { - pos = value.length() + pos = int64(value.length()) } else { pos = numPos.ToInteger() if pos < 0 { pos = 0 } else { - l := value.length() + l := int64(value.length()) if pos > l { pos = l } } } - return intToValue(value.lastIndex(target, pos)) + return intToValue(int64(value.lastIndex(target, toInt(pos)))) } func (r *Runtime) stringproto_localeCompare(call FunctionCall) Value { @@ -261,6 +405,173 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value { panic(r.NewTypeError("RegExp matcher is not a function")) } +func (r *Runtime) stringproto_normalize(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + var form string + if formArg := call.Argument(0); formArg != _undefined { + form = formArg.toString().String() + } else { + form = "NFC" + } + var f norm.Form + switch form { + case "NFC": + f = norm.NFC + case "NFD": + f = norm.NFD + case "NFKC": + f = norm.NFKC + case "NFKD": + f = norm.NFKD + default: + panic(r.newError(r.global.RangeError, "The normalization form should be one of NFC, NFD, NFKC, NFKD")) + } + + if s, ok := s.(unicodeString); ok { + ss := s.String() + return newStringValue(f.String(ss)) + } + + return s +} + +func (r *Runtime) stringproto_padEnd(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + maxLength := toLength(call.Argument(0)) + stringLength := int64(s.length()) + if maxLength <= stringLength { + return s + } + var filler valueString + var fillerASCII bool + if fillString := call.Argument(1); fillString != _undefined { + filler = fillString.toString() + if filler.length() == 0 { + return s + } + _, fillerASCII = filler.(asciiString) + } else { + filler = asciiString(" ") + fillerASCII = true + } + remaining := toInt(maxLength - stringLength) + _, stringASCII := s.(asciiString) + if fillerASCII && stringASCII { + fl := filler.length() + var sb strings.Builder + sb.Grow(toInt(maxLength)) + sb.WriteString(s.String()) + fs := filler.String() + for remaining >= fl { + sb.WriteString(fs) + remaining -= fl + } + if remaining > 0 { + sb.WriteString(fs[:remaining]) + } + return asciiString(sb.String()) + } + var sb unicodeStringBuilder + sb.Grow(toInt(maxLength)) + sb.writeString(s) + fl := filler.length() + for remaining >= fl { + sb.writeString(filler) + remaining -= fl + } + if remaining > 0 { + sb.writeString(filler.substring(0, remaining)) + } + + return sb.string() +} + +func (r *Runtime) stringproto_padStart(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + maxLength := toLength(call.Argument(0)) + stringLength := int64(s.length()) + if maxLength <= stringLength { + return s + } + var filler valueString + var fillerASCII bool + if fillString := call.Argument(1); fillString != _undefined { + filler = fillString.toString() + if filler.length() == 0 { + return s + } + _, fillerASCII = filler.(asciiString) + } else { + filler = asciiString(" ") + fillerASCII = true + } + remaining := toInt(maxLength - stringLength) + _, stringASCII := s.(asciiString) + if fillerASCII && stringASCII { + fl := filler.length() + var sb strings.Builder + sb.Grow(toInt(maxLength)) + fs := filler.String() + for remaining >= fl { + sb.WriteString(fs) + remaining -= fl + } + if remaining > 0 { + sb.WriteString(fs[:remaining]) + } + sb.WriteString(s.String()) + return asciiString(sb.String()) + } + var sb unicodeStringBuilder + sb.Grow(toInt(maxLength)) + fl := filler.length() + for remaining >= fl { + sb.writeString(filler) + remaining -= fl + } + if remaining > 0 { + sb.writeString(filler.substring(0, remaining)) + } + sb.writeString(s) + + return sb.string() +} + +func (r *Runtime) stringproto_repeat(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + n := call.Argument(0).ToNumber() + if n == _positiveInf { + panic(r.newError(r.global.RangeError, "Invalid count value")) + } + numInt := n.ToInteger() + if numInt < 0 { + panic(r.newError(r.global.RangeError, "Invalid count value")) + } + if numInt == 0 || s.length() == 0 { + return stringEmpty + } + num := toInt(numInt) + if s, ok := s.(asciiString); ok { + var sb strings.Builder + sb.Grow(len(s) * num) + for i := 0; i < num; i++ { + sb.WriteString(string(s)) + } + return asciiString(sb.String()) + } + + var sb unicodeStringBuilder + sb.Grow(s.length() * num) + for i := 0; i < num; i++ { + sb.writeString(s) + } + return sb.string() +} + func (r *Runtime) stringproto_replace(call FunctionCall) Value { r.checkObjectCoercible(call.This) searchValue := call.Argument(0) @@ -448,7 +759,7 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value { r.checkObjectCoercible(call.This) s := call.This.toString() - l := s.length() + l := int64(s.length()) start := call.Argument(0).ToInteger() var end int64 if arg1 := call.Argument(1); arg1 != _undefined { @@ -480,7 +791,7 @@ func (r *Runtime) stringproto_slice(call FunctionCall) Value { } if end > start { - return s.substring(start, end) + return s.substring(int(start), int(end)) } return stringEmpty } @@ -539,11 +850,37 @@ func (r *Runtime) stringproto_split(call FunctionCall) Value { return r.newArrayValues(valueArray) } +func (r *Runtime) stringproto_startsWith(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + searchString := call.Argument(0) + if isRegexp(searchString) { + panic(r.NewTypeError("First argument to String.prototype.startsWith must not be a regular expression")) + } + searchStr := searchString.toString() + l := int64(s.length()) + var pos int64 + if posArg := call.Argument(1); posArg != _undefined { + pos = posArg.ToInteger() + } + start := toInt(min(max(pos, 0), l)) + searchLength := searchStr.length() + if int64(searchLength+start) > l { + return valueFalse + } + for i := 0; i < searchLength; i++ { + if s.charAt(start+i) != searchStr.charAt(i) { + return valueFalse + } + } + return valueTrue +} + func (r *Runtime) stringproto_substring(call FunctionCall) Value { r.checkObjectCoercible(call.This) s := call.This.toString() - l := s.length() + l := int64(s.length()) intStart := call.Argument(0).ToInteger() var intEnd int64 if end := call.Argument(1); end != _undefined { @@ -567,7 +904,7 @@ func (r *Runtime) stringproto_substring(call FunctionCall) Value { intStart, intEnd = intEnd, intStart } - return s.substring(intStart, intEnd) + return s.substring(int(intStart), int(intEnd)) } func (r *Runtime) stringproto_toLowerCase(call FunctionCall) Value { @@ -591,7 +928,22 @@ func (r *Runtime) stringproto_trim(call FunctionCall) Value { return newStringValue(strings.Trim(s.String(), parser.WhitespaceChars)) } +func (r *Runtime) stringproto_trimEnd(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + return newStringValue(strings.TrimRight(s.String(), parser.WhitespaceChars)) +} + +func (r *Runtime) stringproto_trimStart(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + return newStringValue(strings.TrimLeft(s.String(), parser.WhitespaceChars)) +} + func (r *Runtime) stringproto_substr(call FunctionCall) Value { + r.checkObjectCoercible(call.This) s := call.This.toString() start := call.Argument(0).ToInteger() var length int64 @@ -611,32 +963,62 @@ func (r *Runtime) stringproto_substr(call FunctionCall) Value { return stringEmpty } - return s.substring(start, start+length) + return s.substring(int(start), int(start+length)) +} + +func (r *Runtime) stringIterProto_next(call FunctionCall) Value { + thisObj := r.toObject(call.This) + if iter, ok := thisObj.self.(*stringIterObject); ok { + return iter.next() + } + panic(r.NewTypeError("Method String Iterator.prototype.next called on incompatible receiver %s", thisObj.String())) +} + +func (r *Runtime) createStringIterProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject) + + o._putProp("next", r.newNativeFunc(r.stringIterProto_next, nil, "next", nil, 0), true, false, true) + o._putSym(symToStringTag, valueProp(asciiString(classStringIterator), false, false, true)) + + return o } func (r *Runtime) initString() { + r.global.StringIteratorPrototype = r.newLazyObject(r.createStringIterProto) r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty}, r.global.ObjectPrototype) o := r.global.StringPrototype.self - o._putProp("toString", r.newNativeFunc(r.stringproto_toString, nil, "toString", nil, 0), true, false, true) - o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true) o._putProp("charAt", r.newNativeFunc(r.stringproto_charAt, nil, "charAt", nil, 1), true, false, true) o._putProp("charCodeAt", r.newNativeFunc(r.stringproto_charCodeAt, nil, "charCodeAt", nil, 1), true, false, true) + o._putProp("codePointAt", r.newNativeFunc(r.stringproto_codePointAt, nil, "codePointAt", nil, 1), true, false, true) o._putProp("concat", r.newNativeFunc(r.stringproto_concat, nil, "concat", nil, 1), true, false, true) + o._putProp("endsWith", r.newNativeFunc(r.stringproto_endsWith, nil, "endsWith", nil, 1), true, false, true) + o._putProp("includes", r.newNativeFunc(r.stringproto_includes, nil, "includes", nil, 1), true, false, true) o._putProp("indexOf", r.newNativeFunc(r.stringproto_indexOf, nil, "indexOf", nil, 1), true, false, true) o._putProp("lastIndexOf", r.newNativeFunc(r.stringproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true) o._putProp("localeCompare", r.newNativeFunc(r.stringproto_localeCompare, nil, "localeCompare", nil, 1), true, false, true) o._putProp("match", r.newNativeFunc(r.stringproto_match, nil, "match", nil, 1), true, false, true) + o._putProp("normalize", r.newNativeFunc(r.stringproto_normalize, nil, "normalize", nil, 0), true, false, true) + o._putProp("padEnd", r.newNativeFunc(r.stringproto_padEnd, nil, "padEnd", nil, 1), true, false, true) + o._putProp("padStart", r.newNativeFunc(r.stringproto_padStart, nil, "padStart", nil, 1), true, false, true) + o._putProp("repeat", r.newNativeFunc(r.stringproto_repeat, nil, "repeat", nil, 1), true, false, true) o._putProp("replace", r.newNativeFunc(r.stringproto_replace, nil, "replace", nil, 2), true, false, true) o._putProp("search", r.newNativeFunc(r.stringproto_search, nil, "search", nil, 1), true, false, true) o._putProp("slice", r.newNativeFunc(r.stringproto_slice, nil, "slice", nil, 2), true, false, true) o._putProp("split", r.newNativeFunc(r.stringproto_split, nil, "split", nil, 2), true, false, true) + o._putProp("startsWith", r.newNativeFunc(r.stringproto_startsWith, nil, "startsWith", nil, 1), true, false, true) o._putProp("substring", r.newNativeFunc(r.stringproto_substring, nil, "substring", nil, 2), true, false, true) - o._putProp("toLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLowerCase", nil, 0), true, false, true) o._putProp("toLocaleLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLocaleLowerCase", nil, 0), true, false, true) - o._putProp("toUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toUpperCase", nil, 0), true, false, true) o._putProp("toLocaleUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toLocaleUpperCase", nil, 0), true, false, true) + o._putProp("toLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLowerCase", nil, 0), true, false, true) + o._putProp("toString", r.newNativeFunc(r.stringproto_toString, nil, "toString", nil, 0), true, false, true) + o._putProp("toUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toUpperCase", nil, 0), true, false, true) o._putProp("trim", r.newNativeFunc(r.stringproto_trim, nil, "trim", nil, 0), true, false, true) + o._putProp("trimEnd", r.newNativeFunc(r.stringproto_trimEnd, nil, "trimEnd", nil, 0), true, false, true) + o._putProp("trimStart", r.newNativeFunc(r.stringproto_trimStart, nil, "trimStart", nil, 0), true, false, true) + o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true) + + o._putSym(symIterator, valueProp(r.newNativeFunc(r.stringproto_iterator, nil, "[Symbol.iterator]", nil, 0), true, false, true)) // Annex B o._putProp("substr", r.newNativeFunc(r.stringproto_substr, nil, "substr", nil, 2), true, false, true) @@ -644,6 +1026,8 @@ func (r *Runtime) initString() { r.global.String = r.newNativeFunc(r.builtin_String, r.builtin_newString, "String", r.global.StringPrototype, 1) o = r.global.String.self o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, nil, "fromCharCode", nil, 1), true, false, true) + o._putProp("fromCodePoint", r.newNativeFunc(r.string_fromcodepoint, nil, "fromCodePoint", nil, 1), true, false, true) + o._putProp("raw", r.newNativeFunc(r.string_raw, nil, "raw", nil, 1), true, false, true) r.addToGlobal("String", r.global.String) diff --git a/builtin_string_test.go b/builtin_string_test.go index 8f538ee0..dbbb71c7 100644 --- a/builtin_string_test.go +++ b/builtin_string_test.go @@ -145,3 +145,25 @@ res.length === 3 && res[0] === "a" && res[1] === "b" && res[2] === "c"; ` testScript1(SCRIPT, valueTrue, t) } + +func TestStringIterSurrPair(t *testing.T) { + const SCRIPT = ` +var lo = '\uD834'; +var hi = '\uDF06'; +var pair = lo + hi; +var string = 'a' + pair + 'b' + lo + pair + hi + lo; +var iterator = string[Symbol.iterator](); +var result; + +result = iterator.next(); +if (result.value !== 'a') { + throw new Error("at 0: " + result.value); +} +result = iterator.next(); +if (result.value !== pair) { + throw new Error("at 1: " + result.value); +} + +` + testScript1(SCRIPT, _undefined, t) +} diff --git a/builtin_symbol.go b/builtin_symbol.go index 83346e14..1e90a824 100644 --- a/builtin_symbol.go +++ b/builtin_symbol.go @@ -1,27 +1,29 @@ package goja +import "github.com/dop251/goja/unistring" + var ( - symHasInstance = &valueSymbol{desc: "Symbol.hasInstance"} - symIsConcatSpreadable = &valueSymbol{desc: "Symbol.isConcatSpreadable"} - symIterator = &valueSymbol{desc: "Symbol.iterator"} - symMatch = &valueSymbol{desc: "Symbol.match"} - symReplace = &valueSymbol{desc: "Symbol.replace"} - symSearch = &valueSymbol{desc: "Symbol.search"} - symSpecies = &valueSymbol{desc: "Symbol.species"} - symSplit = &valueSymbol{desc: "Symbol.split"} - symToPrimitive = &valueSymbol{desc: "Symbol.toPrimitive"} - symToStringTag = &valueSymbol{desc: "Symbol.toStringTag"} - symUnscopables = &valueSymbol{desc: "Symbol.unscopables"} + symHasInstance = newSymbol(asciiString("Symbol.hasInstance")) + symIsConcatSpreadable = newSymbol(asciiString("Symbol.isConcatSpreadable")) + symIterator = newSymbol(asciiString("Symbol.iterator")) + symMatch = newSymbol(asciiString("Symbol.match")) + symReplace = newSymbol(asciiString("Symbol.replace")) + symSearch = newSymbol(asciiString("Symbol.search")) + symSpecies = newSymbol(asciiString("Symbol.species")) + symSplit = newSymbol(asciiString("Symbol.split")) + symToPrimitive = newSymbol(asciiString("Symbol.toPrimitive")) + symToStringTag = newSymbol(asciiString("Symbol.toStringTag")) + symUnscopables = newSymbol(asciiString("Symbol.unscopables")) ) func (r *Runtime) builtin_symbol(call FunctionCall) Value { - desc := "" + var desc valueString if arg := call.Argument(0); !IsUndefined(arg) { - desc = arg.toString().String() - } - return &valueSymbol{ - desc: desc, + desc = arg.toString() + } else { + desc = stringEmpty } + return newSymbol(desc) } func (r *Runtime) symbolproto_tostring(call FunctionCall) Value { @@ -38,7 +40,7 @@ func (r *Runtime) symbolproto_tostring(call FunctionCall) Value { if sym == nil { panic(r.NewTypeError("Method Symbol.prototype.toString is called on incompatible receiver")) } - return newStringValue(sym.descString()) + return sym.desc } func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value { @@ -59,17 +61,16 @@ func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value { } func (r *Runtime) symbol_for(call FunctionCall) Value { - key := call.Argument(0).toString().String() - if v := r.symbolRegistry[key]; v != nil { + key := call.Argument(0).toString() + keyStr := key.string() + if v := r.symbolRegistry[keyStr]; v != nil { return v } if r.symbolRegistry == nil { - r.symbolRegistry = make(map[string]*valueSymbol) - } - v := &valueSymbol{ - desc: key, + r.symbolRegistry = make(map[unistring.String]*valueSymbol) } - r.symbolRegistry[key] = v + v := newSymbol(key) + r.symbolRegistry[keyStr] = v return v } @@ -81,7 +82,7 @@ func (r *Runtime) symbol_keyfor(call FunctionCall) Value { } for key, s := range r.symbolRegistry { if s == sym { - return r.ToValue(key) + return stringValueFromRaw(key) } } return _undefined @@ -124,7 +125,9 @@ func (r *Runtime) createSymbol(val *Object) objectImpl { symToStringTag, symUnscopables, } { - o._putProp(s.desc[len("Symbol."):], s, false, false, false) + n := s.desc.(asciiString) + n = n[len("Symbol(Symbol.") : len(n)-1] + o._putProp(unistring.String(n), s, false, false, false) } return o diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 2fb0985f..00f54883 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -5,6 +5,8 @@ import ( "sort" "strings" "unsafe" + + "github.com/dop251/goja/unistring" ) type typedArraySortCtx struct { @@ -1377,7 +1379,7 @@ func (r *Runtime) addPrototype(ctor *Object, proto *Object) *baseObject { return p } -func (r *Runtime) typedArrayCreator(ctor func(args []Value, newTarget *Object) *Object, name string, bytesPerElement int) func(val *Object) objectImpl { +func (r *Runtime) typedArrayCreator(ctor func(args []Value, newTarget *Object) *Object, name unistring.String, bytesPerElement int) func(val *Object) objectImpl { return func(val *Object) objectImpl { o := r.newNativeConstructOnly(val, ctor, nil, name, 3) o.prototype = r.global.TypedArray diff --git a/compiler.go b/compiler.go index 385ac0dc..c8218918 100644 --- a/compiler.go +++ b/compiler.go @@ -2,10 +2,12 @@ package goja import ( "fmt" - "github.com/dop251/goja/ast" - "github.com/dop251/goja/file" "sort" "strconv" + + "github.com/dop251/goja/ast" + "github.com/dop251/goja/file" + "github.com/dop251/goja/unistring" ) const ( @@ -39,7 +41,7 @@ type Program struct { code []instruction values []Value - funcName string + funcName unistring.String src *SrcFile srcMap []srcMapItem } @@ -56,7 +58,7 @@ type compiler struct { } type scope struct { - names map[string]uint32 + names map[unistring.String]uint32 outer *scope strict bool eval bool @@ -66,13 +68,13 @@ type scope struct { argsNeeded bool thisNeeded bool - namesMap map[string]string + namesMap map[unistring.String]unistring.String lastFreeTmp int } type block struct { typ int - label string + label unistring.String needResult bool cont int breaks []int @@ -111,9 +113,9 @@ func (c *compiler) newScope() { } c.scope = &scope{ outer: c.scope, - names: make(map[string]uint32), + names: make(map[unistring.String]uint32), strict: strict, - namesMap: make(map[string]string), + namesMap: make(map[unistring.String]unistring.String), } } @@ -176,7 +178,7 @@ func (s *scope) isFunction() bool { return s.outer.isFunction() } -func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) { +func (s *scope) lookupName(name unistring.String) (idx uint32, found, noDynamics bool) { var level uint32 = 0 noDynamics = true for curScope := s; curScope != nil; curScope = curScope.outer { @@ -186,7 +188,7 @@ func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) { if curScope.dynamic { noDynamics = false } else { - var mapped string + var mapped unistring.String if m, exists := curScope.namesMap[name]; exists { mapped = m } else { @@ -210,7 +212,7 @@ func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) { return } -func (s *scope) bindName(name string) (uint32, bool) { +func (s *scope) bindName(name unistring.String) (uint32, bool) { if s.lexical { return s.outer.bindName(name) } @@ -223,7 +225,7 @@ func (s *scope) bindName(name string) (uint32, bool) { return idx, true } -func (s *scope) bindNameShadow(name string) (uint32, bool) { +func (s *scope) bindNameShadow(name unistring.String) (uint32, bool) { if s.lexical { return s.outer.bindName(name) } @@ -234,7 +236,7 @@ func (s *scope) bindNameShadow(name string) (uint32, bool) { unique = false // shadow the var delete(s.names, name) - n := strconv.Itoa(int(idx)) + n := unistring.String(strconv.Itoa(int(idx))) s.names[n] = idx } idx := uint32(len(s.names)) @@ -446,14 +448,14 @@ func (c *compiler) isStrictStatement(s ast.Statement) bool { return false } -func (c *compiler) checkIdentifierName(name string, offset int) { +func (c *compiler) checkIdentifierName(name unistring.String, offset int) { switch name { case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield": c.throwSyntaxError(offset, "Unexpected strict mode reserved word") } } -func (c *compiler) checkIdentifierLName(name string, offset int) { +func (c *compiler) checkIdentifierLName(name unistring.String, offset int) { switch name { case "eval", "arguments": c.throwSyntaxError(offset, "Assignment to eval or arguments is not allowed in strict mode") diff --git a/compiler_expr.go b/compiler_expr.go index 581208e8..2df63f28 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -2,10 +2,12 @@ package goja import ( "fmt" + "regexp" + "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" - "regexp" + "github.com/dop251/goja/unistring" ) var ( @@ -60,18 +62,18 @@ type compiledAssignExpr struct { type deleteGlobalExpr struct { baseCompiledExpr - name string + name unistring.String } type deleteVarExpr struct { baseCompiledExpr - name string + name unistring.String } type deletePropExpr struct { baseCompiledExpr left compiledExpr - name string + name unistring.String } type deleteElemExpr struct { @@ -91,7 +93,7 @@ type baseCompiledExpr struct { type compiledIdentifierExpr struct { baseCompiledExpr - name string + name unistring.String } type compiledFunctionLiteral struct { @@ -154,7 +156,7 @@ type compiledBinaryExpr struct { type compiledVariableExpr struct { baseCompiledExpr - name string + name unistring.String initializer compiledExpr expr *ast.VariableExpression } @@ -320,7 +322,7 @@ func (e *compiledIdentifierExpr) emitGetterOrRef() { } } -func (c *compiler) emitVarSetter1(name string, offset int, emitRight func(isRef bool)) { +func (c *compiler) emitVarSetter1(name unistring.String, offset int, emitRight func(isRef bool)) { if c.scope.strict { c.checkIdentifierLName(name, offset) } @@ -353,7 +355,7 @@ func (c *compiler) emitVarSetter1(name string, offset int, emitRight func(isRef } } -func (c *compiler) emitVarSetter(name string, offset int, valueExpr compiledExpr) { +func (c *compiler) emitVarSetter(name unistring.String, offset int, valueExpr compiledExpr) { c.emitVarSetter1(name, offset, func(bool) { c.emitExpr(valueExpr, true) }) @@ -432,7 +434,7 @@ func (e *compiledIdentifierExpr) deleteExpr() compiledExpr { type compiledDotExpr struct { baseCompiledExpr left compiledExpr - name string + name unistring.String } func (e *compiledDotExpr) emitGetter(putOnStack bool) { @@ -854,7 +856,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { e.c.popScope() e.c.p = savedPrg e.c.blockStart = savedBlockStart - name := "" + var name unistring.String if e.expr.Name != nil { name = e.expr.Name.Name } @@ -1444,7 +1446,7 @@ func (c *compiler) compileRegexpLiteral(v *ast.RegExpLiteral) compiledExpr { } func (e *compiledCallExpr) emitGetter(putOnStack bool) { - var calleeName string + var calleeName unistring.String switch callee := e.callee.(type) { case *compiledDotExpr: callee.left.emitGetter(true) @@ -1549,7 +1551,7 @@ func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr { func (c *compiler) compileStringLiteral(v *ast.StringLiteral) compiledExpr { r := &compiledLiteral{ - val: newStringValue(v.Value), + val: stringValueFromRaw(v.Value), } r.init(c, v.Idx0()) return r diff --git a/compiler_stmt.go b/compiler_stmt.go index 099920d7..d869921d 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -2,10 +2,12 @@ package goja import ( "fmt" + "strconv" + "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" - "strconv" + "github.com/dop251/goja/unistring" ) func (c *compiler) compileStatement(v ast.Statement, needResult bool) { @@ -126,7 +128,7 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement) { // remap newIdx, exists := m[idx] if !exists { - exname := " __tmp" + strconv.Itoa(c.scope.lastFreeTmp) + exname := unistring.String(" __tmp" + strconv.Itoa(c.scope.lastFreeTmp)) c.scope.lastFreeTmp++ newIdx, _ = c.scope.bindName(exname) m[idx] = newIdx @@ -207,7 +209,7 @@ func (c *compiler) compileDoWhileStatement(v *ast.DoWhileStatement, needResult b c.compileLabeledDoWhileStatement(v, needResult, "") } -func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label string) { +func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label unistring.String) { c.block = &block{ typ: blockLoop, outer: c.block, @@ -234,7 +236,7 @@ func (c *compiler) compileForStatement(v *ast.ForStatement, needResult bool) { c.compileLabeledForStatement(v, needResult, "") } -func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bool, label string) { +func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bool, label unistring.String) { c.block = &block{ typ: blockLoop, outer: c.block, @@ -306,7 +308,7 @@ func (c *compiler) compileForInStatement(v *ast.ForInStatement, needResult bool) c.compileLabeledForInStatement(v, needResult, "") } -func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResult bool, label string) { +func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResult bool, label unistring.String) { c.block = &block{ typ: blockLoop, outer: c.block, @@ -341,7 +343,7 @@ func (c *compiler) compileForOfStatement(v *ast.ForOfStatement, needResult bool) c.compileLabeledForOfStatement(v, needResult, "") } -func (c *compiler) compileLabeledForOfStatement(v *ast.ForOfStatement, needResult bool, label string) { +func (c *compiler) compileLabeledForOfStatement(v *ast.ForOfStatement, needResult bool, label unistring.String) { c.block = &block{ typ: blockLoop, outer: c.block, @@ -377,7 +379,7 @@ func (c *compiler) compileWhileStatement(v *ast.WhileStatement, needResult bool) c.compileLabeledWhileStatement(v, needResult, "") } -func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label string) { +func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label unistring.String) { c.block = &block{ typ: blockLoop, outer: c.block, @@ -517,6 +519,10 @@ func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) { break } } + if block == nil { + c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name) + return + } } else { // find the nearest loop or switch L: @@ -531,17 +537,17 @@ func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) { break L } } + if block == nil { + c.throwSyntaxError(int(idx)-1, "Could not find block") + return + } } - if block != nil { - if len(c.p.code) == c.blockStart && block.needResult { - c.emit(loadUndef) - } - block.breaks = append(block.breaks, len(c.p.code)) - c.emit(nil) - } else { - c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name) + if len(c.p.code) == c.blockStart && block.needResult { + c.emit(loadUndef) } + block.breaks = append(block.breaks, len(c.p.code)) + c.emit(nil) } func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) { @@ -555,6 +561,10 @@ func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) { break } } + if block == nil { + c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name) + return + } } else { // find the nearest loop for b := c.block; b != nil; b = b.outer { @@ -565,17 +575,17 @@ func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) { break } } + if block == nil { + c.throwSyntaxError(int(idx)-1, "Could not find block") + return + } } - if block != nil { - if len(c.p.code) == c.blockStart && block.needResult { - c.emit(loadUndef) - } - block.conts = append(block.conts, len(c.p.code)) - c.emit(nil) - } else { - c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name) + if len(c.p.code) == c.blockStart && block.needResult { + c.emit(loadUndef) } + block.conts = append(block.conts, len(c.p.code)) + c.emit(nil) } func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) { @@ -720,7 +730,7 @@ func (c *compiler) compileStatements(list []ast.Statement, needResult bool) { } } -func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bool, label string) { +func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bool, label unistring.String) { c.block = &block{ typ: blockBranch, outer: c.block, diff --git a/func.go b/func.go index 138afc65..10b6bbe4 100644 --- a/func.go +++ b/func.go @@ -1,6 +1,10 @@ package goja -import "reflect" +import ( + "reflect" + + "github.com/dop251/goja/unistring" +) type baseFuncObject struct { baseObject @@ -36,20 +40,20 @@ func (f *nativeFuncObject) exportType() reflect.Type { return reflect.TypeOf(f.f) } -func (f *funcObject) _addProto(n string) Value { +func (f *funcObject) _addProto(n unistring.String) Value { if n == "prototype" { - if _, exists := f.values["prototype"]; !exists { + if _, exists := f.values[n]; !exists { return f.addPrototype() } } return nil } -func (f *funcObject) getStr(p string, receiver Value) Value { +func (f *funcObject) getStr(p unistring.String, receiver Value) Value { return f.getStrWithOwnProp(f.getOwnPropStr(p), p, receiver) } -func (f *funcObject) getOwnPropStr(name string) Value { +func (f *funcObject) getOwnPropStr(name unistring.String) Value { if v := f._addProto(name); v != nil { return v } @@ -57,16 +61,16 @@ func (f *funcObject) getOwnPropStr(name string) Value { return f.baseObject.getOwnPropStr(name) } -func (f *funcObject) setOwnStr(name string, val Value, throw bool) bool { +func (f *funcObject) setOwnStr(name unistring.String, val Value, throw bool) bool { f._addProto(name) return f.baseObject.setOwnStr(name, val, throw) } -func (f *funcObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (f *funcObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return f._setForeignStr(name, f.getOwnPropStr(name), val, receiver, throw) } -func (f *funcObject) deleteStr(name string, throw bool) bool { +func (f *funcObject) deleteStr(name unistring.String, throw bool) bool { f._addProto(name) return f.baseObject.deleteStr(name, throw) } @@ -77,7 +81,7 @@ func (f *funcObject) addPrototype() Value { return f._putProp("prototype", proto, true, false, false) } -func (f *funcObject) hasOwnPropertyStr(name string) bool { +func (f *funcObject) hasOwnPropertyStr(name unistring.String) bool { if r := f.baseObject.hasOwnPropertyStr(name); r { return true } @@ -176,12 +180,12 @@ func (f *funcObject) assertConstructor() func(args []Value, newTarget *Object) * return f.construct } -func (f *baseFuncObject) init(name string, length int) { +func (f *baseFuncObject) init(name unistring.String, length int) { f.baseObject.init() if name != "" { f.nameProp.configurable = true - f.nameProp.value = newStringValue(name) + f.nameProp.value = stringValueFromRaw(name) f._put("name", &f.nameProp) } @@ -242,11 +246,11 @@ func (f *nativeFuncObject) assertConstructor() func(args []Value, newTarget *Obj return f.construct } -func (f *boundFuncObject) getStr(p string, receiver Value) Value { +func (f *boundFuncObject) getStr(p unistring.String, receiver Value) Value { return f.getStrWithOwnProp(f.getOwnPropStr(p), p, receiver) } -func (f *boundFuncObject) getOwnPropStr(name string) Value { +func (f *boundFuncObject) getOwnPropStr(name unistring.String) Value { if name == "caller" || name == "arguments" { return f.val.runtime.global.throwerProperty } @@ -254,21 +258,21 @@ func (f *boundFuncObject) getOwnPropStr(name string) Value { return f.nativeFuncObject.getOwnPropStr(name) } -func (f *boundFuncObject) deleteStr(name string, throw bool) bool { +func (f *boundFuncObject) deleteStr(name unistring.String, throw bool) bool { if name == "caller" || name == "arguments" { return true } return f.nativeFuncObject.deleteStr(name, throw) } -func (f *boundFuncObject) setOwnStr(name string, val Value, throw bool) bool { +func (f *boundFuncObject) setOwnStr(name unistring.String, val Value, throw bool) bool { if name == "caller" || name == "arguments" { panic(f.val.runtime.NewTypeError("'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.")) } return f.nativeFuncObject.setOwnStr(name, val, throw) } -func (f *boundFuncObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (f *boundFuncObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return f._setForeignStr(name, f.getOwnPropStr(name), val, receiver, throw) } diff --git a/map.go b/map.go index 907206e7..b092b0d0 100644 --- a/map.go +++ b/map.go @@ -1,6 +1,8 @@ package goja -import "hash" +import ( + "hash/maphash" +) type mapEntry struct { key, value Value @@ -10,7 +12,7 @@ type mapEntry struct { } type orderedMap struct { - hash hash.Hash64 + hash *maphash.Hash hashTable map[uint64]*mapEntry iterFirst, iterLast *mapEntry size int @@ -138,7 +140,7 @@ func (iter *orderedMapIter) close() { iter.cur = nil } -func newOrderedMap(h hash.Hash64) *orderedMap { +func newOrderedMap(h *maphash.Hash) *orderedMap { return &orderedMap{ hash: h, hashTable: make(map[uint64]*mapEntry), diff --git a/object.go b/object.go index be5bac5b..898f6860 100644 --- a/object.go +++ b/object.go @@ -7,6 +7,8 @@ import ( "runtime" "sort" "unsafe" + + "github.com/dop251/goja/unistring" ) const ( @@ -24,9 +26,10 @@ const ( classRegExp = "RegExp" classDate = "Date" - classArrayIterator = "Array Iterator" - classMapIterator = "Map Iterator" - classSetIterator = "Set Iterator" + classArrayIterator = "Array Iterator" + classMapIterator = "Map Iterator" + classSetIterator = "Set Iterator" + classStringIterator = "String Iterator" ) type weakCollection interface { @@ -171,35 +174,35 @@ func (p *PropertyDescriptor) complete() { type objectImpl interface { sortable className() string - getStr(p string, receiver Value) Value + getStr(p unistring.String, receiver Value) Value getIdx(p valueInt, receiver Value) Value getSym(p *valueSymbol, receiver Value) Value - getOwnPropStr(string) Value + getOwnPropStr(unistring.String) Value getOwnPropIdx(valueInt) Value getOwnPropSym(*valueSymbol) Value - setOwnStr(p string, v Value, throw bool) bool + setOwnStr(p unistring.String, v Value, throw bool) bool setOwnIdx(p valueInt, v Value, throw bool) bool setOwnSym(p *valueSymbol, v Value, throw bool) bool - setForeignStr(p string, v, receiver Value, throw bool) (res bool, handled bool) + setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) setForeignSym(p *valueSymbol, v, receiver Value, throw bool) (res bool, handled bool) - hasPropertyStr(string) bool + hasPropertyStr(unistring.String) bool hasPropertyIdx(idx valueInt) bool hasPropertySym(s *valueSymbol) bool - hasOwnPropertyStr(string) bool + hasOwnPropertyStr(unistring.String) bool hasOwnPropertyIdx(valueInt) bool hasOwnPropertySym(s *valueSymbol) bool - defineOwnPropertyStr(name string, desc PropertyDescriptor, throw bool) bool + defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool defineOwnPropertySym(name *valueSymbol, desc PropertyDescriptor, throw bool) bool - deleteStr(name string, throw bool) bool + deleteStr(name unistring.String, throw bool) bool deleteIdx(idx valueInt, throw bool) bool deleteSym(s *valueSymbol, throw bool) bool @@ -219,10 +222,10 @@ type objectImpl interface { exportType() reflect.Type equal(objectImpl) bool ownKeys(all bool, accum []Value) []Value - ownSymbols() []Value + ownSymbols(all bool, accum []Value) []Value ownPropertyKeys(all bool, accum []Value) []Value - _putProp(name string, value Value, writable, enumerable, configurable bool) Value + _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value _putSym(s *valueSymbol, prop Value) } @@ -232,8 +235,8 @@ type baseObject struct { prototype *Object extensible bool - values map[string]Value - propNames []string + values map[unistring.String]Value + propNames []unistring.String lastSortedPropLen, idxPropCount int @@ -278,14 +281,14 @@ func (f ConstructorCall) Argument(idx int) Value { } func (o *baseObject) init() { - o.values = make(map[string]Value) + o.values = make(map[unistring.String]Value) } func (o *baseObject) className() string { return o.class } -func (o *baseObject) hasPropertyStr(name string) bool { +func (o *baseObject) hasPropertyStr(name unistring.String) bool { if o.val.self.hasOwnPropertyStr(name) { return true } @@ -296,7 +299,7 @@ func (o *baseObject) hasPropertyStr(name string) bool { } func (o *baseObject) hasPropertyIdx(idx valueInt) bool { - return o.val.self.hasPropertyStr(idx.String()) + return o.val.self.hasPropertyStr(idx.string()) } func (o *baseObject) hasPropertySym(s *valueSymbol) bool { @@ -325,7 +328,7 @@ func (o *baseObject) getWithOwnProp(prop, p, receiver Value) Value { return prop } -func (o *baseObject) getStrWithOwnProp(prop Value, name string, receiver Value) Value { +func (o *baseObject) getStrWithOwnProp(prop Value, name unistring.String, receiver Value) Value { if prop == nil && o.prototype != nil { if receiver == nil { return o.prototype.self.getStr(name, o.val) @@ -342,14 +345,14 @@ func (o *baseObject) getStrWithOwnProp(prop Value, name string, receiver Value) } func (o *baseObject) getIdx(idx valueInt, receiver Value) Value { - return o.val.self.getStr(idx.String(), receiver) + return o.val.self.getStr(idx.string(), receiver) } func (o *baseObject) getSym(s *valueSymbol, receiver Value) Value { return o.getWithOwnProp(o.getOwnPropSym(s), s, receiver) } -func (o *baseObject) getStr(name string, receiver Value) Value { +func (o *baseObject) getStr(name unistring.String, receiver Value) Value { prop := o.values[name] if prop == nil { if o.prototype != nil { @@ -369,7 +372,7 @@ func (o *baseObject) getStr(name string, receiver Value) Value { } func (o *baseObject) getOwnPropIdx(idx valueInt) Value { - return o.val.self.getOwnPropStr(idx.String()) + return o.val.self.getOwnPropStr(idx.string()) } func (o *baseObject) getOwnPropSym(s *valueSymbol) Value { @@ -379,11 +382,11 @@ func (o *baseObject) getOwnPropSym(s *valueSymbol) Value { return nil } -func (o *baseObject) getOwnPropStr(name string) Value { +func (o *baseObject) getOwnPropStr(name unistring.String) Value { return o.values[name] } -func (o *baseObject) checkDeleteProp(name string, prop *valueProperty, throw bool) bool { +func (o *baseObject) checkDeleteProp(name unistring.String, prop *valueProperty, throw bool) bool { if !prop.configurable { o.val.runtime.typeErrorResult(throw, "Cannot delete property '%s' of %s", name, o.val.toString()) return false @@ -391,14 +394,14 @@ func (o *baseObject) checkDeleteProp(name string, prop *valueProperty, throw boo return true } -func (o *baseObject) checkDelete(name string, val Value, throw bool) bool { +func (o *baseObject) checkDelete(name unistring.String, val Value, throw bool) bool { if val, ok := val.(*valueProperty); ok { return o.checkDeleteProp(name, val, throw) } return true } -func (o *baseObject) _delete(name string) { +func (o *baseObject) _delete(name unistring.String) { delete(o.values, name) for i, n := range o.propNames { if n == name { @@ -416,13 +419,13 @@ func (o *baseObject) _delete(name string) { } func (o *baseObject) deleteIdx(idx valueInt, throw bool) bool { - return o.val.self.deleteStr(idx.String(), throw) + return o.val.self.deleteStr(idx.string(), throw) } func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool { if o.symValues != nil { if val := o.symValues.get(s); val != nil { - if !o.checkDelete(s.String(), val, throw) { + if !o.checkDelete(s.desc.string(), val, throw) { return false } o.symValues.remove(s) @@ -431,7 +434,7 @@ func (o *baseObject) deleteSym(s *valueSymbol, throw bool) bool { return true } -func (o *baseObject) deleteStr(name string, throw bool) bool { +func (o *baseObject) deleteStr(name unistring.String, throw bool) bool { if val, exists := o.values[name]; exists { if !o.checkDelete(name, val, throw) { return false @@ -461,7 +464,7 @@ func (o *baseObject) setProto(proto *Object, throw bool) bool { return true } -func (o *baseObject) setOwnStr(name string, val Value, throw bool) bool { +func (o *baseObject) setOwnStr(name unistring.String, val Value, throw bool) bool { ownDesc := o.values[name] if ownDesc == nil { if proto := o.prototype; proto != nil { @@ -494,7 +497,7 @@ func (o *baseObject) setOwnStr(name string, val Value, throw bool) bool { } func (o *baseObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { - return o.val.self.setOwnStr(idx.String(), val, throw) + return o.val.self.setOwnStr(idx.string(), val, throw) } func (o *baseObject) setOwnSym(name *valueSymbol, val Value, throw bool) bool { @@ -534,7 +537,7 @@ func (o *baseObject) setOwnSym(name *valueSymbol, val Value, throw bool) bool { return true } -func (o *baseObject) _setForeignStr(name string, prop, val, receiver Value, throw bool) (bool, bool) { +func (o *baseObject) _setForeignStr(name unistring.String, prop, val, receiver Value, throw bool) (bool, bool) { if prop != nil { if prop, ok := prop.(*valueProperty); ok { if !prop.isWritable() { @@ -580,12 +583,12 @@ func (o *baseObject) _setForeignIdx(idx valueInt, prop, val, receiver Value, thr return false, false } -func (o *baseObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (o *baseObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return o._setForeignStr(name, o.values[name], val, receiver, throw) } func (o *baseObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) { - return o.val.self.setForeignStr(name.String(), val, receiver, throw) + return o.val.self.setForeignStr(name.string(), val, receiver, throw) } func (o *baseObject) setForeignSym(name *valueSymbol, val, receiver Value, throw bool) (bool, bool) { @@ -622,16 +625,16 @@ func (o *baseObject) hasOwnPropertySym(s *valueSymbol) bool { return false } -func (o *baseObject) hasOwnPropertyStr(name string) bool { +func (o *baseObject) hasOwnPropertyStr(name unistring.String) bool { _, exists := o.values[name] return exists } func (o *baseObject) hasOwnPropertyIdx(idx valueInt) bool { - return o.val.self.hasOwnPropertyStr(idx.String()) + return o.val.self.hasOwnPropertyStr(idx.string()) } -func (o *baseObject) _defineOwnProperty(name string, existingValue Value, descr PropertyDescriptor, throw bool) (val Value, ok bool) { +func (o *baseObject) _defineOwnProperty(name unistring.String, existingValue Value, descr PropertyDescriptor, throw bool) (val Value, ok bool) { getterObj, _ := descr.Getter.(*Object) setterObj, _ := descr.Setter.(*Object) @@ -734,7 +737,7 @@ Reject: } -func (o *baseObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (o *baseObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { existingVal := o.values[name] if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok { o.values[name] = v @@ -747,7 +750,7 @@ func (o *baseObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, } func (o *baseObject) defineOwnPropertyIdx(idx valueInt, desc PropertyDescriptor, throw bool) bool { - return o.val.self.defineOwnPropertyStr(idx.String(), desc, throw) + return o.val.self.defineOwnPropertyStr(idx.string(), desc, throw) } func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescriptor, throw bool) bool { @@ -755,7 +758,7 @@ func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescript if o.symValues != nil { existingVal = o.symValues.get(s) } - if v, ok := o._defineOwnProperty(s.String(), existingVal, descr, throw); ok { + if v, ok := o._defineOwnProperty(s.desc.string(), existingVal, descr, throw); ok { if o.symValues == nil { o.symValues = newOrderedMap(&o.val.runtime.hash) } @@ -765,7 +768,7 @@ func (o *baseObject) defineOwnPropertySym(s *valueSymbol, descr PropertyDescript return false } -func (o *baseObject) _put(name string, v Value) { +func (o *baseObject) _put(name unistring.String, v Value) { if _, exists := o.values[name]; !exists { o.propNames = append(o.propNames, name) } @@ -785,7 +788,7 @@ func valueProp(value Value, writable, enumerable, configurable bool) Value { } } -func (o *baseObject) _putProp(name string, value Value, writable, enumerable, configurable bool) Value { +func (o *baseObject) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value { prop := valueProp(value, writable, enumerable, configurable) o._put(name, prop) return prop @@ -810,7 +813,7 @@ func (o *baseObject) tryExoticToPrimitive(hint string) Value { } func (o *baseObject) tryPrimitive(methodName string) Value { - if method, ok := o.val.self.getStr(methodName, nil).(*Object); ok { + if method, ok := o.val.self.getStr(unistring.String(methodName), nil).(*Object); ok { if call, ok := method.self.assertCallable(); ok { v := call(FunctionCall{ This: o.val, @@ -905,7 +908,7 @@ func (o *baseObject) export() interface{} { m := make(map[string]interface{}) for _, itemName := range o.ownKeys(false, nil) { itemNameStr := itemName.String() - v := o.val.self.getStr(itemNameStr, nil) + v := o.val.self.getStr(itemName.string(), nil) if v != nil { m[itemNameStr] = v.Export() } else { @@ -929,21 +932,21 @@ const ( ) type propIterItem struct { - name string + name unistring.String value Value // set only when enumerable == _ENUM_UNKNOWN enumerable enumerableFlag } type objectPropIter struct { o *baseObject - propNames []string + propNames []unistring.String idx int } type propFilterIter struct { wrapped iterNextFunc all bool - seen map[string]bool + seen map[unistring.String]bool } func (i *propFilterIter) next() (propIterItem, iterNextFunc) { @@ -989,7 +992,7 @@ func (i *objectPropIter) next() (propIterItem, iterNextFunc) { func (o *baseObject) enumerate() iterNextFunc { return (&propFilterIter{ wrapped: o.val.self.enumerateUnfiltered(), - seen: make(map[string]bool), + seen: make(map[unistring.String]bool), }).next } @@ -997,7 +1000,7 @@ func (o *baseObject) ownIter() iterNextFunc { if len(o.propNames) > o.lastSortedPropLen { o.fixPropOrder() } - propNames := make([]string, len(o.propNames)) + propNames := make([]unistring.String, len(o.propNames)) copy(propNames, o.propNames) return (&objectPropIter{ o: o, @@ -1069,7 +1072,7 @@ func (o *baseObject) ownKeys(all bool, keys []Value) []Value { } if all { for _, k := range o.propNames { - keys = append(keys, newStringValue(k)) + keys = append(keys, stringValueFromRaw(k)) } } else { for _, k := range o.propNames { @@ -1077,34 +1080,44 @@ func (o *baseObject) ownKeys(all bool, keys []Value) []Value { if prop, ok := prop.(*valueProperty); ok && !prop.enumerable { continue } - keys = append(keys, newStringValue(k)) + keys = append(keys, stringValueFromRaw(k)) } } return keys } -func (o *baseObject) ownSymbols() (res []Value) { +func (o *baseObject) ownSymbols(all bool, accum []Value) []Value { if o.symValues != nil { iter := o.symValues.newIter() - for { - entry := iter.next() - if entry == nil { - break + if all { + for { + entry := iter.next() + if entry == nil { + break + } + accum = append(accum, entry.key) + } + } else { + for { + entry := iter.next() + if entry == nil { + break + } + if prop, ok := entry.value.(*valueProperty); ok { + if !prop.enumerable { + continue + } + } + accum = append(accum, entry.key) } - res = append(res, entry.key) } } - return + return accum } func (o *baseObject) ownPropertyKeys(all bool, accum []Value) []Value { - accum = o.val.self.ownKeys(all, accum) - if all { - accum = append(accum, o.ownSymbols()...) - } - - return accum + return o.ownSymbols(all, o.val.self.ownKeys(all, accum)) } func (o *baseObject) hasInstance(Value) bool { @@ -1141,7 +1154,7 @@ func (o *Object) get(p Value, receiver Value) Value { case *valueSymbol: return o.self.getSym(p, receiver) default: - return o.self.getStr(p.String(), receiver) + return o.self.getStr(p.string(), receiver) } } @@ -1152,7 +1165,7 @@ func (o *Object) getOwnProp(p Value) Value { case *valueSymbol: return o.self.getOwnPropSym(p) default: - return o.self.getOwnPropStr(p.String()) + return o.self.getOwnPropStr(p.string()) } } @@ -1163,7 +1176,7 @@ func (o *Object) hasOwnProperty(p Value) bool { case *valueSymbol: return o.self.hasOwnPropertySym(p) default: - return o.self.hasOwnPropertyStr(p.String()) + return o.self.hasOwnPropertyStr(p.string()) } } @@ -1174,11 +1187,11 @@ func (o *Object) hasProperty(p Value) bool { case *valueSymbol: return o.self.hasPropertySym(p) default: - return o.self.hasPropertyStr(p.String()) + return o.self.hasPropertyStr(p.string()) } } -func (o *Object) setStr(name string, val, receiver Value, throw bool) bool { +func (o *Object) setStr(name unistring.String, val, receiver Value, throw bool) bool { if receiver == o { return o.self.setOwnStr(name, val, throw) } else { @@ -1222,7 +1235,7 @@ func (o *Object) set(name Value, val, receiver Value, throw bool) bool { case *valueSymbol: return o.setSym(name, val, receiver, throw) default: - return o.setStr(name.String(), val, receiver, throw) + return o.setStr(name.string(), val, receiver, throw) } } @@ -1233,7 +1246,7 @@ func (o *Object) setOwn(name Value, val Value, throw bool) bool { case *valueSymbol: return o.self.setOwnSym(name, val, throw) default: - return o.self.setOwnStr(name.String(), val, throw) + return o.self.setOwnStr(name.string(), val, throw) } } @@ -1318,7 +1331,7 @@ func (o *Object) delete(n Value, throw bool) bool { case *valueSymbol: return o.self.deleteSym(n, throw) default: - return o.self.deleteStr(n.String(), throw) + return o.self.deleteStr(n.string(), throw) } } @@ -1329,7 +1342,7 @@ func (o *Object) defineOwnProperty(n Value, desc PropertyDescriptor, throw bool) case *valueSymbol: return o.self.defineOwnPropertySym(n, desc, throw) default: - return o.self.defineOwnPropertyStr(n.String(), desc, throw) + return o.self.defineOwnPropertyStr(n.string(), desc, throw) } } diff --git a/object_args.go b/object_args.go index 85aaf5b7..e272ce3b 100644 --- a/object_args.go +++ b/object_args.go @@ -1,5 +1,7 @@ package goja +import "github.com/dop251/goja/unistring" + type argumentsObject struct { baseObject length int @@ -10,11 +12,11 @@ type mappedProperty struct { v *Value } -func (a *argumentsObject) getStr(name string, receiver Value) Value { +func (a *argumentsObject) getStr(name unistring.String, receiver Value) Value { return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver) } -func (a *argumentsObject) getOwnPropStr(name string) Value { +func (a *argumentsObject) getOwnPropStr(name unistring.String) Value { if mapped, ok := a.values[name].(*mappedProperty); ok { return *mapped.v } @@ -27,7 +29,7 @@ func (a *argumentsObject) init() { a._putProp("length", intToValue(int64(a.length)), true, false, true) } -func (a *argumentsObject) setOwnStr(name string, val Value, throw bool) bool { +func (a *argumentsObject) setOwnStr(name unistring.String, val Value, throw bool) bool { if prop, ok := a.values[name].(*mappedProperty); ok { if !prop.writable { a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name) @@ -39,7 +41,7 @@ func (a *argumentsObject) setOwnStr(name string, val Value, throw bool) bool { return a.baseObject.setOwnStr(name, val, throw) } -func (a *argumentsObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (a *argumentsObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw) } @@ -55,7 +57,7 @@ func (a *argumentsObject) setForeignStr(name string, val, receiver Value, throw a.baseObject.putStr(name, val, throw) }*/ -func (a *argumentsObject) deleteStr(name string, throw bool) bool { +func (a *argumentsObject) deleteStr(name unistring.String, throw bool) bool { if prop, ok := a.values[name].(*mappedProperty); ok { if !a.checkDeleteProp(name, &prop.valueProperty, throw) { return false @@ -89,7 +91,7 @@ func (a *argumentsObject) enumerateUnfiltered() iterNextFunc { }).next) } -func (a *argumentsObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (a *argumentsObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if mapped, ok := a.values[name].(*mappedProperty); ok { existing := &valueProperty{ configurable: mapped.configurable, diff --git a/object_gomap.go b/object_gomap.go index ad9d4e6a..601b3638 100644 --- a/object_gomap.go +++ b/object_gomap.go @@ -2,6 +2,8 @@ package goja import ( "reflect" + + "github.com/dop251/goja/unistring" ) type objectGoMapSimple struct { @@ -24,23 +26,24 @@ func (o *objectGoMapSimple) _getStr(name string) Value { return o.val.runtime.ToValue(v) } -func (o *objectGoMapSimple) getStr(name string, receiver Value) Value { - if v := o._getStr(name); v != nil { +func (o *objectGoMapSimple) getStr(name unistring.String, receiver Value) Value { + if v := o._getStr(name.String()); v != nil { return v } return o.baseObject.getStr(name, receiver) } -func (o *objectGoMapSimple) getOwnPropStr(name string) Value { - if v := o._getStr(name); v != nil { +func (o *objectGoMapSimple) getOwnPropStr(name unistring.String) Value { + if v := o._getStr(name.String()); v != nil { return v } return nil } -func (o *objectGoMapSimple) setOwnStr(name string, val Value, throw bool) bool { - if _, exists := o.data[name]; exists { - o.data[name] = val.Export() +func (o *objectGoMapSimple) setOwnStr(name unistring.String, val Value, throw bool) bool { + n := name.String() + if _, exists := o.data[n]; exists { + o.data[n] = val.Export() return true } if proto := o.prototype; proto != nil { @@ -54,7 +57,7 @@ func (o *objectGoMapSimple) setOwnStr(name string, val Value, throw bool) bool { o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) return false } else { - o.data[name] = val.Export() + o.data[n] = val.Export() } return true } @@ -66,8 +69,8 @@ func trueValIfPresent(present bool) Value { return nil } -func (o *objectGoMapSimple) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { - return o._setForeignStr(name, trueValIfPresent(o._hasStr(name)), val, receiver, throw) +func (o *objectGoMapSimple) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, trueValIfPresent(o._hasStr(name.String())), val, receiver, throw) } func (o *objectGoMapSimple) _hasStr(name string) bool { @@ -75,21 +78,22 @@ func (o *objectGoMapSimple) _hasStr(name string) bool { return exists } -func (o *objectGoMapSimple) hasOwnPropertyStr(name string) bool { - return o._hasStr(name) +func (o *objectGoMapSimple) hasOwnPropertyStr(name unistring.String) bool { + return o._hasStr(name.String()) } -func (o *objectGoMapSimple) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (o *objectGoMapSimple) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { return false } - if o.extensible || o._hasStr(name) { - o.data[name] = descr.Value.Export() + n := name.String() + if o.extensible || o._hasStr(n) { + o.data[n] = descr.Value.Export() return true } - o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", name) + o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", n) return false } @@ -111,8 +115,8 @@ func (o *objectGoMapSimple) assertCallable() (call func(FunctionCall) Value, ok } */ -func (o *objectGoMapSimple) deleteStr(name string, _ bool) bool { - delete(o.data, name) +func (o *objectGoMapSimple) deleteStr(name unistring.String, _ bool) bool { + delete(o.data, name.String()) return true } @@ -127,7 +131,7 @@ func (i *gomapPropIter) next() (propIterItem, iterNextFunc) { name := i.propNames[i.idx] i.idx++ if _, exists := i.o.data[name]; exists { - return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next + return propIterItem{name: unistring.NewFromString(name), enumerable: _ENUM_TRUE}, i.next } } diff --git a/object_gomap_reflect.go b/object_gomap_reflect.go index 74ae87c9..03491d36 100644 --- a/object_gomap_reflect.go +++ b/object_gomap_reflect.go @@ -1,6 +1,10 @@ package goja -import "reflect" +import ( + "reflect" + + "github.com/dop251/goja/unistring" +) type objectGoMapReflect struct { objectGoReflect @@ -54,8 +58,8 @@ func (o *objectGoMapReflect) _getStr(name string) Value { return nil } -func (o *objectGoMapReflect) getStr(name string, receiver Value) Value { - if v := o._getStr(name); v != nil { +func (o *objectGoMapReflect) getStr(name unistring.String, receiver Value) Value { + if v := o._getStr(name.String()); v != nil { return v } return o.objectGoReflect.getStr(name, receiver) @@ -68,8 +72,8 @@ func (o *objectGoMapReflect) getIdx(idx valueInt, receiver Value) Value { return o.objectGoReflect.getIdx(idx, receiver) } -func (o *objectGoMapReflect) getOwnPropStr(name string) Value { - if v := o._getStr(name); v != nil { +func (o *objectGoMapReflect) getOwnPropStr(name unistring.String) Value { + if v := o._getStr(name.String()); v != nil { return &valueProperty{ value: v, writable: true, @@ -87,7 +91,7 @@ func (o *objectGoMapReflect) getOwnPropIdx(idx valueInt) Value { enumerable: true, } } - return o.objectGoReflect.getOwnPropStr(idx.String()) + return o.objectGoReflect.getOwnPropStr(idx.string()) } func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) { @@ -117,8 +121,9 @@ func (o *objectGoMapReflect) _put(key reflect.Value, val Value, throw bool) bool return false } -func (o *objectGoMapReflect) setOwnStr(name string, val Value, throw bool) bool { - key := o.strToKey(name, false) +func (o *objectGoMapReflect) setOwnStr(name unistring.String, val Value, throw bool) bool { + n := name.String() + key := o.strToKey(n, false) if !key.IsValid() || !o.value.MapIndex(key).IsValid() { if proto := o.prototype; proto != nil { // we know it's foreign because prototype loops are not allowed @@ -128,11 +133,11 @@ func (o *objectGoMapReflect) setOwnStr(name string, val Value, throw bool) bool } // new property if !o.extensible { - o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", n) return false } else { if throw && !key.IsValid() { - o.strToKey(name, true) + o.strToKey(n, true) return false } } @@ -165,7 +170,7 @@ func (o *objectGoMapReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool return true } -func (o *objectGoMapReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (o *objectGoMapReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw) } @@ -173,24 +178,24 @@ func (o *objectGoMapReflect) setForeignIdx(idx valueInt, val, receiver Value, th return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw) } -func (o *objectGoMapReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (o *objectGoMapReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { return false } - return o._put(o.strToKey(name, throw), descr.Value, throw) + return o._put(o.strToKey(name.String(), throw), descr.Value, throw) } func (o *objectGoMapReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { - if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) { + if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) { return false } return o._put(o.toKey(idx, throw), descr.Value, throw) } -func (o *objectGoMapReflect) hasOwnPropertyStr(name string) bool { - key := o.strToKey(name, false) +func (o *objectGoMapReflect) hasOwnPropertyStr(name unistring.String) bool { + key := o.strToKey(name.String(), false) if key.IsValid() && o.value.MapIndex(key).IsValid() { return true } @@ -205,8 +210,8 @@ func (o *objectGoMapReflect) hasOwnPropertyIdx(idx valueInt) bool { return false } -func (o *objectGoMapReflect) deleteStr(name string, throw bool) bool { - key := o.strToKey(name, throw) +func (o *objectGoMapReflect) deleteStr(name unistring.String, throw bool) bool { + key := o.strToKey(name.String(), throw) if !key.IsValid() { return false } @@ -235,7 +240,7 @@ func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) { v := i.o.value.MapIndex(key) i.idx++ if v.IsValid() { - return propIterItem{name: key.String(), enumerable: _ENUM_TRUE}, i.next + return propIterItem{name: unistring.NewFromString(key.String()), enumerable: _ENUM_TRUE}, i.next } } diff --git a/object_gomap_reflect_test.go b/object_gomap_reflect_test.go index 9c07bf3c..93266bcc 100644 --- a/object_gomap_reflect_test.go +++ b/object_gomap_reflect_test.go @@ -249,3 +249,32 @@ func TestGoMapReflectProtoProp(t *testing.T) { t.Fatal(err) } } + +func TestGoMapReflectUnicode(t *testing.T) { + const SCRIPT = ` + Object.setPrototypeOf(m, s); + if (m.Тест !== "passed") { + throw new Error("m.Тест: " + m.Тест); + } + m["é"]; + ` + type S struct { + Тест string + } + vm := New() + m := map[string]int{ + "é": 42, + } + s := S{ + Тест: "passed", + } + vm.Set("m", m) + vm.Set("s", &s) + res, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if res == nil || !res.StrictEquals(valueInt(42)) { + t.Fatalf("Unexpected value: %v", res) + } +} diff --git a/object_gomap_test.go b/object_gomap_test.go index 0e966e45..451ac01b 100644 --- a/object_gomap_test.go +++ b/object_gomap_test.go @@ -306,3 +306,32 @@ func TestGoMapProtoPropChain(t *testing.T) { t.Fatal(err) } } + +func TestGoMapUnicode(t *testing.T) { + const SCRIPT = ` + Object.setPrototypeOf(m, s); + if (m.Тест !== "passed") { + throw new Error("m.Тест: " + m.Тест); + } + m["é"]; + ` + type S struct { + Тест string + } + vm := New() + m := map[string]interface{}{ + "é": 42, + } + s := S{ + Тест: "passed", + } + vm.Set("m", m) + vm.Set("s", &s) + res, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if res == nil || !res.StrictEquals(valueInt(42)) { + t.Fatalf("Unexpected value: %v", res) + } +} diff --git a/object_goreflect.go b/object_goreflect.go index f5cf9d26..0e8bbc69 100644 --- a/object_goreflect.go +++ b/object_goreflect.go @@ -4,6 +4,8 @@ import ( "fmt" "go/ast" "reflect" + + "github.com/dop251/goja/unistring" ) // JsonEncodable allows custom JSON encoding by JSON.stringify() @@ -83,8 +85,8 @@ func (o *objectGoReflect) valueOfFunc(FunctionCall) Value { return o.toPrimitive() } -func (o *objectGoReflect) getStr(name string, receiver Value) Value { - if v := o._get(name); v != nil { +func (o *objectGoReflect) getStr(name unistring.String, receiver Value) Value { + if v := o._get(name.String()); v != nil { return v } return o.baseObject.getStr(name, receiver) @@ -128,9 +130,10 @@ func (o *objectGoReflect) _get(name string) Value { return nil } -func (o *objectGoReflect) getOwnPropStr(name string) Value { +func (o *objectGoReflect) getOwnPropStr(name unistring.String) Value { + n := name.String() if o.value.Kind() == reflect.Struct { - if v := o._getField(name); v.IsValid() { + if v := o._getField(n); v.IsValid() { return &valueProperty{ value: o.val.runtime.ToValue(o.getAddr(v).Interface()), writable: v.CanSet(), @@ -139,7 +142,7 @@ func (o *objectGoReflect) getOwnPropStr(name string) Value { } } - if v := o._getMethod(name); v.IsValid() { + if v := o._getMethod(n); v.IsValid() { return &valueProperty{ value: o.val.runtime.ToValue(v.Interface()), enumerable: true, @@ -149,8 +152,8 @@ func (o *objectGoReflect) getOwnPropStr(name string) Value { return nil } -func (o *objectGoReflect) setOwnStr(name string, val Value, throw bool) bool { - has, ok := o._put(name, val, throw) +func (o *objectGoReflect) setOwnStr(name unistring.String, val Value, throw bool) bool { + has, ok := o._put(name.String(), val, throw) if !has { if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name) @@ -162,8 +165,8 @@ func (o *objectGoReflect) setOwnStr(name string, val Value, throw bool) bool { return ok } -func (o *objectGoReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { - return o._setForeignStr(name, trueValIfPresent(o._has(name)), val, receiver, throw) +func (o *objectGoReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, trueValIfPresent(o._has(name.String())), val, receiver, throw) } func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool) { @@ -185,14 +188,14 @@ func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool return false, false } -func (o *objectGoReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value { - if _, ok := o._put(name, value, false); ok { +func (o *objectGoReflect) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value { + if _, ok := o._put(name.String(), value, false); ok { return value } return o.baseObject._putProp(name, value, writable, enumerable, configurable) } -func (r *Runtime) checkHostObjectPropertyDescr(name string, descr PropertyDescriptor, throw bool) bool { +func (r *Runtime) checkHostObjectPropertyDescr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if descr.Getter != nil || descr.Setter != nil { r.typeErrorResult(throw, "Host objects do not support accessor properties") return false @@ -208,10 +211,11 @@ func (r *Runtime) checkHostObjectPropertyDescr(name string, descr PropertyDescri return true } -func (o *objectGoReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (o *objectGoReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { - if has, ok := o._put(name, descr.Value, throw); !has { - o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", name) + n := name.String() + if has, ok := o._put(n, descr.Value, throw); !has { + o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", n) return false } else { return ok @@ -232,8 +236,8 @@ func (o *objectGoReflect) _has(name string) bool { return false } -func (o *objectGoReflect) hasOwnPropertyStr(name string) bool { - return o._has(name) +func (o *objectGoReflect) hasOwnPropertyStr(name unistring.String) bool { + return o._has(name.String()) } func (o *objectGoReflect) _toNumber() Value { @@ -293,9 +297,10 @@ func (o *objectGoReflect) toPrimitive() Value { return o.toPrimitiveString() } -func (o *objectGoReflect) deleteStr(name string, throw bool) bool { - if o._has(name) { - o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type") +func (o *objectGoReflect) deleteStr(name unistring.String, throw bool) bool { + n := name.String() + if o._has(n) { + o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type", n) return false } return o.baseObject.deleteStr(name, throw) @@ -311,7 +316,7 @@ func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) { if i.idx < len(names) { name := names[i.idx] i.idx++ - return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextField + return propIterItem{name: unistring.NewFromString(name), enumerable: _ENUM_TRUE}, i.nextField } i.idx = 0 @@ -323,7 +328,7 @@ func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) { if i.idx < len(names) { name := names[i.idx] i.idx++ - return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextMethod + return propIterItem{name: unistring.NewFromString(name), enumerable: _ENUM_TRUE}, i.nextMethod } return propIterItem{}, nil diff --git a/object_goreflect_test.go b/object_goreflect_test.go index 94fbf10e..8ddc13cb 100644 --- a/object_goreflect_test.go +++ b/object_goreflect_test.go @@ -1047,3 +1047,20 @@ func TestGoObj__Proto__(t *testing.T) { t.Fatal(err) } } + +func TestGoReflectUnicodeProps(t *testing.T) { + type S struct { + Тест string + } + vm := New() + var s S + vm.Set("s", &s) + _, err := vm.RunString(` + if (!s.hasOwnProperty("Тест")) { + throw new Error("hasOwnProperty"); + } + `) + if err != nil { + t.Fatal(err) + } +} diff --git a/object_goslice.go b/object_goslice.go index 10892bee..21dc8998 100644 --- a/object_goslice.go +++ b/object_goslice.go @@ -1,10 +1,10 @@ package goja import ( - "math" - "math/bits" "reflect" "strconv" + + "github.com/dop251/goja/unistring" ) type objectGoSlice struct { @@ -28,7 +28,7 @@ func (o *objectGoSlice) updateLen() { o.lengthProp.value = intToValue(int64(len(*o.data))) } -func (o *objectGoSlice) getStr(name string, receiver Value) Value { +func (o *objectGoSlice) getStr(name unistring.String, receiver Value) Value { var ownProp Value if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) { v := (*o.data)[idx] @@ -54,7 +54,7 @@ func (o *objectGoSlice) getIdx(idx valueInt, receiver Value) Value { return nil } -func (o *objectGoSlice) getOwnPropStr(name string) Value { +func (o *objectGoSlice) getOwnPropStr(name unistring.String) Value { if idx := strToGoIdx(name); idx >= 0 { if idx < len(*o.data) { v := o.val.runtime.ToValue((*o.data)[idx]) @@ -134,15 +134,6 @@ func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) { (*o.data)[idx] = v.Export() } -func toInt(i int64) int { - if bits.UintSize == 32 { - if i >= math.MaxInt32 || i < math.MinInt32 { - panic(rangeError("Integer value overflows 32-bit int")) - } - } - return int(i) -} - func (o *objectGoSlice) putLength(v Value, throw bool) bool { newLen := toInt(toLength(v)) curLen := len(*o.data) @@ -171,7 +162,7 @@ func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool { } o.putIdx(i, val, throw) } else { - name := idx.String() + name := idx.string() if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name) return false @@ -182,7 +173,7 @@ func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool { return true } -func (o *objectGoSlice) setOwnStr(name string, val Value, throw bool) bool { +func (o *objectGoSlice) setOwnStr(name unistring.String, val Value, throw bool) bool { if idx := strToGoIdx(name); idx >= 0 { if idx >= len(*o.data) { if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok { @@ -208,7 +199,7 @@ func (o *objectGoSlice) setForeignIdx(idx valueInt, val, receiver Value, throw b return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw) } -func (o *objectGoSlice) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (o *objectGoSlice) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw) } @@ -219,7 +210,7 @@ func (o *objectGoSlice) hasOwnPropertyIdx(idx valueInt) bool { return false } -func (o *objectGoSlice) hasOwnPropertyStr(name string) bool { +func (o *objectGoSlice) hasOwnPropertyStr(name unistring.String) bool { if idx := strToIdx64(name); idx >= 0 { return idx < int64(len(*o.data)) } @@ -228,7 +219,7 @@ func (o *objectGoSlice) hasOwnPropertyStr(name string) bool { func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { if i := toInt(int64(idx)); i >= 0 { - if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) { + if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) { return false } val := descr.Value @@ -242,7 +233,7 @@ func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescrip return false } -func (o *objectGoSlice) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (o *objectGoSlice) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if idx := strToGoIdx(name); idx >= 0 { if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { return false @@ -275,7 +266,7 @@ func (o *objectGoSlice) toPrimitive() Value { return o.toPrimitiveString() } -func (o *objectGoSlice) deleteStr(name string, throw bool) bool { +func (o *objectGoSlice) deleteStr(name unistring.String, throw bool) bool { if idx := strToIdx64(name); idx >= 0 { if idx < int64(len(*o.data)) { o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice") @@ -306,7 +297,7 @@ func (i *goslicePropIter) next() (propIterItem, iterNextFunc) { if i.idx < i.limit && i.idx < len(*i.o.data) { name := strconv.Itoa(i.idx) i.idx++ - return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next + return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next } return propIterItem{}, nil diff --git a/object_goslice_reflect.go b/object_goslice_reflect.go index f4ff9046..4532c838 100644 --- a/object_goslice_reflect.go +++ b/object_goslice_reflect.go @@ -3,6 +3,8 @@ package goja import ( "reflect" "strconv" + + "github.com/dop251/goja/unistring" ) type objectGoSliceReflect struct { @@ -32,7 +34,7 @@ func (o *objectGoSliceReflect) _hasIdx(idx valueInt) bool { return false } -func (o *objectGoSliceReflect) _hasStr(name string) bool { +func (o *objectGoSliceReflect) _hasStr(name unistring.String) bool { if idx := strToIdx64(name); idx >= 0 && idx < int64(o.value.Len()) { return true } @@ -51,10 +53,10 @@ func (o *objectGoSliceReflect) getIdx(idx valueInt, receiver Value) Value { if idx := toInt(int64(idx)); idx >= 0 && idx < o.value.Len() { return o._getIdx(idx) } - return o.objectGoReflect.getStr(idx.String(), receiver) + return o.objectGoReflect.getStr(idx.string(), receiver) } -func (o *objectGoSliceReflect) getStr(name string, receiver Value) Value { +func (o *objectGoSliceReflect) getStr(name unistring.String, receiver Value) Value { var ownProp Value if idx := strToGoIdx(name); idx >= 0 && idx < o.value.Len() { ownProp = o._getIdx(idx) @@ -66,7 +68,7 @@ func (o *objectGoSliceReflect) getStr(name string, receiver Value) Value { return o.getStrWithOwnProp(ownProp, name, receiver) } -func (o *objectGoSliceReflect) getOwnPropStr(name string) Value { +func (o *objectGoSliceReflect) getOwnPropStr(name unistring.String) Value { if idx := strToGoIdx(name); idx >= 0 { if idx < o.value.Len() { return &valueProperty{ @@ -180,7 +182,7 @@ func (o *objectGoSliceReflect) setOwnIdx(idx valueInt, val Value, throw bool) bo } o.putIdx(i, val, throw) } else { - name := idx.String() + name := idx.string() if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name) return false @@ -191,7 +193,7 @@ func (o *objectGoSliceReflect) setOwnIdx(idx valueInt, val Value, throw bool) bo return true } -func (o *objectGoSliceReflect) setOwnStr(name string, val Value, throw bool) bool { +func (o *objectGoSliceReflect) setOwnStr(name unistring.String, val Value, throw bool) bool { if idx := strToGoIdx(name); idx >= 0 { if idx >= o.value.Len() { if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok { @@ -217,7 +219,7 @@ func (o *objectGoSliceReflect) setForeignIdx(idx valueInt, val, receiver Value, return o._setForeignIdx(idx, trueValIfPresent(o._hasIdx(idx)), val, receiver, throw) } -func (o *objectGoSliceReflect) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +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) } @@ -225,16 +227,16 @@ func (o *objectGoSliceReflect) hasOwnPropertyIdx(idx valueInt) bool { return o._hasIdx(idx) } -func (o *objectGoSliceReflect) hasOwnPropertyStr(name string) bool { +func (o *objectGoSliceReflect) hasOwnPropertyStr(name unistring.String) bool { if o._hasStr(name) { return true } - return o.objectGoReflect._has(name) + return o.objectGoReflect._has(name.String()) } func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { if i := toInt(int64(idx)); i >= 0 { - if !o.val.runtime.checkHostObjectPropertyDescr(idx.String(), descr, throw) { + if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) { return false } val := descr.Value @@ -248,7 +250,7 @@ func (o *objectGoSliceReflect) defineOwnPropertyIdx(idx valueInt, descr Property return false } -func (o *objectGoSliceReflect) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { +func (o *objectGoSliceReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { if idx := strToGoIdx(name); idx >= 0 { if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { return false @@ -278,7 +280,7 @@ func (o *objectGoSliceReflect) toPrimitive() Value { return o.toPrimitiveString() } -func (o *objectGoSliceReflect) deleteStr(name string, throw bool) bool { +func (o *objectGoSliceReflect) deleteStr(name unistring.String, throw bool) bool { if idx := strToIdx64(name); idx >= 0 { if idx < int64(o.value.Len()) { o.val.runtime.typeErrorResult(throw, "Can't delete from Go slice") @@ -310,7 +312,7 @@ func (i *gosliceReflectPropIter) next() (propIterItem, iterNextFunc) { if i.idx < i.limit && i.idx < i.o.value.Len() { name := strconv.Itoa(i.idx) i.idx++ - return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next + return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next } return i.o.objectGoReflect.enumerateUnfiltered()() diff --git a/object_lazy.go b/object_lazy.go index d75a9744..ef9f0e49 100644 --- a/object_lazy.go +++ b/object_lazy.go @@ -1,6 +1,10 @@ package goja -import "reflect" +import ( + "reflect" + + "github.com/dop251/goja/unistring" +) type lazyObject struct { val *Object @@ -61,7 +65,7 @@ func (o *lazyObject) hasOwnPropertySym(s *valueSymbol) bool { return obj.hasOwnPropertySym(s) } -func (o *lazyObject) defineOwnPropertyStr(name string, desc PropertyDescriptor, throw bool) bool { +func (o *lazyObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { obj := o.create(o.val) o.val.self = obj return obj.defineOwnPropertyStr(name, desc, throw) @@ -91,19 +95,19 @@ func (o *lazyObject) deleteSym(s *valueSymbol, throw bool) bool { return obj.deleteSym(s, throw) } -func (o *lazyObject) getStr(name string, receiver Value) Value { +func (o *lazyObject) getStr(name unistring.String, receiver Value) Value { obj := o.create(o.val) o.val.self = obj return obj.getStr(name, receiver) } -func (o *lazyObject) getOwnPropStr(name string) Value { +func (o *lazyObject) getOwnPropStr(name unistring.String) Value { obj := o.create(o.val) o.val.self = obj return obj.getOwnPropStr(name) } -func (o *lazyObject) setOwnStr(p string, v Value, throw bool) bool { +func (o *lazyObject) setOwnStr(p unistring.String, v Value, throw bool) bool { obj := o.create(o.val) o.val.self = obj return obj.setOwnStr(p, v, throw) @@ -121,7 +125,7 @@ func (o *lazyObject) setOwnSym(p *valueSymbol, v Value, throw bool) bool { return obj.setOwnSym(p, v, throw) } -func (o *lazyObject) setForeignStr(p string, v, receiver Value, throw bool) (bool, bool) { +func (o *lazyObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (bool, bool) { obj := o.create(o.val) o.val.self = obj return obj.setForeignStr(p, v, receiver, throw) @@ -139,19 +143,19 @@ func (o *lazyObject) setForeignSym(p *valueSymbol, v, receiver Value, throw bool return obj.setForeignSym(p, v, receiver, throw) } -func (o *lazyObject) hasPropertyStr(name string) bool { +func (o *lazyObject) hasPropertyStr(name unistring.String) bool { obj := o.create(o.val) o.val.self = obj return obj.hasPropertyStr(name) } -func (o *lazyObject) hasOwnPropertyStr(name string) bool { +func (o *lazyObject) hasOwnPropertyStr(name unistring.String) bool { obj := o.create(o.val) o.val.self = obj return obj.hasOwnPropertyStr(name) } -func (o *lazyObject) _putProp(string, Value, bool, bool, bool) Value { +func (o *lazyObject) _putProp(unistring.String, Value, bool, bool, bool) Value { panic("cannot use _putProp() in lazy object") } @@ -189,7 +193,7 @@ func (o *lazyObject) assertConstructor() func(args []Value, newTarget *Object) * return obj.assertConstructor() } -func (o *lazyObject) deleteStr(name string, throw bool) bool { +func (o *lazyObject) deleteStr(name unistring.String, throw bool) bool { obj := o.create(o.val) o.val.self = obj return obj.deleteStr(name, throw) @@ -255,10 +259,10 @@ func (o *lazyObject) ownKeys(all bool, accum []Value) []Value { return obj.ownKeys(all, accum) } -func (o *lazyObject) ownSymbols() []Value { +func (o *lazyObject) ownSymbols(all bool, accum []Value) []Value { obj := o.create(o.val) o.val.self = obj - return obj.ownSymbols() + return obj.ownSymbols(all, accum) } func (o *lazyObject) ownPropertyKeys(all bool, accum []Value) []Value { diff --git a/object_test.go b/object_test.go index 9bce00f8..03444c02 100644 --- a/object_test.go +++ b/object_test.go @@ -97,6 +97,18 @@ func TestPropertyOrder(t *testing.T) { testScript1(SCRIPT, _undefined, t) } +func TestDefinePropertiesSymbol(t *testing.T) { + const SCRIPT = ` + var desc = {}; + desc[Symbol.toStringTag] = {value: "Test"}; + var o = {}; + Object.defineProperties(o, desc); + o[Symbol.toStringTag] === "Test"; + ` + + testScript1(SCRIPT, valueTrue, t) +} + func BenchmarkPut(b *testing.B) { v := &Object{} diff --git a/parser/expression.go b/parser/expression.go index a90bc1a9..ae418d06 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -4,10 +4,11 @@ import ( "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" + "github.com/dop251/goja/unistring" ) func (self *_parser) parseIdentifier() *ast.Identifier { - literal := self.literal + literal := self.parsedLiteral idx := self.idx self.next() return &ast.Identifier{ @@ -17,7 +18,7 @@ func (self *_parser) parseIdentifier() *ast.Identifier { } func (self *_parser) parsePrimaryExpression() ast.Expression { - literal := self.literal + literal, parsedLiteral := self.literal, self.parsedLiteral idx := self.idx switch self.token { case token.IDENTIFIER: @@ -31,7 +32,7 @@ func (self *_parser) parsePrimaryExpression() ast.Expression { } } return &ast.Identifier{ - Name: literal, + Name: parsedLiteral, Idx: idx, } case token.NULL: @@ -43,7 +44,7 @@ func (self *_parser) parsePrimaryExpression() ast.Expression { case token.BOOLEAN: self.next() value := false - switch literal { + switch parsedLiteral { case "true": value = true case "false": @@ -58,14 +59,10 @@ func (self *_parser) parsePrimaryExpression() ast.Expression { } case token.STRING: self.next() - value, err := parseStringLiteral(literal[1 : len(literal)-1]) - if err != nil { - self.error(idx, err.Error()) - } return &ast.StringLiteral{ Idx: idx, Literal: literal, - Value: value, + Value: parsedLiteral, } case token.NUMBER: self.next() @@ -112,7 +109,7 @@ func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral { } idx := self.idxOf(offset) - pattern, err := self.scanString(offset) + pattern, _, err := self.scanString(offset, false) endOffset := self.chrOffset if err == nil { @@ -151,11 +148,11 @@ func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.VariableEx return &ast.BadExpression{From: idx, To: self.idx} } - literal := self.literal + name := self.parsedLiteral idx := self.idx self.next() node := &ast.VariableExpression{ - Name: literal, + Name: name, Idx: idx, } @@ -192,31 +189,27 @@ func (self *_parser) parseVariableDeclarationList(var_ file.Idx) []ast.Expressio return list } -func (self *_parser) parseObjectPropertyKey() (string, string) { - idx, tkn, literal := self.idx, self.token, self.literal - value := "" +func (self *_parser) parseObjectPropertyKey() (string, unistring.String) { + idx, tkn, literal, parsedLiteral := self.idx, self.token, self.literal, self.parsedLiteral + var value unistring.String self.next() switch tkn { case token.IDENTIFIER: - value = literal + value = parsedLiteral case token.NUMBER: var err error _, err = parseNumberLiteral(literal) if err != nil { self.error(idx, err.Error()) } else { - value = literal + value = unistring.String(literal) } case token.STRING: - var err error - value, err = parseStringLiteral(literal[1 : len(literal)-1]) - if err != nil { - self.error(idx, err.Error()) - } + value = parsedLiteral default: // null, false, class, etc. - if matchIdentifier.MatchString(literal) { - value = literal + if isId(tkn) { + value = unistring.String(literal) } } return literal, value @@ -339,10 +332,10 @@ func (self *_parser) parseCallExpression(left ast.Expression) ast.Expression { func (self *_parser) parseDotMember(left ast.Expression) ast.Expression { period := self.expect(token.PERIOD) - literal := self.literal + literal := self.parsedLiteral idx := self.idx - if !matchIdentifier.MatchString(literal) { + if self.token != token.IDENTIFIER && !isId(self.token) { self.expect(token.IDENTIFIER) self.nextStatement() return &ast.BadExpression{From: period, To: self.idx} @@ -382,7 +375,7 @@ func (self *_parser) parseNewExpression() ast.Expression { } return &ast.MetaProperty{ Meta: &ast.Identifier{ - Name: token.NEW.String(), + Name: unistring.String(token.NEW.String()), Idx: idx, }, Property: prop, diff --git a/parser/lexer.go b/parser/lexer.go index 0626b007..de6baadc 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -1,27 +1,19 @@ package parser import ( - "bytes" "errors" "fmt" - "regexp" "strconv" "strings" "unicode" + "unicode/utf16" "unicode/utf8" "github.com/dop251/goja/file" "github.com/dop251/goja/token" - "unicode/utf16" + "github.com/dop251/goja/unistring" ) -type _chr struct { - value rune - width int -} - -var matchIdentifier = regexp.MustCompile(`^[$_\p{L}][$_\p{L}\d}]*$`) - func isDecimalDigit(chr rune) bool { return '0' <= chr && chr <= '9' } @@ -55,45 +47,65 @@ func isIdentifierPart(chr rune) bool { chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr)) } -func (self *_parser) scanIdentifier() (string, error) { +func (self *_parser) scanIdentifier() (string, unistring.String, bool, error) { offset := self.chrOffset - parse := false + hasEscape := false + isUnicode := false + length := 0 for isIdentifierPart(self.chr) { - if self.chr == '\\' { + r := self.chr + length++ + if r == '\\' { + hasEscape = true distance := self.chrOffset - offset self.read() if self.chr != 'u' { - return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr)) + return "", "", false, fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr)) } - parse = true var value rune for j := 0; j < 4; j++ { self.read() decimal, ok := hex2decimal(byte(self.chr)) if !ok { - return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr)) + return "", "", false, fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr)) } value = value<<4 | decimal } if value == '\\' { - return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) + return "", "", false, fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) } else if distance == 0 { if !isIdentifierStart(value) { - return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) + return "", "", false, fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) } } else if distance > 0 { if !isIdentifierPart(value) { - return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) + return "", "", false, fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) } } + r = value + } + if r >= utf8.RuneSelf { + isUnicode = true + if r > 0xFFFF { + length++ + } } self.read() } - literal := string(self.str[offset:self.chrOffset]) - if parse { - return parseStringLiteral(literal) + + literal := self.str[offset:self.chrOffset] + var parsed unistring.String + if hasEscape || isUnicode { + var err error + parsed, err = parseStringLiteral1(literal, length, isUnicode) + if err != nil { + return "", "", false, err + } + } else { + parsed = unistring.String(literal) } - return literal, nil + + return literal, parsed, hasEscape, nil } // 7.2 @@ -118,7 +130,52 @@ func isLineTerminator(chr rune) bool { return false } -func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) { +func isId(tkn token.Token) bool { + switch tkn { + case token.KEYWORD, + token.BOOLEAN, + token.NULL, + token.THIS, + token.IF, + token.IN, + token.OF, + token.DO, + + token.VAR, + token.FOR, + token.NEW, + token.TRY, + + token.ELSE, + token.CASE, + token.VOID, + token.WITH, + + token.WHILE, + token.BREAK, + token.CATCH, + token.THROW, + + token.RETURN, + token.TYPEOF, + token.DELETE, + token.SWITCH, + + token.DEFAULT, + token.FINALLY, + + token.FUNCTION, + token.CONTINUE, + token.DEBUGGER, + + token.INSTANCEOF: + + return true + } + return false +} + +func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unistring.String, idx file.Idx) { self.implicitSemicolon = false @@ -131,30 +188,43 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) { switch chr := self.chr; { case isIdentifierStart(chr): var err error - literal, err = self.scanIdentifier() + var hasEscape bool + literal, parsedLiteral, hasEscape, err = self.scanIdentifier() if err != nil { tkn = token.ILLEGAL break } - if len(literal) > 1 { + if len(parsedLiteral) > 1 { // Keywords are longer than 1 character, avoid lookup otherwise var strict bool - tkn, strict = token.IsKeyword(literal) + tkn, strict = token.IsKeyword(string(parsedLiteral)) switch tkn { case 0: // Not a keyword - if literal == "true" || literal == "false" { + if parsedLiteral == "true" || parsedLiteral == "false" { + if hasEscape { + tkn = token.ILLEGAL + return + } self.insertSemicolon = true tkn = token.BOOLEAN return - } else if literal == "null" { + } else if parsedLiteral == "null" { + if hasEscape { + tkn = token.ILLEGAL + return + } self.insertSemicolon = true tkn = token.NULL return } case token.KEYWORD: + if hasEscape { + tkn = token.ILLEGAL + return + } tkn = token.KEYWORD if strict { // TODO If strict and in strict mode, then this is not a break @@ -169,10 +239,17 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) { token.RETURN, token.CONTINUE, token.DEBUGGER: + if hasEscape { + tkn = token.ILLEGAL + return + } self.insertSemicolon = true return default: + if hasEscape { + tkn = token.ILLEGAL + } return } @@ -286,7 +363,7 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) { insertSemicolon = true tkn = token.STRING var err error - literal, err = self.scanString(self.chrOffset - 1) + literal, parsedLiteral, err = self.scanString(self.chrOffset-1, true) if err != nil { tkn = token.ILLEGAL } @@ -360,14 +437,6 @@ func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token return tkn0 } -func (self *_parser) chrAt(index int) _chr { - value, width := utf8.DecodeRuneInString(self.str[index:]) - return _chr{ - value: value, - width: width, - } -} - func (self *_parser) _peek() rune { if self.offset+1 < self.length { return rune(self.str[self.offset+1]) @@ -475,19 +544,30 @@ func (self *_parser) scanMantissa(base int) { } } -func (self *_parser) scanEscape(quote rune) { +func (self *_parser) scanEscape(quote rune) (int, bool) { var length, base uint32 - switch self.chr { - //case '0', '1', '2', '3', '4', '5', '6', '7': - // Octal: - // length, base, limit = 3, 8, 255 - case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '0': + chr := self.chr + switch chr { + case '0', '1', '2', '3', '4', '5', '6', '7': + // Octal: + length, base = 3, 8 + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'': self.read() - return - case '\r', '\n', '\u2028', '\u2029': - self.scanNewline() - return + return 1, false + case '\r': + self.read() + if self.chr == '\n' { + self.read() + return 2, false + } + return 1, false + case '\n': + self.read() + return 1, false + case '\u2028', '\u2029': + self.read() + return 1, true case 'x': self.read() length, base = 2, 16 @@ -496,24 +576,34 @@ func (self *_parser) scanEscape(quote rune) { length, base = 4, 16 default: self.read() // Always make progress - return } - var value uint32 - for ; length > 0 && self.chr != quote && self.chr >= 0; length-- { - digit := uint32(digitValue(self.chr)) - if digit >= base { - break + if length > 0 { + var value uint32 + for ; length > 0 && self.chr != quote && self.chr >= 0; length-- { + digit := uint32(digitValue(self.chr)) + if digit >= base { + break + } + value = value*base + digit + self.read() } - value = value*base + digit - self.read() + chr = rune(value) + } + if chr >= utf8.RuneSelf { + if chr > 0xFFFF { + return 2, true + } + return 1, true } + return 1, false } -func (self *_parser) scanString(offset int) (string, error) { +func (self *_parser) scanString(offset int, parse bool) (literal string, parsed unistring.String, err error) { // " ' / quote := rune(self.str[offset]) - + length := 0 + isUnicode := false for self.chr != quote { chr := self.chr if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 { @@ -521,14 +611,19 @@ func (self *_parser) scanString(offset int) (string, error) { } self.read() if chr == '\\' { - if quote == '/' { - if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 { + if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 { + if quote == '/' { goto newline } - self.read() + self.scanNewline() } else { - self.scanEscape(quote) + l, u := self.scanEscape(quote) + length += l + if u { + isUnicode = true + } } + continue } else if chr == '[' && quote == '/' { // Allow a slash (/) in a bracket character class ([...]) // TODO Fix this, this is hacky... @@ -536,21 +631,31 @@ func (self *_parser) scanString(offset int) (string, error) { } else if chr == ']' && quote == -1 { quote = '/' } + if chr >= utf8.RuneSelf { + isUnicode = true + if chr > 0xFFFF { + length++ + } + } + length++ } // " ' / self.read() - - return string(self.str[offset:self.chrOffset]), nil + literal = self.str[offset:self.chrOffset] + if parse { + parsed, err = parseStringLiteral1(literal[1:len(literal)-1], length, isUnicode) + } + return newline: self.scanNewline() - err := "String not terminated" + errStr := "String not terminated" if quote == '/' { - err = "Invalid regular expression: missing /" - self.error(self.idxOf(offset), err) + errStr = "Invalid regular expression: missing /" + self.error(self.idxOf(offset), errStr) } - return "", errors.New(err) + return "", "", errors.New(errStr) } func (self *_parser) scanNewline() { @@ -617,21 +722,16 @@ error: return nil, errors.New("Illegal numeric literal") } -func parseStringLiteral(literal string) (string, error) { - // Best case scenario... - if literal == "" { - return "", nil - } - - // Slightly less-best case scenario... - if !strings.ContainsRune(literal, '\\') { - return literal, nil +func parseStringLiteral1(literal string, length int, unicode bool) (unistring.String, error) { + var sb strings.Builder + var chars []uint16 + if unicode { + chars = make([]uint16, 1, length+1) + chars[0] = unistring.BOM + } else { + sb.Grow(length) } - str := literal - buffer := bytes.NewBuffer(make([]byte, 0, 3*len(literal)/2)) - var surrogate rune -S: for len(str) > 0 { switch chr := str[0]; { // We do not explicitly handle the case of the quote @@ -639,11 +739,20 @@ S: // This assumes we're already passed a partially well-formed literal case chr >= utf8.RuneSelf: chr, size := utf8.DecodeRuneInString(str) - buffer.WriteRune(chr) + if chr <= 0xFFFF { + chars = append(chars, uint16(chr)) + } else { + first, second := utf16.EncodeRune(chr) + chars = append(chars, uint16(first), uint16(second)) + } str = str[size:] continue case chr != '\\': - buffer.WriteByte(chr) + if unicode { + chars = append(chars, uint16(chr)) + } else { + sb.WriteByte(chr) + } str = str[1:] continue } @@ -736,20 +845,32 @@ S: default: value = rune(chr) } - if surrogate != 0 { - value = utf16.DecodeRune(surrogate, value) - surrogate = 0 + } + if unicode { + if value <= 0xFFFF { + chars = append(chars, uint16(value)) } else { - if utf16.IsSurrogate(value) { - surrogate = value - continue S - } + first, second := utf16.EncodeRune(value) + chars = append(chars, uint16(first), uint16(second)) + } + } else { + if value >= utf8.RuneSelf { + return "", fmt.Errorf("Unexpected unicode character") } + sb.WriteByte(byte(value)) } - buffer.WriteRune(value) } - return buffer.String(), nil + if unicode { + if len(chars) != length+1 { + panic(fmt.Errorf("unexpected unicode length while parsing '%s'", literal)) + } + return unistring.FromUtf16(chars), nil + } + if sb.Len() != length { + panic(fmt.Errorf("unexpected length while parsing '%s'", literal)) + } + return unistring.String(sb.String()), nil } func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) { diff --git a/parser/lexer_test.go b/parser/lexer_test.go index a25858e1..e91183fc 100644 --- a/parser/lexer_test.go +++ b/parser/lexer_test.go @@ -5,6 +5,7 @@ import ( "github.com/dop251/goja/file" "github.com/dop251/goja/token" + "github.com/dop251/goja/unistring" ) func TestLexer(t *testing.T) { @@ -17,13 +18,13 @@ func TestLexer(t *testing.T) { test := func(src string, test ...interface{}) { parser := setup(src) for len(test) > 0 { - tkn, literal, idx := parser.scan() + tkn, literal, _, idx := parser.scan() if len(test) > 0 { is(tkn, test[0].(token.Token)) test = test[1:] } if len(test) > 0 { - is(literal, test[0].(string)) + is(literal, unistring.String(test[0].(string))) test = test[1:] } if len(test) > 0 { @@ -184,7 +185,7 @@ Second line \ test(`var \u0024 = 1`, token.VAR, "var", 1, - token.IDENTIFIER, "$", 5, + token.IDENTIFIER, "\\u0024", 5, token.ASSIGN, "", 12, token.NUMBER, "1", 14, token.EOF, "", 15, @@ -368,7 +369,8 @@ Second line \ ) test(`"\x0G"`, - token.STRING, "\"\\x0G\"", 1, + token.ILLEGAL, "\"\\x0G\"", 1, + //token.STRING, "\"\\x0G\"", 1, token.EOF, "", 7, ) diff --git a/parser/parser.go b/parser/parser.go index 20f86c9c..a5784d33 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -42,6 +42,7 @@ import ( "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" + "github.com/dop251/goja/unistring" ) // A Mode value is a set of flags (or 0). They control optional parser functionality. @@ -60,9 +61,10 @@ type _parser struct { chrOffset int // The offset of current character offset int // The offset after current character (may be greater than 1) - idx file.Idx // The index of token - token token.Token // The token - literal string // The literal of the token, if any + idx file.Idx // The index of token + token token.Token // The token + literal string // The literal of the token, if any + parsedLiteral unistring.String scope *_scope insertSemicolon bool // If we see a newline, then insert an implicit semicolon @@ -188,7 +190,7 @@ func (self *_parser) parse() (*ast.Program, error) { } func (self *_parser) next() { - self.token, self.literal, self.idx = self.scan() + self.token, self.literal, self.parsedLiteral, self.idx = self.scan() } func (self *_parser) optionalSemicolon() { diff --git a/parser/parser_test.go b/parser/parser_test.go index ed4c0471..3dd8736c 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -8,6 +8,7 @@ import ( "github.com/dop251/goja/ast" "github.com/dop251/goja/file" + "github.com/dop251/goja/unistring" ) func firstErr(err error) error { @@ -86,9 +87,9 @@ func TestParserErr(t *testing.T) { return program, parser } - program, parser := test("", nil) + test("", nil) - program, parser = test(` + program, parser := test(` var abc; break; do { } while(true); @@ -513,7 +514,7 @@ func TestParser(t *testing.T) { abc() `, nil) - program := test("", nil) + test("", nil) test("//", nil) @@ -531,7 +532,7 @@ func TestParser(t *testing.T) { test("new +", "(anonymous): Line 1:5 Unexpected token +") - program = test(";", nil) + program := test(";", nil) is(len(program.Body), 1) is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(1)) @@ -874,71 +875,77 @@ func TestParser(t *testing.T) { func Test_parseStringLiteral(t *testing.T) { tt(t, func() { - test := func(have, want string) { - have, err := parseStringLiteral(have) + test := func(have string, want unistring.String) { + parser := newParser("", have) + parser.read() + parser.read() + _, res, err := parser.scanString(0, true) is(err, nil) - is(have, want) + is(res, want) } - test("", "") + test(`""`, "") + test(`/=/`, "=") - test("1(\\\\d+)", "1(\\d+)") + test("'1(\\\\d+)'", "1(\\d+)") - test("\\u2029", "\u2029") + test("'\\u2029'", "\u2029") - test("abc\\uFFFFabc", "abc\uFFFFabc") + test("'abc\\uFFFFabc'", "abc\uFFFFabc") - test("[First line \\\nSecond line \\\n Third line\\\n. ]", + test("'[First line \\\nSecond line \\\n Third line\\\n. ]'", "[First line Second line Third line. ]") - test("\\u007a\\x79\\u000a\\x78", "zy\nx") + test("'\\u007a\\x79\\u000a\\x78'", "zy\nx") // S7.8.4_A4.2_T3 - test("\\a", "a") - test("\u0410", "\u0410") + test("'\\a'", "a") + test("'\u0410'", "\u0410") // S7.8.4_A5.1_T1 - test("\\0", "\u0000") + test("'\\0'", "\u0000") // S8.4_A5 - test("\u0000", "\u0000") + test("'\u0000'", "\u0000") // 15.5.4.20 - test("'abc'\\\n'def'", "'abc''def'") + test("\"'abc'\\\n'def'\"", "'abc''def'") // 15.5.4.20-4-1 - test("'abc'\\\r\n'def'", "'abc''def'") + test("\"'abc'\\\r\n'def'\"", "'abc''def'") // Octal - test("\\0", "\000") - test("\\00", "\000") - test("\\000", "\000") - test("\\09", "\0009") - test("\\009", "\0009") - test("\\0009", "\0009") - test("\\1", "\001") - test("\\01", "\001") - test("\\001", "\001") - test("\\0011", "\0011") - test("\\1abc", "\001abc") - - test("\\\u4e16", "\u4e16") + test("'\\0'", "\000") + test("'\\00'", "\000") + test("'\\000'", "\000") + test("'\\09'", "\0009") + test("'\\009'", "\0009") + test("'\\0009'", "\0009") + test("'\\1'", "\001") + test("'\\01'", "\001") + test("'\\001'", "\001") + test("'\\0011'", "\0011") + test("'\\1abc'", "\001abc") + + test("'\\\u4e16'", "\u4e16") // err - test = func(have, want string) { - have, err := parseStringLiteral(have) + test = func(have string, want unistring.String) { + parser := newParser("", have) + parser.read() + parser.read() + _, res, err := parser.scanString(0, true) is(err.Error(), want) - is(have, "") + is(res, "") } - test(`\u`, `invalid escape: \u: len("") != 4`) - test(`\u0`, `invalid escape: \u: len("0") != 4`) - test(`\u00`, `invalid escape: \u: len("00") != 4`) - test(`\u000`, `invalid escape: \u: len("000") != 4`) + test(`"\u"`, `invalid escape: \u: len("") != 4`) + test(`"\u0"`, `invalid escape: \u: len("0") != 4`) + test(`"\u00"`, `invalid escape: \u: len("00") != 4`) + test(`"\u000"`, `invalid escape: \u: len("000") != 4`) - test(`\x`, `invalid escape: \x: len("") != 2`) - test(`\x0`, `invalid escape: \x: len("0") != 2`) - test(`\x0`, `invalid escape: \x: len("0") != 2`) + test(`"\x"`, `invalid escape: \x: len("") != 2`) + test(`"\x0"`, `invalid escape: \x: len("0") != 2`) }) } diff --git a/parser/scope.go b/parser/scope.go index 1710d5fe..d28ad20b 100644 --- a/parser/scope.go +++ b/parser/scope.go @@ -2,6 +2,7 @@ package parser import ( "github.com/dop251/goja/ast" + "github.com/dop251/goja/unistring" ) type _scope struct { @@ -12,7 +13,7 @@ type _scope struct { inFunction bool declarationList []ast.Declaration - labels []string + labels []unistring.String } func (self *_parser) openScope() { @@ -30,7 +31,7 @@ func (self *_scope) declare(declaration ast.Declaration) { self.declarationList = append(self.declarationList, declaration) } -func (self *_scope) hasLabel(name string) bool { +func (self *_scope) hasLabel(name unistring.String) bool { for _, label := range self.labels { if label == name { return true diff --git a/proxy.go b/proxy.go index ba7f6622..51e0c861 100644 --- a/proxy.go +++ b/proxy.go @@ -1,6 +1,10 @@ package goja -import "reflect" +import ( + "reflect" + + "github.com/dop251/goja/unistring" +) // Proxy is a Go wrapper around ECMAScript Proxy. Calling Runtime.ToValue() on it // returns the underlying Proxy. Calling Export() on an ECMAScript Proxy returns a wrapper. @@ -24,7 +28,7 @@ func (i *proxyPropIter) next() (propIterItem, iterNextFunc) { name := i.names[i.idx] i.idx++ if prop := i.p.val.getOwnProp(name); prop != nil { - return propIterItem{name: name.String(), value: prop}, i.next + return propIterItem{name: name.string(), value: prop}, i.next } } if proto := i.p.proto(); proto != nil { @@ -118,7 +122,7 @@ func (p *proxyObject) proxyCall(trap proxyTrap, args ...Value) (Value, bool) { panic(r.NewTypeError("Proxy already revoked")) } - if m := toMethod(r.getVStr(p.handler, trap.String())); m != nil { + if m := toMethod(r.getVStr(p.handler, unistring.String(trap.String()))); m != nil { return m(FunctionCall{ This: p.handler, Arguments: args, @@ -230,8 +234,8 @@ func (p *proxyObject) proxyDefineOwnProperty(name Value, descr PropertyDescripto return false, false } -func (p *proxyObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { - if v, ok := p.proxyDefineOwnProperty(newStringValue(name), descr, throw); ok { +func (p *proxyObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if v, ok := p.proxyDefineOwnProperty(stringValueFromRaw(name), descr, throw); ok { return v } return p.target.self.defineOwnPropertyStr(name, descr, throw) @@ -271,8 +275,8 @@ func (p *proxyObject) proxyHas(name Value) (bool, bool) { return false, false } -func (p *proxyObject) hasPropertyStr(name string) bool { - if b, ok := p.proxyHas(newStringValue(name)); ok { +func (p *proxyObject) hasPropertyStr(name unistring.String) bool { + if b, ok := p.proxyHas(stringValueFromRaw(name)); ok { return b } @@ -295,7 +299,7 @@ func (p *proxyObject) hasPropertySym(s *valueSymbol) bool { return p.target.self.hasPropertySym(s) } -func (p *proxyObject) hasOwnPropertyStr(name string) bool { +func (p *proxyObject) hasOwnPropertyStr(name unistring.String) bool { return p.getOwnPropStr(name) != nil } @@ -361,8 +365,8 @@ func (p *proxyObject) proxyGetOwnPropertyDescriptor(name Value) (Value, bool) { return nil, false } -func (p *proxyObject) getOwnPropStr(name string) Value { - if v, ok := p.proxyGetOwnPropertyDescriptor(newStringValue(name)); ok { +func (p *proxyObject) getOwnPropStr(name unistring.String) Value { + if v, ok := p.proxyGetOwnPropertyDescriptor(stringValueFromRaw(name)); ok { return v } @@ -385,8 +389,8 @@ func (p *proxyObject) getOwnPropSym(s *valueSymbol) Value { return p.target.self.getOwnPropSym(s) } -func (p *proxyObject) getStr(name string, receiver Value) Value { - if v, ok := p.proxyGet(newStringValue(name), receiver); ok { +func (p *proxyObject) getStr(name unistring.String, receiver Value) Value { + if v, ok := p.proxyGet(stringValueFromRaw(name), receiver); ok { return v } return p.target.self.getStr(name, receiver) @@ -451,8 +455,8 @@ func (p *proxyObject) proxySet(name, value, receiver Value, throw bool) (bool, b return false, false } -func (p *proxyObject) setOwnStr(name string, v Value, throw bool) bool { - if res, ok := p.proxySet(newStringValue(name), v, p.val, throw); ok { +func (p *proxyObject) setOwnStr(name unistring.String, v Value, throw bool) bool { + if res, ok := p.proxySet(stringValueFromRaw(name), v, p.val, throw); ok { return res } return p.target.setStr(name, v, p.val, throw) @@ -472,8 +476,8 @@ func (p *proxyObject) setOwnSym(s *valueSymbol, v Value, throw bool) bool { return p.target.setSym(s, v, p.val, throw) } -func (p *proxyObject) setForeignStr(name string, v, receiver Value, throw bool) (bool, bool) { - if res, ok := p.proxySet(newStringValue(name), v, receiver, throw); ok { +func (p *proxyObject) setForeignStr(name unistring.String, v, receiver Value, throw bool) (bool, bool) { + if res, ok := p.proxySet(stringValueFromRaw(name), v, receiver, throw); ok { return res, true } return p.target.setStr(name, v, receiver, throw), true @@ -509,8 +513,8 @@ func (p *proxyObject) proxyDelete(n Value) (bool, bool) { return false, false } -func (p *proxyObject) deleteStr(name string, throw bool) bool { - if ret, ok := p.proxyDelete(newStringValue(name)); ok { +func (p *proxyObject) deleteStr(name unistring.String, throw bool) bool { + if ret, ok := p.proxyDelete(stringValueFromRaw(name)); ok { return ret } @@ -719,7 +723,7 @@ func (p *proxyObject) filterKeys(vals []Value, all, symbols bool) []Value { } } else { if _, ok := val.(*valueSymbol); !ok { - prop = p.getOwnPropStr(val.String()) + prop = p.getOwnPropStr(val.string()) } else { continue } @@ -760,12 +764,17 @@ func (p *proxyObject) ownKeys(all bool, _ []Value) []Value { // we can assume ac return p.target.self.ownKeys(all, nil) } -func (p *proxyObject) ownSymbols() []Value { +func (p *proxyObject) ownSymbols(all bool, accum []Value) []Value { if vals, ok := p.proxyOwnKeys(); ok { - return p.filterKeys(vals, true, true) + res := p.filterKeys(vals, true, true) + if accum == nil { + return res + } + accum = append(accum, res...) + return accum } - return p.target.self.ownSymbols() + return p.target.self.ownSymbols(all, accum) } func (p *proxyObject) className() string { diff --git a/regexp.go b/regexp.go index df57847b..31205f5a 100644 --- a/regexp.go +++ b/regexp.go @@ -35,7 +35,7 @@ func (r *regexp2Wrapper) FindSubmatchIndex(s valueString, start int) (result []i case asciiString: match, err = wrapped.FindStringMatch(string(s)[start:]) case unicodeString: - match, err = wrapped.FindRunesMatch(utf16.Decode(s[start:])) + match, err = wrapped.FindRunesMatch(utf16.Decode(s[start+1:])) default: panic(fmt.Errorf("Unknown string type: %T", s)) } @@ -208,7 +208,7 @@ func (r *regexp2Wrapper) MatchString(s valueString) bool { matched, _ := wrapped.MatchString(string(s)) return matched case unicodeString: - matched, _ := wrapped.MatchRunes(utf16.Decode(s)) + matched, _ := wrapped.MatchRunes(utf16.Decode(s[1:])) return matched default: panic(fmt.Errorf("Unknown string type: %T", s)) @@ -287,7 +287,7 @@ func (r *regexpObject) execResultToArray(target valueString, result []int) Value for index := 0; index < captureCount; index++ { offset := index << 1 if result[offset] >= lowerBound { - valueArray[index] = target.substring(int64(result[offset]), int64(result[offset+1])) + valueArray[index] = target.substring(result[offset], result[offset+1]) lowerBound = result[offset] } else { valueArray[index] = _undefined @@ -311,7 +311,7 @@ func (r *regexpObject) execRegexp(target valueString) (match bool, result []int) if !r.global && !r.sticky { index = 0 } - if index >= 0 && index <= target.length() { + if index >= 0 && index <= int64(target.length()) { result = r.pattern.FindSubmatchIndex(target, int(index)) } if result == nil || r.sticky && result[0] != 0 { diff --git a/runtime.go b/runtime.go index ed5f088f..d7f45694 100644 --- a/runtime.go +++ b/runtime.go @@ -10,6 +10,7 @@ import ( "math/bits" "math/rand" "reflect" + "runtime" "strconv" "time" @@ -17,7 +18,7 @@ import ( js_ast "github.com/dop251/goja/ast" "github.com/dop251/goja/parser" - "runtime" + "github.com/dop251/goja/unistring" ) const ( @@ -88,7 +89,6 @@ type global struct { RegExpPrototype *Object DatePrototype *Object SymbolPrototype *Object - ArrayIterator *Object ArrayBufferPrototype *Object DataViewPrototype *Object @@ -98,10 +98,11 @@ type global struct { MapPrototype *Object SetPrototype *Object - IteratorPrototype *Object - ArrayIteratorPrototype *Object - MapIteratorPrototype *Object - SetIteratorPrototype *Object + IteratorPrototype *Object + ArrayIteratorPrototype *Object + MapIteratorPrototype *Object + SetIteratorPrototype *Object + StringIteratorPrototype *Object ErrorPrototype *Object TypeErrorPrototype *Object @@ -158,7 +159,7 @@ type Runtime struct { now Now _collator *collate.Collator - symbolRegistry map[string]*valueSymbol + symbolRegistry map[unistring.String]*valueSymbol typeInfoCache map[reflect.Type]*reflectTypeInfo fieldNameMapper FieldNameMapper @@ -169,7 +170,7 @@ type Runtime struct { type StackFrame struct { prg *Program - funcName string + funcName unistring.String pc int } @@ -187,7 +188,7 @@ func (f *StackFrame) FuncName() string { if f.funcName == "" { return "" } - return f.funcName + return f.funcName.String() } func (f *StackFrame) Position() Position { @@ -203,7 +204,7 @@ func (f *StackFrame) Position() Position { func (f *StackFrame) Write(b *bytes.Buffer) { if f.prg != nil { if n := f.prg.funcName; n != "" { - b.WriteString(n) + b.WriteString(n.String()) b.WriteString(" (") } if n := f.prg.src.name; n != "" { @@ -221,7 +222,7 @@ func (f *StackFrame) Write(b *bytes.Buffer) { } } else { if f.funcName != "" { - b.WriteString(f.funcName) + b.WriteString(f.funcName.String()) b.WriteString(" (") } b.WriteString("native") @@ -311,7 +312,7 @@ func (e *Exception) Value() Value { } func (r *Runtime) addToGlobal(name string, value Value) { - r.globalObject.self._putProp(name, value, true, false, true) + r.globalObject.self._putProp(unistring.String(name), value, true, false, true) } func (r *Runtime) createIterProto(val *Object) objectImpl { @@ -382,7 +383,7 @@ func (r *Runtime) newError(typ *Object, format string, args ...interface{}) Valu return r.builtin_new(typ, []Value{newStringValue(msg)}) } -func (r *Runtime) throwReferenceError(name string) { +func (r *Runtime) throwReferenceError(name unistring.String) { panic(r.newError(r.global.ReferenceError, "%s is not defined", name)) } @@ -431,7 +432,7 @@ func (r *Runtime) NewGoError(err error) *Object { return e } -func (r *Runtime) newFunc(name string, len int, strict bool) (f *funcObject) { +func (r *Runtime) newFunc(name unistring.String, len int, strict bool) (f *funcObject) { v := &Object{runtime: r} f = &funcObject{} @@ -448,7 +449,7 @@ func (r *Runtime) newFunc(name string, len int, strict bool) (f *funcObject) { return } -func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *nativeFuncObject { +func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length int) *nativeFuncObject { f := &nativeFuncObject{ baseFuncObject: baseFuncObject{ baseObject: baseObject{ @@ -469,7 +470,7 @@ func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, con return f } -func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name string, length int) *Object { +func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name unistring.String, length int) *Object { v := &Object{runtime: r} f := &nativeFuncObject{ @@ -501,7 +502,7 @@ func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name return v } -func (r *Runtime) newNativeConstructOnly(v *Object, ctor func(args []Value, newTarget *Object) *Object, defaultProto *Object, name string, length int) *nativeFuncObject { +func (r *Runtime) newNativeConstructOnly(v *Object, ctor func(args []Value, newTarget *Object) *Object, defaultProto *Object, name unistring.String, length int) *nativeFuncObject { if v == nil { v = &Object{runtime: r} } @@ -534,7 +535,7 @@ func (r *Runtime) newNativeConstructOnly(v *Object, ctor func(args []Value, newT return f } -func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *Object { +func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length int) *Object { v := &Object{runtime: r} f := &nativeFuncObject{ @@ -558,7 +559,7 @@ func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(ar return v } -func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) *nativeFuncObject { +func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length int) *nativeFuncObject { f := &nativeFuncObject{ baseFuncObject: baseFuncObject{ baseObject: baseObject{ @@ -579,11 +580,11 @@ func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Val return f } -func (r *Runtime) newNativeFuncConstruct(construct func(args []Value, proto *Object) *Object, name string, prototype *Object, length int) *Object { +func (r *Runtime) newNativeFuncConstruct(construct func(args []Value, proto *Object) *Object, name unistring.String, prototype *Object, length int) *Object { return r.newNativeFuncConstructProto(construct, name, prototype, r.global.FunctionPrototype, length) } -func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto *Object) *Object, name string, prototype, proto *Object, length int) *Object { +func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto *Object) *Object, name unistring.String, prototype, proto *Object, length int) *Object { v := &Object{runtime: r} f := &nativeFuncObject{} @@ -929,6 +930,15 @@ func toLength(v Value) int64 { return i } +func toInt(i int64) int { + if bits.UintSize == 32 { + if i > math.MaxInt32 || i < math.MinInt32 { + panic(rangeError("Integer value overflows 32-bit int")) + } + } + return int(i) +} + func (r *Runtime) toIndex(v Value) int { intIdx := v.ToInteger() if intIdx >= 0 && intIdx < maxInt { @@ -1178,10 +1188,10 @@ func (r *Runtime) ToValue(i interface{}) Value { return valueFalse } case func(FunctionCall) Value: - name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() + name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()) return r.newNativeFunc(i, nil, name, nil, 0) case func(ConstructorCall) *Object: - name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() + name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()) return r.newNativeConstructor(i, name, 0) case int: return intToValue(int64(i)) @@ -1309,7 +1319,7 @@ func (r *Runtime) ToValue(i interface{}) Value { obj.self = a return obj case reflect.Func: - name := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() + name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()) return r.newNativeFunc(r.wrapReflectFunc(value), nil, name, nil, value.Type().NumIn()) } @@ -1559,7 +1569,7 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro if field.Anonymous { v = o } else { - v = o.self.getStr(name, nil) + v = o.self.getStr(unistring.NewFromString(name), nil) } if v != nil { @@ -1650,12 +1660,12 @@ func (r *Runtime) GlobalObject() *Object { // Set the specified value as a property of the global object. // The value is first converted using ToValue() func (r *Runtime) Set(name string, value interface{}) { - r.globalObject.self.setOwnStr(name, r.ToValue(value), false) + r.globalObject.self.setOwnStr(unistring.NewFromString(name), r.ToValue(value), false) } // Get the specified property of the global object. func (r *Runtime) Get(name string) Value { - return r.globalObject.self.getStr(name, nil) + return r.globalObject.self.getStr(unistring.NewFromString(name), nil) } // SetRandSource sets random source for this Runtime. If not called, the default math/rand is used. @@ -1846,7 +1856,7 @@ func toPropertyKey(key Value) Value { return key.ToPrimitiveString() } -func (r *Runtime) getVStr(v Value, p string) Value { +func (r *Runtime) getVStr(v Value, p unistring.String) Value { o := v.ToObject(r) return o.self.getStr(p, v) } @@ -1933,3 +1943,15 @@ func isArray(object *Object) bool { return false } } + +func isRegexp(v Value) bool { + if o, ok := v.(*Object); ok { + matcher := o.self.getSym(symMatch, nil) + if matcher != nil && matcher != _undefined { + return matcher.ToBoolean() + } + _, reg := o.self.(*regexpObject) + return reg + } + return false +} diff --git a/runtime_test.go b/runtime_test.go index ae1127f3..aef72246 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "runtime" + "strconv" "testing" "time" ) @@ -1146,7 +1147,7 @@ func TestInterruptInWrappedFunction(t *testing.T) { rt.Interrupt(errors.New("hi")) }() - v, err = fn(nil) + _, err = fn(nil) if err == nil { t.Fatal("expected error") } @@ -1173,7 +1174,7 @@ func TestRunLoopPreempt(t *testing.T) { vm.Interrupt(errors.New("hi")) }() - v, err = fn(nil) + _, err = fn(nil) if err == nil { t.Fatal("expected error") } @@ -1523,3 +1524,44 @@ func BenchmarkMainLoop(b *testing.B) { vm.RunProgram(prg) } } + +func BenchmarkStringMapGet(b *testing.B) { + m := make(map[string]Value) + for i := 0; i < 100; i++ { + m[strconv.Itoa(i)] = intToValue(int64(i)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + if m["50"] == nil { + b.Fatal() + } + } +} + +func BenchmarkValueStringMapGet(b *testing.B) { + m := make(map[valueString]Value) + for i := 0; i < 100; i++ { + m[asciiString(strconv.Itoa(i))] = intToValue(int64(i)) + } + b.ResetTimer() + var key valueString = asciiString("50") + for i := 0; i < b.N; i++ { + if m[key] == nil { + b.Fatal() + } + } +} + +func BenchmarkAsciiStringMapGet(b *testing.B) { + m := make(map[asciiString]Value) + for i := 0; i < 100; i++ { + m[asciiString(strconv.Itoa(i))] = intToValue(int64(i)) + } + b.ResetTimer() + var key = asciiString("50") + for i := 0; i < b.N; i++ { + if m[key] == nil { + b.Fatal() + } + } +} diff --git a/string.go b/string.go index 58b0249f..f1e42022 100644 --- a/string.go +++ b/string.go @@ -3,8 +3,11 @@ package goja import ( "io" "strconv" + "strings" "unicode/utf16" "unicode/utf8" + + "github.com/dop251/goja/unistring" ) const ( @@ -46,37 +49,121 @@ var ( type valueString interface { Value - charAt(int64) rune - length() int64 + charAt(int) rune + length() int concat(valueString) valueString - substring(start, end int64) valueString + substring(start, end int) valueString compareTo(valueString) int reader(start int) io.RuneReader - index(valueString, int64) int64 - lastIndex(valueString, int64) int64 + index(valueString, int) int + lastIndex(valueString, int) int toLower() valueString toUpper() valueString toTrimmedUTF8() string } +type stringIterObject struct { + baseObject + reader io.RuneReader +} + +func isUTF16FirstSurrogate(r rune) bool { + return r >= 0xD800 && r <= 0xDBFF +} + +func isUTF16SecondSurrogate(r rune) bool { + return r >= 0xDC00 && r <= 0xDFFF +} + +func (si *stringIterObject) next() Value { + if si.reader == nil { + return si.val.runtime.createIterResultObject(_undefined, true) + } + r, _, err := si.reader.ReadRune() + if err == io.EOF { + si.reader = nil + return si.val.runtime.createIterResultObject(_undefined, true) + } + return si.val.runtime.createIterResultObject(stringFromRune(r), false) +} + +func stringFromRune(r rune) valueString { + if r < utf8.RuneSelf { + var sb strings.Builder + sb.Grow(1) + sb.WriteByte(byte(r)) + return asciiString(sb.String()) + } + var sb unicodeStringBuilder + if r <= 0xFFFF { + sb.Grow(1) + } else { + sb.Grow(2) + } + sb.writeRune(r) + return sb.string() +} + +func (r *Runtime) createStringIterator(s valueString) Value { + o := &Object{runtime: r} + + si := &stringIterObject{ + reader: s.reader(0), + } + si.class = classStringIterator + si.val = o + si.extensible = true + o.self = si + si.prototype = r.global.StringIteratorPrototype + si.init() + + return o +} + type stringObject struct { baseObject value valueString - length int64 + length int lengthProp valueProperty } -func newUnicodeString(s string) valueString { - return unicodeString(utf16.Encode([]rune(s))) -} - func newStringValue(s string) valueString { + utf16Size := 0 + ascii := true for _, chr := range s { + utf16Size++ if chr >= utf8.RuneSelf { - return newUnicodeString(s) + ascii = false + if chr > 0xFFFF { + utf16Size++ + } } } - return asciiString(s) + if ascii { + return asciiString(s) + } + buf := make([]uint16, utf16Size+1) + buf[0] = unistring.BOM + c := 1 + for _, chr := range s { + if chr <= 0xFFFF { + buf[c] = uint16(chr) + } else { + first, second := utf16.EncodeRune(chr) + buf[c] = uint16(first) + c++ + buf[c] = uint16(second) + } + c++ + } + return unicodeString(buf) +} + +func stringValueFromRaw(raw unistring.String) valueString { + if b := raw.AsUtf16(); b != nil { + return unicodeString(b) + } + return asciiString(raw) } func (s *stringObject) init() { @@ -88,12 +175,12 @@ func (s *stringObject) setLength() { if s.value != nil { s.length = s.value.length() } - s.lengthProp.value = intToValue(s.length) + s.lengthProp.value = intToValue(int64(s.length)) s._put("length", &s.lengthProp) } -func (s *stringObject) getStr(name string, receiver Value) Value { - if i := strToIdx64(name); i >= 0 && i < s.length { +func (s *stringObject) getStr(name unistring.String, receiver Value) Value { + if i := strToGoIdx(name); i >= 0 && i < s.length { return s._getIdx(i) } return s.baseObject.getStr(name, receiver) @@ -102,16 +189,16 @@ func (s *stringObject) getStr(name string, receiver Value) Value { func (s *stringObject) getIdx(idx valueInt, receiver Value) Value { i := int64(idx) if i >= 0 { - if i < s.length { - return s._getIdx(i) + if i < int64(s.length) { + return s._getIdx(int(i)) } return nil } - return s.baseObject.getStr(idx.String(), receiver) + return s.baseObject.getStr(idx.string(), receiver) } -func (s *stringObject) getOwnPropStr(name string) Value { - if i := strToIdx64(name); i >= 0 && i < s.length { +func (s *stringObject) getOwnPropStr(name unistring.String) Value { + if i := strToGoIdx(name); i >= 0 && i < s.length { val := s._getIdx(i) return &valueProperty{ value: val, @@ -125,8 +212,8 @@ func (s *stringObject) getOwnPropStr(name string) Value { func (s *stringObject) getOwnPropIdx(idx valueInt) Value { i := int64(idx) if i >= 0 { - if i < s.length { - val := s._getIdx(i) + if i < int64(s.length) { + val := s._getIdx(int(i)) return &valueProperty{ value: val, enumerable: true, @@ -135,15 +222,15 @@ func (s *stringObject) getOwnPropIdx(idx valueInt) Value { return nil } - return s.baseObject.getOwnPropStr(idx.String()) + return s.baseObject.getOwnPropStr(idx.string()) } -func (s *stringObject) _getIdx(idx int64) Value { +func (s *stringObject) _getIdx(idx int) Value { return s.value.substring(idx, idx+1) } -func (s *stringObject) setOwnStr(name string, val Value, throw bool) bool { - if i := strToIdx64(name); i >= 0 && i < s.length { +func (s *stringObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + if i := strToGoIdx(name); i >= 0 && i < s.length { s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i) return false } @@ -153,15 +240,15 @@ func (s *stringObject) setOwnStr(name string, val Value, throw bool) bool { func (s *stringObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { i := int64(idx) - if i >= 0 && i < s.length { + if i >= 0 && i < int64(s.length) { s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i) return false } - return s.baseObject.setOwnStr(idx.String(), val, throw) + return s.baseObject.setOwnStr(idx.string(), val, throw) } -func (s *stringObject) setForeignStr(name string, val, receiver Value, throw bool) (bool, bool) { +func (s *stringObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { return s._setForeignStr(name, s.getOwnPropStr(name), val, receiver, throw) } @@ -169,8 +256,8 @@ func (s *stringObject) setForeignIdx(idx valueInt, val, receiver Value, throw bo return s._setForeignIdx(idx, s.getOwnPropIdx(idx), val, receiver, throw) } -func (s *stringObject) defineOwnPropertyStr(name string, descr PropertyDescriptor, throw bool) bool { - if i := strToIdx64(name); i >= 0 && i < s.length { +func (s *stringObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if i := strToGoIdx(name); i >= 0 && i < s.length { s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i) return false } @@ -180,25 +267,25 @@ func (s *stringObject) defineOwnPropertyStr(name string, descr PropertyDescripto func (s *stringObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { i := int64(idx) - if i >= 0 && i < s.length { + if i >= 0 && i < int64(s.length) { s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i) return false } - return s.baseObject.defineOwnPropertyStr(idx.String(), descr, throw) + return s.baseObject.defineOwnPropertyStr(idx.string(), descr, throw) } type stringPropIter struct { str valueString // separate, because obj can be the singleton obj *stringObject - idx, length int64 + idx, length int } func (i *stringPropIter) next() (propIterItem, iterNextFunc) { if i.idx < i.length { - name := strconv.FormatInt(i.idx, 10) + name := strconv.Itoa(i.idx) i.idx++ - return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next + return propIterItem{name: unistring.String(name), enumerable: _ENUM_TRUE}, i.next } return i.obj.baseObject.enumerateUnfiltered()() @@ -213,15 +300,15 @@ func (s *stringObject) enumerateUnfiltered() iterNextFunc { } func (s *stringObject) ownKeys(all bool, accum []Value) []Value { - for i := int64(0); i < s.length; i++ { - accum = append(accum, asciiString(strconv.FormatInt(i, 10))) + for i := 0; i < s.length; i++ { + accum = append(accum, asciiString(strconv.Itoa(i))) } return s.baseObject.ownKeys(all, accum) } -func (s *stringObject) deleteStr(name string, throw bool) bool { - if i := strToIdx64(name); i >= 0 && i < s.length { +func (s *stringObject) deleteStr(name unistring.String, throw bool) bool { + if i := strToGoIdx(name); i >= 0 && i < s.length { s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i) return false } @@ -231,16 +318,16 @@ func (s *stringObject) deleteStr(name string, throw bool) bool { func (s *stringObject) deleteIdx(idx valueInt, throw bool) bool { i := int64(idx) - if i >= 0 && i < s.length { + if i >= 0 && i < int64(s.length) { s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i) return false } - return s.baseObject.deleteStr(idx.String(), throw) + return s.baseObject.deleteStr(idx.string(), throw) } -func (s *stringObject) hasOwnPropertyStr(name string) bool { - if i := strToIdx64(name); i >= 0 && i < s.length { +func (s *stringObject) hasOwnPropertyStr(name unistring.String) bool { + if i := strToGoIdx(name); i >= 0 && i < s.length { return true } return s.baseObject.hasOwnPropertyStr(name) @@ -248,8 +335,8 @@ func (s *stringObject) hasOwnPropertyStr(name string) bool { func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool { i := int64(idx) - if i >= 0 && i < s.length { + if i >= 0 && i < int64(s.length) { return true } - return s.baseObject.hasOwnPropertyStr(idx.String()) + return s.baseObject.hasOwnPropertyStr(idx.string()) } diff --git a/string_ascii.go b/string_ascii.go index b55dd7fb..ea4d92ec 100644 --- a/string_ascii.go +++ b/string_ascii.go @@ -2,12 +2,14 @@ package goja import ( "fmt" - "hash" + "hash/maphash" "io" "math" "reflect" "strconv" "strings" + + "github.com/dop251/goja/unistring" ) type asciiString string @@ -218,19 +220,19 @@ func (s asciiString) baseObject(r *Runtime) *Object { return ss.val } -func (s asciiString) hash(hash hash.Hash64) uint64 { - _, _ = hash.Write([]byte(s)) +func (s asciiString) hash(hash *maphash.Hash) uint64 { + _, _ = hash.WriteString(string(s)) h := hash.Sum64() hash.Reset() return h } -func (s asciiString) charAt(idx int64) rune { +func (s asciiString) charAt(idx int) rune { return rune(s[idx]) } -func (s asciiString) length() int64 { - return int64(len(s)) +func (s asciiString) length() int { + return len(s) } func (s asciiString) concat(other valueString) valueString { @@ -240,21 +242,21 @@ func (s asciiString) concat(other valueString) valueString { copy(b, s) copy(b[len(s):], other) return asciiString(b) - //return asciiString(string(s) + string(other)) case unicodeString: b := make([]uint16, len(s)+len(other)) + b[0] = unistring.BOM for i := 0; i < len(s); i++ { - b[i] = uint16(s[i]) + b[i+1] = uint16(s[i]) } - copy(b[len(s):], other) + copy(b[len(s)+1:], other[1:]) return unicodeString(b) default: - panic(fmt.Errorf("Unknown string type: %T", other)) + panic(fmt.Errorf("unknown string type: %T", other)) } } -func (s asciiString) substring(start, end int64) valueString { - return asciiString(s[start:end]) +func (s asciiString) substring(start, end int) valueString { + return s[start:end] } func (s asciiString) compareTo(other valueString) int { @@ -264,13 +266,13 @@ func (s asciiString) compareTo(other valueString) int { case unicodeString: return strings.Compare(string(s), other.String()) default: - panic(fmt.Errorf("Unknown string type: %T", other)) + panic(fmt.Errorf("unknown string type: %T", other)) } } -func (s asciiString) index(substr valueString, start int64) int64 { +func (s asciiString) index(substr valueString, start int) int { if substr, ok := substr.(asciiString); ok { - p := int64(strings.Index(string(s[start:]), string(substr))) + p := strings.Index(string(s[start:]), string(substr)) if p >= 0 { return p + start } @@ -278,16 +280,16 @@ func (s asciiString) index(substr valueString, start int64) int64 { return -1 } -func (s asciiString) lastIndex(substr valueString, pos int64) int64 { +func (s asciiString) lastIndex(substr valueString, pos int) int { if substr, ok := substr.(asciiString); ok { - end := pos + int64(len(substr)) + end := pos + len(substr) var ss string - if end > int64(len(s)) { + if end > len(s) { ss = string(s) } else { ss = string(s[:end]) } - return int64(strings.LastIndex(ss, string(substr))) + return strings.LastIndex(ss, string(substr)) } return -1 } @@ -304,6 +306,10 @@ func (s asciiString) toTrimmedUTF8() string { return strings.TrimSpace(string(s)) } +func (s asciiString) string() unistring.String { + return unistring.String(s) +} + func (s asciiString) Export() interface{} { return string(s) } diff --git a/string_unicode.go b/string_unicode.go index d20149fa..53238f48 100644 --- a/string_unicode.go +++ b/string_unicode.go @@ -3,17 +3,18 @@ package goja import ( "errors" "fmt" - "github.com/dop251/goja/parser" - "golang.org/x/text/cases" - "golang.org/x/text/language" - "hash" + "hash/maphash" "io" "math" "reflect" "strings" "unicode/utf16" "unicode/utf8" - "unsafe" + + "github.com/dop251/goja/parser" + "github.com/dop251/goja/unistring" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) type unicodeString []uint16 @@ -27,6 +28,11 @@ type runeReaderReplace struct { wrapped io.RuneReader } +type unicodeStringBuilder struct { + buf []uint16 + unicode bool +} + var ( InvalidRuneError = errors.New("Invalid rune") ) @@ -43,33 +49,101 @@ func (rr runeReaderReplace) ReadRune() (r rune, size int, err error) { func (rr *unicodeRuneReader) ReadRune() (r rune, size int, err error) { if rr.pos < len(rr.s) { r = rune(rr.s[rr.pos]) - if r != utf8.RuneError { - if utf16.IsSurrogate(r) { - if rr.pos+1 < len(rr.s) { - r1 := utf16.DecodeRune(r, rune(rr.s[rr.pos+1])) + size++ + rr.pos++ + if isUTF16FirstSurrogate(r) { + if rr.pos < len(rr.s) { + second := rune(rr.s[rr.pos]) + if isUTF16SecondSurrogate(second) { + r = utf16.DecodeRune(r, second) size++ rr.pos++ - if r1 == utf8.RuneError { - err = InvalidRuneError - } else { - r = r1 - } } else { err = InvalidRuneError } + } else { + err = InvalidRuneError } + } else if isUTF16SecondSurrogate(r) { + err = InvalidRuneError } - size++ - rr.pos++ } else { err = io.EOF } return } +func (b *unicodeStringBuilder) grow(n int) { + if cap(b.buf)-len(b.buf) < n { + buf := make([]uint16, len(b.buf), 2*cap(b.buf)+n) + copy(buf, b.buf) + b.buf = buf + } +} + +func (b *unicodeStringBuilder) Grow(n int) { + b.grow(n + 1) +} + +func (b *unicodeStringBuilder) ensureStarted(initialSize int) { + b.grow(len(b.buf) + initialSize + 1) + if len(b.buf) == 0 { + b.buf = append(b.buf, unistring.BOM) + } +} + +func (b *unicodeStringBuilder) writeString(s valueString) { + b.ensureStarted(int(s.length())) + switch s := s.(type) { + case unicodeString: + b.buf = append(b.buf, s[1:]...) + b.unicode = true + case asciiString: + for i := 0; i < len(s); i++ { + b.buf = append(b.buf, uint16(s[i])) + } + default: + panic(fmt.Errorf("unsupported string type: %T", s)) + } +} + +func (b *unicodeStringBuilder) string() valueString { + if b.unicode { + return unicodeString(b.buf) + } + if len(b.buf) == 0 { + return stringEmpty + } + buf := make([]byte, 0, len(b.buf)-1) + for _, c := range b.buf[1:] { + buf = append(buf, byte(c)) + } + return asciiString(buf) +} + +func (b *unicodeStringBuilder) writeRune(r rune) { + if r <= 0xFFFF { + b.ensureStarted(1) + b.buf = append(b.buf, uint16(r)) + b.unicode = r >= utf8.RuneSelf + } else { + b.ensureStarted(2) + first, second := utf16.EncodeRune(r) + b.buf = append(b.buf, uint16(first), uint16(second)) + b.unicode = true + } +} + +func (b *unicodeStringBuilder) writeASCII(bytes []byte) { + b.ensureStarted(len(bytes)) + for _, c := range bytes { + b.buf = append(b.buf, uint16(c)) + } +} + func (s unicodeString) reader(start int) io.RuneReader { return &unicodeRuneReader{ - s: s[start:], + s: s[start+1:], } } @@ -150,18 +224,21 @@ func (s unicodeString) baseObject(r *Runtime) *Object { return ss.val } -func (s unicodeString) charAt(idx int64) rune { - return rune(s[idx]) +func (s unicodeString) charAt(idx int) rune { + return rune(s[idx+1]) } -func (s unicodeString) length() int64 { - return int64(len(s)) +func (s unicodeString) length() int { + return len(s) - 1 } func (s unicodeString) concat(other valueString) valueString { switch other := other.(type) { case unicodeString: - return unicodeString(append(s, other...)) + b := make(unicodeString, len(s)+len(other)-1) + copy(b, s) + copy(b[len(s):], other[1:]) + return b case asciiString: b := make([]uint16, len(s)+len(other)) copy(b, s) @@ -175,11 +252,14 @@ func (s unicodeString) concat(other valueString) valueString { } } -func (s unicodeString) substring(start, end int64) valueString { - ss := s[start:end] +func (s unicodeString) substring(start, end int) valueString { + ss := s[start+1 : end+1] for _, c := range ss { if c >= utf8.RuneSelf { - return unicodeString(ss) + b := make(unicodeString, end-start+1) + b[0] = unistring.BOM + copy(b[1:], ss) + return b } } as := make([]byte, end-start) @@ -190,32 +270,32 @@ func (s unicodeString) substring(start, end int64) valueString { } func (s unicodeString) String() string { - return string(utf16.Decode(s)) + return string(utf16.Decode(s[1:])) } func (s unicodeString) compareTo(other valueString) int { return strings.Compare(s.String(), other.String()) } -func (s unicodeString) index(substr valueString, start int64) int64 { +func (s unicodeString) index(substr valueString, start int) int { var ss []uint16 switch substr := substr.(type) { case unicodeString: - ss = substr + ss = substr[1:] case asciiString: ss = make([]uint16, len(substr)) for i := 0; i < len(substr); i++ { ss[i] = uint16(substr[i]) } default: - panic(fmt.Errorf("Unknown string type: %T", substr)) + panic(fmt.Errorf("unknown string type: %T", substr)) } - + s1 := s[1:] // TODO: optimise - end := int64(len(s) - len(ss)) + end := len(s1) - len(ss) for start <= end { - for i := int64(0); i < int64(len(ss)); i++ { - if s[start+i] != ss[i] { + for i := 0; i < len(ss); i++ { + if s1[start+i] != ss[i] { goto nomatch } } @@ -227,11 +307,11 @@ func (s unicodeString) index(substr valueString, start int64) int64 { return -1 } -func (s unicodeString) lastIndex(substr valueString, start int64) int64 { +func (s unicodeString) lastIndex(substr valueString, start int) int { var ss []uint16 switch substr := substr.(type) { case unicodeString: - ss = substr + ss = substr[1:] case asciiString: ss = make([]uint16, len(substr)) for i := 0; i < len(substr); i++ { @@ -241,13 +321,14 @@ func (s unicodeString) lastIndex(substr valueString, start int64) int64 { panic(fmt.Errorf("Unknown string type: %T", substr)) } - if maxStart := int64(len(s) - len(ss)); start > maxStart { + s1 := s[1:] + if maxStart := len(s1) - len(ss); start > maxStart { start = maxStart } // TODO: optimise for start >= 0 { - for i := int64(0); i < int64(len(ss)); i++ { - if s[start+i] != ss[i] { + for i := 0; i < len(ss); i++ { + if s1[start+i] != ss[i] { goto nomatch } } @@ -259,6 +340,10 @@ func (s unicodeString) lastIndex(substr valueString, start int64) int64 { return -1 } +func unicodeStringFromRunes(r []rune) unicodeString { + return unistring.NewFromRunes(r).AsUtf16() +} + func (s unicodeString) toLower() valueString { caser := cases.Lower(language.Und) r := []rune(caser.String(s.String())) @@ -279,7 +364,7 @@ func (s unicodeString) toLower() valueString { if ascii { return asciiString(r) } - return unicodeString(utf16.Encode(r)) + return unicodeStringFromRunes(r) } func (s unicodeString) toUpper() valueString { @@ -295,9 +380,13 @@ func (s unicodeString) ExportType() reflect.Type { return reflectTypeString } -func (s unicodeString) hash(hash hash.Hash64) uint64 { - _, _ = hash.Write(*(*[]byte)(unsafe.Pointer(&s))) +func (s unicodeString) hash(hash *maphash.Hash) uint64 { + _, _ = hash.WriteString(string(unistring.FromUtf16(s))) h := hash.Sum64() hash.Reset() return h } + +func (s unicodeString) string() unistring.String { + return unistring.FromUtf16(s) +} diff --git a/tc39_test.go b/tc39_test.go index 090c2b6d..a704ce9c 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -7,9 +7,11 @@ import ( "io/ioutil" "os" "path" + "sort" "strings" "sync" "testing" + "time" ) const ( @@ -19,7 +21,7 @@ const ( var ( invalidFormatError = errors.New("Invalid file format") - ignorableTestError = &valueSymbol{} + ignorableTestError = newSymbol(stringEmpty) sabStub = MustCompile("sabStub.js", ` Object.defineProperty(this, "SharedArrayBuffer", { @@ -32,18 +34,11 @@ var ( var ( skipList = map[string]bool{ - "test/language/literals/regexp/S7.8.5_A1.1_T2.js": true, // UTF-16 - "test/language/literals/regexp/S7.8.5_A1.4_T2.js": true, // UTF-16 - "test/language/literals/regexp/S7.8.5_A2.1_T2.js": true, // UTF-16 - "test/language/literals/regexp/S7.8.5_A2.4_T2.js": true, // UTF-16 "test/built-ins/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 "test/annexB/built-ins/escape/escape-above-astral.js": true, // \u{xxxxx} - // utf-16 - "test/built-ins/Array/prototype/concat/Array.prototype.concat_spreadable-string-wrapper.js": true, - // class "test/language/statements/class/subclass/builtin-objects/Symbol/symbol-valid-as-extends-value.js": true, "test/language/statements/class/subclass/builtin-objects/Symbol/new-symbol-with-super-throws.js": true, @@ -63,12 +58,16 @@ var ( "test/language/statements/class/subclass/builtin-objects/TypedArray/regular-subclassing.js": true, "test/language/statements/class/subclass/builtin-objects/DataView/super-must-be-called.js": true, "test/language/statements/class/subclass/builtin-objects/DataView/regular-subclassing.js": true, + "test/language/statements/class/subclass/builtin-objects/String/super-must-be-called.js": true, + "test/language/statements/class/subclass/builtin-objects/String/regular-subclassing.js": true, + "test/language/statements/class/subclass/builtin-objects/String/length.js": true, // full unicode regexp flag "test/built-ins/RegExp/prototype/Symbol.match/u-advance-after-empty.js": true, "test/built-ins/RegExp/prototype/Symbol.match/get-unicode-error.js": true, "test/built-ins/RegExp/prototype/Symbol.match/builtin-success-u-return-val-groups.js": true, "test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js": true, + "test/built-ins/RegExp/unicode_identity_escape.js": true, // object literals "test/built-ins/Array/from/source-object-iterator-1.js": true, @@ -86,6 +85,12 @@ var ( // arrow-function "test/built-ins/Object/prototype/toString/proxy-function.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, } featuresBlackList = []string{ @@ -101,9 +106,7 @@ var ( "12.9.4", "19.1", "19.4", - "21.1.3.14", - "21.1.3.15", - "21.1.3.17", + "21.1", "21.2.5.6", "22.1.2.1", "22.1.2.3", @@ -126,6 +129,7 @@ var ( esIdPrefixWhiteList = []string{ "sec-array.prototype.includes", "sec-%typedarray%", + "sec-string.prototype", } ) @@ -134,11 +138,21 @@ type tc39Test struct { f func(t *testing.T) } +type tc39BenchmarkItem struct { + name string + duration time.Duration +} + +type tc39BenchmarkData []tc39BenchmarkItem + type tc39TestCtx struct { base string t *testing.T prgCache map[string]*Program prgCacheLock sync.Mutex + enableBench bool + benchmark tc39BenchmarkData + benchLock sync.Mutex testQueue []tc39Test } @@ -334,6 +348,11 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { } } + var startTime time.Time + if ctx.enableBench { + startTime = time.Now() + } + hasRaw := meta.hasFlag("raw") if hasRaw || !meta.hasFlag("onlyStrict") { @@ -348,6 +367,15 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { ctx.runTC39Test(name, "'use strict';\n"+src, meta, t) } + if ctx.enableBench { + ctx.benchLock.Lock() + ctx.benchmark = append(ctx.benchmark, tc39BenchmarkItem{ + name: name, + duration: time.Since(startTime), + }) + ctx.benchLock.Unlock() + } + } func (ctx *tc39TestCtx) init() { @@ -459,32 +487,48 @@ func TestTC39(t *testing.T) { ctx := &tc39TestCtx{ base: tc39BASE, - t: t, } ctx.init() - - //ctx.runTC39File("test/language/types/number/8.5.1.js", t) - //ctx.runTC39Tests("test/language") - ctx.runTC39Tests("test/language/expressions") - ctx.runTC39Tests("test/language/arguments-object") - ctx.runTC39Tests("test/language/asi") - ctx.runTC39Tests("test/language/directive-prologue") - ctx.runTC39Tests("test/language/function-code") - ctx.runTC39Tests("test/language/eval-code") - ctx.runTC39Tests("test/language/global-code") - ctx.runTC39Tests("test/language/identifier-resolution") - ctx.runTC39Tests("test/language/identifiers") - //ctx.runTC39Tests("test/language/literals") // octal sequences in strict mode - ctx.runTC39Tests("test/language/punctuators") - ctx.runTC39Tests("test/language/reserved-words") - ctx.runTC39Tests("test/language/source-text") - ctx.runTC39Tests("test/language/statements") - ctx.runTC39Tests("test/language/types") - ctx.runTC39Tests("test/language/white-space") - ctx.runTC39Tests("test/built-ins") - ctx.runTC39Tests("test/annexB/built-ins/String/prototype/substr") - ctx.runTC39Tests("test/annexB/built-ins/escape") - ctx.runTC39Tests("test/annexB/built-ins/unescape") - - ctx.flush() + //ctx.enableBench = true + + t.Run("tc39", func(t *testing.T) { + ctx.t = t + //ctx.runTC39File("test/language/types/number/8.5.1.js", t) + //ctx.runTC39Tests("test/language") + ctx.runTC39Tests("test/language/expressions") + ctx.runTC39Tests("test/language/arguments-object") + ctx.runTC39Tests("test/language/asi") + ctx.runTC39Tests("test/language/directive-prologue") + ctx.runTC39Tests("test/language/function-code") + ctx.runTC39Tests("test/language/eval-code") + ctx.runTC39Tests("test/language/global-code") + ctx.runTC39Tests("test/language/identifier-resolution") + ctx.runTC39Tests("test/language/identifiers") + //ctx.runTC39Tests("test/language/literals") // octal sequences in strict mode + ctx.runTC39Tests("test/language/punctuators") + ctx.runTC39Tests("test/language/reserved-words") + ctx.runTC39Tests("test/language/source-text") + ctx.runTC39Tests("test/language/statements") + ctx.runTC39Tests("test/language/types") + ctx.runTC39Tests("test/language/white-space") + ctx.runTC39Tests("test/built-ins") + ctx.runTC39Tests("test/annexB/built-ins/String/prototype/substr") + ctx.runTC39Tests("test/annexB/built-ins/escape") + ctx.runTC39Tests("test/annexB/built-ins/unescape") + + ctx.flush() + }) + + if ctx.enableBench { + sort.Slice(ctx.benchmark, func(i, j int) bool { + return ctx.benchmark[i].duration > ctx.benchmark[j].duration + }) + bench := ctx.benchmark + if len(bench) > 50 { + bench = bench[:50] + } + for _, item := range bench { + fmt.Printf("%s\t%d\n", item.name, item.duration/time.Millisecond) + } + } } diff --git a/typedarrays.go b/typedarrays.go index 885bf119..9a10b0a8 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -6,6 +6,8 @@ import ( "reflect" "strconv" "unsafe" + + "github.com/dop251/goja/unistring" ) type byteOrder bool @@ -458,15 +460,15 @@ func (a *typedArrayObject) _getIdx(idx int) Value { return nil } -func strToTAIdx(s string) (int, bool) { - i, err := strconv.ParseInt(s, 10, bits.UintSize) +func strToTAIdx(s unistring.String) (int, bool) { + i, err := strconv.ParseInt(string(s), 10, bits.UintSize) if err != nil { return 0, false } return int(i), true } -func (a *typedArrayObject) getOwnPropStr(name string) Value { +func (a *typedArrayObject) getOwnPropStr(name unistring.String) Value { if idx, ok := strToTAIdx(name); ok { v := a._getIdx(idx) if v != nil { @@ -493,7 +495,7 @@ func (a *typedArrayObject) getOwnPropIdx(idx valueInt) Value { return nil } -func (a *typedArrayObject) getStr(name string, receiver Value) Value { +func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value { if idx, ok := strToTAIdx(name); ok { prop := a._getIdx(idx) if prop == nil { @@ -538,7 +540,7 @@ func (a *typedArrayObject) _hasIdx(idx int) bool { return idx >= 0 && idx < a.length } -func (a *typedArrayObject) setOwnStr(p string, v Value, throw bool) bool { +func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bool { if idx, ok := strToTAIdx(p); ok { return a._putIdx(idx, v, throw) } @@ -549,7 +551,7 @@ func (a *typedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool { return a._putIdx(toInt(int64(p)), v, throw) } -func (a *typedArrayObject) setForeignStr(p string, v, receiver Value, throw bool) (res bool, handled bool) { +func (a *typedArrayObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) { return a._setForeignStr(p, a.getOwnPropStr(p), v, receiver, throw) } @@ -557,7 +559,7 @@ func (a *typedArrayObject) setForeignIdx(p valueInt, v, receiver Value, throw bo return a._setForeignIdx(p, trueValIfPresent(a.hasOwnPropertyIdx(p)), v, receiver, throw) } -func (a *typedArrayObject) hasOwnPropertyStr(name string) bool { +func (a *typedArrayObject) hasOwnPropertyStr(name unistring.String) bool { if idx, ok := strToTAIdx(name); ok { a.viewedArrayBuf.ensureNotDetached() return idx < a.length @@ -571,14 +573,14 @@ func (a *typedArrayObject) hasOwnPropertyIdx(idx valueInt) bool { } func (a *typedArrayObject) _defineIdxProperty(idx int, desc PropertyDescriptor, throw bool) bool { - prop, ok := a._defineOwnProperty(strconv.Itoa(idx), a.getOwnPropIdx(valueInt(idx)), desc, throw) + prop, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw) if ok { return a._putIdx(idx, prop, throw) } return ok } -func (a *typedArrayObject) defineOwnPropertyStr(name string, desc PropertyDescriptor, throw bool) bool { +func (a *typedArrayObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { if idx, ok := strToTAIdx(name); ok { return a._defineIdxProperty(idx, desc, throw) } @@ -589,7 +591,7 @@ func (a *typedArrayObject) defineOwnPropertyIdx(name valueInt, desc PropertyDesc return a._defineIdxProperty(toInt(int64(name)), desc, throw) } -func (a *typedArrayObject) deleteStr(name string, throw bool) bool { +func (a *typedArrayObject) deleteStr(name unistring.String, throw bool) bool { if idx, ok := strToTAIdx(name); ok { if idx < a.length { a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String()) @@ -627,7 +629,7 @@ func (i *typedArrayPropIter) next() (propIterItem, iterNextFunc) { name := strconv.Itoa(i.idx) prop := i.a._getIdx(i.idx) i.idx++ - return propIterItem{name: name, value: prop}, i.next + return propIterItem{name: unistring.String(name), value: prop}, i.next } return i.a.baseObject.enumerateUnfiltered()() diff --git a/unistring/string.go b/unistring/string.go new file mode 100644 index 00000000..481f06cf --- /dev/null +++ b/unistring/string.go @@ -0,0 +1,122 @@ +// Package unistring contains an implementation of a hybrid ASCII/UTF-16 string. +// For ASCII strings the underlying representation is equivalent to a normal Go string. +// For unicode strings the underlying representation is UTF-16 as []uint16 with 0th element set to 0xFEFF. +// unicode.String allows representing malformed UTF-16 values (e.g. stand-alone parts of surrogate pairs) +// which cannot be represented in UTF-8. +// At the same time it is possible to use unicode.String as property keys just as efficiently as simple strings, +// (the leading 0xFEFF ensures there is no clash with ASCII string), and it is possible to convert it +// to valueString without extra allocations. +package unistring + +import ( + "reflect" + "unicode/utf16" + "unicode/utf8" + "unsafe" +) + +const ( + BOM = 0xFEFF +) + +type String string + +func NewFromString(s string) String { + ascii := true + size := 0 + for _, c := range s { + if c >= utf8.RuneSelf { + ascii = false + if c > 0xFFFF { + size++ + } + } + size++ + } + if ascii { + return String(s) + } + b := make([]uint16, size+1) + b[0] = BOM + i := 1 + for _, c := range s { + if c <= 0xFFFF { + b[i] = uint16(c) + } else { + first, second := utf16.EncodeRune(c) + b[i] = uint16(first) + i++ + b[i] = uint16(second) + } + i++ + } + return FromUtf16(b) +} + +func NewFromRunes(s []rune) String { + ascii := true + size := 0 + for _, c := range s { + if c >= utf8.RuneSelf { + ascii = false + if c > 0xFFFF { + size++ + } + } + size++ + } + if ascii { + return String(s) + } + b := make([]uint16, size+1) + b[0] = BOM + i := 1 + for _, c := range s { + if c <= 0xFFFF { + b[i] = uint16(c) + } else { + first, second := utf16.EncodeRune(c) + b[i] = uint16(first) + i++ + b[i] = uint16(second) + } + i++ + } + return FromUtf16(b) +} + +func FromUtf16(b []uint16) String { + var str string + hdr := (*reflect.StringHeader)(unsafe.Pointer(&str)) + hdr.Data = uintptr(unsafe.Pointer(&b[0])) + hdr.Len = len(b) * 2 + + return String(str) +} + +func (s String) String() string { + if b := s.AsUtf16(); b != nil { + return string(utf16.Decode(b[1:])) + } + + return string(s) +} + +func (s String) AsUtf16() []uint16 { + if len(s) < 4 || len(s)&1 != 0 { + return nil + } + l := len(s) / 2 + raw := string(s) + hdr := (*reflect.StringHeader)(unsafe.Pointer(&raw)) + a := *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{ + Data: hdr.Data, + Len: l, + Cap: l, + })) + if a[0] == BOM { + return a + } + + return nil +} diff --git a/value.go b/value.go index 18e864ec..969bca86 100644 --- a/value.go +++ b/value.go @@ -1,13 +1,14 @@ package goja import ( - "fmt" - "hash" + "hash/maphash" "math" "reflect" "regexp" "strconv" "unsafe" + + "github.com/dop251/goja/unistring" ) var ( @@ -39,6 +40,7 @@ var intCache [256]Value type Value interface { ToInteger() int64 toString() valueString + string() unistring.String ToPrimitiveString() Value String() string ToFloat() float64 @@ -53,7 +55,7 @@ type Value interface { baseObject(r *Runtime) *Object - hash(hash64 hash.Hash64) uint64 + hash(hasher *maphash.Hash) uint64 } type valueContainer interface { @@ -71,12 +73,12 @@ type valueUndefined struct { valueNull } type valueSymbol struct { - desc string + desc valueString } type valueUnresolved struct { r *Runtime - ref string + ref unistring.String } type memberUnresolved struct { @@ -127,6 +129,10 @@ func (i valueInt) toString() valueString { return asciiString(i.String()) } +func (i valueInt) string() unistring.String { + return unistring.String(i.String()) +} + func (i valueInt) ToPrimitiveString() Value { return i } @@ -195,60 +201,64 @@ func (i valueInt) ExportType() reflect.Type { return reflectTypeInt } -func (i valueInt) hash(hash.Hash64) uint64 { +func (i valueInt) hash(*maphash.Hash) uint64 { return uint64(i) } -func (o valueBool) ToInteger() int64 { - if o { +func (b valueBool) ToInteger() int64 { + if b { return 1 } return 0 } -func (o valueBool) toString() valueString { - if o { +func (b valueBool) toString() valueString { + if b { return stringTrue } return stringFalse } -func (o valueBool) ToPrimitiveString() Value { - return o +func (b valueBool) ToPrimitiveString() Value { + return b } -func (o valueBool) String() string { - if o { +func (b valueBool) String() string { + if b { return "true" } return "false" } -func (o valueBool) ToFloat() float64 { - if o { +func (b valueBool) string() unistring.String { + return unistring.String(b.String()) +} + +func (b valueBool) ToFloat() float64 { + if b { return 1.0 } return 0 } -func (o valueBool) ToBoolean() bool { - return bool(o) +func (b valueBool) ToBoolean() bool { + return bool(b) } -func (o valueBool) ToObject(r *Runtime) *Object { - return r.newPrimitiveObject(o, r.global.BooleanPrototype, "Boolean") +func (b valueBool) ToObject(r *Runtime) *Object { + return r.newPrimitiveObject(b, r.global.BooleanPrototype, "Boolean") } -func (o valueBool) ToNumber() Value { - if o { +func (b valueBool) ToNumber() Value { + if b { return valueInt(1) } return valueInt(0) } -func (o valueBool) SameAs(other Value) bool { +func (b valueBool) SameAs(other Value) bool { if other, ok := other.(valueBool); ok { - return o == other + return b == other } return false } @@ -266,26 +276,26 @@ func (b valueBool) Equals(other Value) bool { } -func (o valueBool) StrictEquals(other Value) bool { +func (b valueBool) StrictEquals(other Value) bool { if other, ok := other.(valueBool); ok { - return o == other + return b == other } return false } -func (o valueBool) baseObject(r *Runtime) *Object { +func (b valueBool) baseObject(r *Runtime) *Object { return r.global.BooleanPrototype } -func (o valueBool) Export() interface{} { - return bool(o) +func (b valueBool) Export() interface{} { + return bool(b) } -func (o valueBool) ExportType() reflect.Type { +func (b valueBool) ExportType() reflect.Type { return reflectTypeBool } -func (b valueBool) hash(hash.Hash64) uint64 { +func (b valueBool) hash(*maphash.Hash) uint64 { if b { return uint64(uintptr(unsafe.Pointer(&valueTrue))) } @@ -300,6 +310,10 @@ func (n valueNull) toString() valueString { return stringNull } +func (n valueNull) string() unistring.String { + return stringNull.string() +} + func (n valueNull) ToPrimitiveString() Value { return n } @@ -320,6 +334,10 @@ func (u valueUndefined) String() string { return "undefined" } +func (u valueUndefined) string() unistring.String { + return "undefined" +} + func (u valueUndefined) ToNumber() Value { return _NaN } @@ -338,7 +356,7 @@ func (u valueUndefined) ToFloat() float64 { return math.NaN() } -func (u valueUndefined) hash(hash.Hash64) uint64 { +func (u valueUndefined) hash(*maphash.Hash) uint64 { return uint64(uintptr(unsafe.Pointer(&_undefined))) } @@ -390,7 +408,7 @@ func (n valueNull) ExportType() reflect.Type { return reflectTypeNil } -func (n valueNull) hash(hash.Hash64) uint64 { +func (n valueNull) hash(*maphash.Hash) uint64 { return uint64(uintptr(unsafe.Pointer(&_null))) } @@ -402,6 +420,10 @@ func (p *valueProperty) toString() valueString { return stringEmpty } +func (p *valueProperty) string() unistring.String { + return "" +} + func (p *valueProperty) ToPrimitiveString() Value { return _undefined } @@ -470,20 +492,20 @@ func (p *valueProperty) StrictEquals(Value) bool { return false } -func (n *valueProperty) baseObject(r *Runtime) *Object { +func (p *valueProperty) baseObject(r *Runtime) *Object { r.typeErrorResult(true, "BUG: baseObject() is called on valueProperty") // TODO error message return nil } -func (n *valueProperty) Export() interface{} { +func (p *valueProperty) Export() interface{} { panic("Cannot export valueProperty") } -func (n *valueProperty) ExportType() reflect.Type { +func (p *valueProperty) ExportType() reflect.Type { panic("Cannot export valueProperty") } -func (n *valueProperty) hash(hash.Hash64) uint64 { +func (p *valueProperty) hash(*maphash.Hash) uint64 { panic("valueProperty should never be used in maps or sets") } @@ -503,6 +525,10 @@ func (f valueFloat) toString() valueString { return asciiString(f.String()) } +func (f valueFloat) string() unistring.String { + return unistring.String(f.String()) +} + func (f valueFloat) ToPrimitiveString() Value { return f } @@ -608,7 +634,7 @@ func (f valueFloat) ExportType() reflect.Type { return reflectTypeFloat } -func (f valueFloat) hash(hash.Hash64) uint64 { +func (f valueFloat) hash(*maphash.Hash) uint64 { if f == _negativeZero { return 0 } @@ -623,6 +649,10 @@ func (o *Object) toString() valueString { return o.self.toPrimitiveString().toString() } +func (o *Object) string() unistring.String { + return o.self.toPrimitiveString().string() +} + func (o *Object) ToPrimitiveString() Value { return o.self.toPrimitiveString().ToPrimitiveString() } @@ -688,12 +718,12 @@ func (o *Object) ExportType() reflect.Type { return o.self.exportType() } -func (o *Object) hash(hash.Hash64) uint64 { +func (o *Object) hash(*maphash.Hash) uint64 { return uint64(uintptr(unsafe.Pointer(o))) } func (o *Object) Get(name string) Value { - return o.self.getStr(name, nil) + return o.self.getStr(unistring.NewFromString(name), nil) } func (o *Object) Keys() (keys []string) { @@ -710,7 +740,7 @@ func (o *Object) Keys() (keys []string) { // configurable: configurable, enumerable: enumerable}) func (o *Object) DefineDataProperty(name string, value Value, writable, configurable, enumerable Flag) error { return tryFunc(func() { - o.self.defineOwnPropertyStr(name, PropertyDescriptor{ + o.self.defineOwnPropertyStr(unistring.NewFromString(name), PropertyDescriptor{ Value: value, Writable: writable, Configurable: configurable, @@ -723,7 +753,7 @@ func (o *Object) DefineDataProperty(name string, value Value, writable, configur // configurable: configurable, enumerable: enumerable}) func (o *Object) DefineAccessorProperty(name string, getter, setter Value, configurable, enumerable Flag) error { return tryFunc(func() { - o.self.defineOwnPropertyStr(name, PropertyDescriptor{ + o.self.defineOwnPropertyStr(unistring.NewFromString(name), PropertyDescriptor{ Getter: getter, Setter: setter, Configurable: configurable, @@ -734,7 +764,7 @@ func (o *Object) DefineAccessorProperty(name string, getter, setter Value, confi func (o *Object) Set(name string, value interface{}) error { return tryFunc(func() { - o.self.setOwnStr(name, o.runtime.ToValue(value), true) + o.self.setOwnStr(unistring.NewFromString(name), o.runtime.ToValue(value), true) }) } @@ -774,6 +804,11 @@ func (o valueUnresolved) toString() valueString { return nil } +func (o valueUnresolved) string() unistring.String { + o.throw() + return "" +} + func (o valueUnresolved) ToPrimitiveString() Value { o.throw() return nil @@ -834,7 +869,7 @@ func (o valueUnresolved) ExportType() reflect.Type { return nil } -func (o valueUnresolved) hash(hash.Hash64) uint64 { +func (o valueUnresolved) hash(*maphash.Hash) uint64 { o.throw() return 0 } @@ -852,7 +887,11 @@ func (s *valueSymbol) ToPrimitiveString() Value { } func (s *valueSymbol) String() string { - return s.descString() + return s.desc.String() +} + +func (s *valueSymbol) string() unistring.String { + return s.desc.string() } func (s *valueSymbol) ToFloat() float64 { @@ -898,12 +937,14 @@ func (s *valueSymbol) baseObject(r *Runtime) *Object { return r.newPrimitiveObject(s, r.global.SymbolPrototype, "Symbol") } -func (s *valueSymbol) hash(hash.Hash64) uint64 { +func (s *valueSymbol) hash(*maphash.Hash) uint64 { return uint64(uintptr(unsafe.Pointer(s))) } -func (s *valueSymbol) descString() string { - return fmt.Sprintf("Symbol(%s)", s.desc) +func newSymbol(s valueString) *valueSymbol { + return &valueSymbol{ + desc: asciiString("Symbol(").concat(s).concat(asciiString(")")), + } } func init() { diff --git a/vm.go b/vm.go index 65199369..eff4b4c3 100644 --- a/vm.go +++ b/vm.go @@ -7,6 +7,8 @@ import ( "strconv" "sync" "sync/atomic" + + "github.com/dop251/goja/unistring" ) const ( @@ -18,7 +20,7 @@ type valueStack []Value type stash struct { values valueStack extraArgs valueStack - names map[string]uint32 + names map[unistring.String]uint32 obj objectImpl outer *stash @@ -26,7 +28,7 @@ type stash struct { type context struct { prg *Program - funcName string + funcName unistring.String stash *stash newTarget Value pc, sb int @@ -42,12 +44,12 @@ type iterStackItem struct { type ref interface { get() Value set(Value) - refname() string + refname() unistring.String } type stashRef struct { v *Value - n string + n unistring.String } func (r stashRef) get() Value { @@ -58,13 +60,13 @@ func (r *stashRef) set(v Value) { *r.v = v } -func (r *stashRef) refname() string { +func (r *stashRef) refname() unistring.String { return r.n } type objRef struct { base objectImpl - name string + name unistring.String strict bool } @@ -76,13 +78,13 @@ func (r *objRef) set(v Value) { r.base.setOwnStr(r.name, v, r.strict) } -func (r *objRef) refname() string { +func (r *objRef) refname() unistring.String { return r.name } type unresolvedRef struct { runtime *Runtime - name string + name unistring.String } func (r *unresolvedRef) get() Value { @@ -94,14 +96,14 @@ func (r *unresolvedRef) set(Value) { r.get() } -func (r *unresolvedRef) refname() string { +func (r *unresolvedRef) refname() unistring.String { return r.name } type vm struct { r *Runtime prg *Program - funcName string + funcName unistring.String pc int stack valueStack sp, sb, args int @@ -201,7 +203,7 @@ func (s *valueStack) expand(idx int) { } } -func stashObjHas(obj objectImpl, name string) bool { +func stashObjHas(obj objectImpl, name unistring.String) bool { if obj.hasPropertyStr(name) { if unscopables, ok := obj.getSym(symUnscopables, nil).(*Object); ok { if b := unscopables.self.getStr(name, nil); b != nil { @@ -213,7 +215,7 @@ func stashObjHas(obj objectImpl, name string) bool { return false } -func (s *stash) put(name string, v Value) bool { +func (s *stash) put(name unistring.String, v Value) bool { if s.obj != nil { if stashObjHas(s.obj, name) { s.obj.setOwnStr(name, v, false) @@ -245,7 +247,7 @@ func (s *stash) getByIdx(idx uint32) Value { return _undefined } -func (s *stash) getByName(name string, _ *vm) (v Value, exists bool) { +func (s *stash) getByName(name unistring.String, _ *vm) (v Value, exists bool) { if s.obj != nil { if stashObjHas(s.obj, name) { return nilSafe(s.obj.getStr(name, nil)), true @@ -259,9 +261,9 @@ func (s *stash) getByName(name string, _ *vm) (v Value, exists bool) { //return valueUnresolved{r: vm.r, ref: name}, false } -func (s *stash) createBinding(name string) { +func (s *stash) createBinding(name unistring.String) { if s.names == nil { - s.names = make(map[string]uint32) + s.names = make(map[unistring.String]uint32) } if _, exists := s.names[name]; !exists { s.names[name] = uint32(len(s.names)) @@ -269,7 +271,7 @@ func (s *stash) createBinding(name string) { } } -func (s *stash) deleteBinding(name string) bool { +func (s *stash) deleteBinding(name unistring.String) bool { if s.obj != nil { if stashObjHas(s.obj, name) { return s.obj.deleteStr(name, false) @@ -1034,11 +1036,11 @@ func (_deleteElemStrict) exec(vm *vm) { vm.pc++ } -type deleteProp string +type deleteProp unistring.String func (d deleteProp) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-1]) - if obj.self.deleteStr(string(d), false) { + if obj.self.deleteStr(unistring.String(d), false) { vm.stack[vm.sp-1] = valueTrue } else { vm.stack[vm.sp-1] = valueFalse @@ -1046,42 +1048,42 @@ func (d deleteProp) exec(vm *vm) { vm.pc++ } -type deletePropStrict string +type deletePropStrict unistring.String func (d deletePropStrict) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-1]) - obj.self.deleteStr(string(d), true) + obj.self.deleteStr(unistring.String(d), true) vm.stack[vm.sp-1] = valueTrue vm.pc++ } -type setProp string +type setProp unistring.String func (p setProp) exec(vm *vm) { val := vm.stack[vm.sp-1] - vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(string(p), val, false) + vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(unistring.String(p), val, false) vm.stack[vm.sp-2] = val vm.sp-- vm.pc++ } -type setPropStrict string +type setPropStrict unistring.String func (p setPropStrict) exec(vm *vm) { obj := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] obj1 := vm.r.toObject(obj) - obj1.self.setOwnStr(string(p), val, true) + obj1.self.setOwnStr(unistring.String(p), val, true) vm.stack[vm.sp-2] = val vm.sp-- vm.pc++ } -type setProp1 string +type setProp1 unistring.String func (p setProp1) exec(vm *vm) { - vm.r.toObject(vm.stack[vm.sp-2]).self._putProp(string(p), vm.stack[vm.sp-1], true, true, true) + vm.r.toObject(vm.stack[vm.sp-2]).self._putProp(unistring.String(p), vm.stack[vm.sp-1], true, true, true) vm.sp-- vm.pc++ @@ -1098,7 +1100,7 @@ func (_setProto) exec(vm *vm) { vm.pc++ } -type setPropGetter string +type setPropGetter unistring.String func (s setPropGetter) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-2]) @@ -1110,13 +1112,13 @@ func (s setPropGetter) exec(vm *vm) { Enumerable: FLAG_TRUE, } - obj.self.defineOwnPropertyStr(string(s), descr, false) + obj.self.defineOwnPropertyStr(unistring.String(s), descr, false) vm.sp-- vm.pc++ } -type setPropSetter string +type setPropSetter unistring.String func (s setPropSetter) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-2]) @@ -1128,13 +1130,13 @@ func (s setPropSetter) exec(vm *vm) { Enumerable: FLAG_TRUE, } - obj.self.defineOwnPropertyStr(string(s), descr, false) + obj.self.defineOwnPropertyStr(unistring.String(s), descr, false) vm.sp-- vm.pc++ } -type getProp string +type getProp unistring.String func (g getProp) exec(vm *vm) { v := vm.stack[vm.sp-1] @@ -1142,22 +1144,23 @@ func (g getProp) exec(vm *vm) { if obj == nil { panic(vm.r.NewTypeError("Cannot read property '%s' of undefined", g)) } - vm.stack[vm.sp-1] = nilSafe(obj.self.getStr(string(g), v)) + vm.stack[vm.sp-1] = nilSafe(obj.self.getStr(unistring.String(g), v)) vm.pc++ } -type getPropCallee string +type getPropCallee unistring.String func (g getPropCallee) exec(vm *vm) { v := vm.stack[vm.sp-1] obj := v.baseObject(vm.r) + n := unistring.String(g) if obj == nil { - panic(vm.r.NewTypeError("Cannot read property '%s' of undefined or null", g)) + panic(vm.r.NewTypeError("Cannot read property '%s' of undefined or null", n)) } - prop := obj.self.getStr(string(g), v) + prop := obj.self.getStr(n, v) if prop == nil { - prop = memberUnresolved{valueUnresolved{r: vm.r, ref: string(g)}} + prop = memberUnresolved{valueUnresolved{r: vm.r, ref: n}} } vm.stack[vm.sp-1] = prop @@ -1196,7 +1199,7 @@ func (_getElemCallee) exec(vm *vm) { prop := obj.get(propName, v) if prop == nil { - prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.String()}} + prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}} } vm.stack[vm.sp-2] = prop @@ -1306,7 +1309,7 @@ func (s setLocalP) exec(vm *vm) { } type setVar struct { - name string + name unistring.String idx uint32 } @@ -1334,10 +1337,10 @@ end: vm.pc++ } -type resolveVar1 string +type resolveVar1 unistring.String func (s resolveVar1) exec(vm *vm) { - name := string(s) + name := unistring.String(s) var ref ref for stash := vm.stash; stash != nil; stash = stash.outer { if stash.obj != nil { @@ -1368,10 +1371,10 @@ end: vm.pc++ } -type deleteVar string +type deleteVar unistring.String func (d deleteVar) exec(vm *vm) { - name := string(d) + name := unistring.String(d) ret := true for stash := vm.stash; stash != nil; stash = stash.outer { if stash.obj != nil { @@ -1400,10 +1403,10 @@ end: vm.pc++ } -type deleteGlobal string +type deleteGlobal unistring.String func (d deleteGlobal) exec(vm *vm) { - name := string(d) + name := unistring.String(d) var ret bool if vm.r.globalObject.self.hasPropertyStr(name) { ret = vm.r.globalObject.self.deleteStr(name, false) @@ -1418,10 +1421,10 @@ func (d deleteGlobal) exec(vm *vm) { vm.pc++ } -type resolveVar1Strict string +type resolveVar1Strict unistring.String func (s resolveVar1Strict) exec(vm *vm) { - name := string(s) + name := unistring.String(s) var ref ref for stash := vm.stash; stash != nil; stash = stash.outer { if stash.obj != nil { @@ -1454,7 +1457,7 @@ func (s resolveVar1Strict) exec(vm *vm) { ref = &unresolvedRef{ runtime: vm.r, - name: string(s), + name: name, } end: @@ -1462,21 +1465,21 @@ end: vm.pc++ } -type setGlobal string +type setGlobal unistring.String func (s setGlobal) exec(vm *vm) { v := vm.peek() - vm.r.globalObject.self.setOwnStr(string(s), v, false) + vm.r.globalObject.self.setOwnStr(unistring.String(s), v, false) vm.pc++ } -type setGlobalStrict string +type setGlobalStrict unistring.String func (s setGlobalStrict) exec(vm *vm) { v := vm.peek() - name := string(s) + name := unistring.String(s) o := vm.r.globalObject.self if o.hasOwnPropertyStr(name) { o.setOwnStr(name, v, true) @@ -1501,14 +1504,14 @@ func (g getLocal) exec(vm *vm) { } type getVar struct { - name string + name unistring.String idx uint32 ref bool } func (g getVar) exec(vm *vm) { level := int(g.idx >> 24) - idx := uint32(g.idx & 0x00FFFFFF) + idx := g.idx & 0x00FFFFFF stash := vm.stash name := g.name for i := 0; i < level; i++ { @@ -1536,7 +1539,7 @@ end: } type resolveVar struct { - name string + name unistring.String idx uint32 strict bool } @@ -1620,10 +1623,10 @@ func (_putValue) exec(vm *vm) { vm.pc++ } -type getVar1 string +type getVar1 unistring.String func (n getVar1) exec(vm *vm) { - name := string(n) + name := unistring.String(n) var val Value for stash := vm.stash; stash != nil; stash = stash.outer { if v, exists := stash.getByName(name, vm); exists { @@ -1641,10 +1644,10 @@ func (n getVar1) exec(vm *vm) { vm.pc++ } -type getVar1Callee string +type getVar1Callee unistring.String func (n getVar1Callee) exec(vm *vm) { - name := string(n) + name := unistring.String(n) var val Value for stash := vm.stash; stash != nil; stash = stash.outer { if v, exists := stash.getByName(name, vm); exists { @@ -1774,7 +1777,7 @@ func (vm *vm) _nativeCall(f *nativeFuncObject, n int) { if f.f != nil { vm.pushCtx() vm.prg = nil - vm.funcName = f.nameProp.get(nil).String() + vm.funcName = f.nameProp.get(nil).string() ret := f.f(FunctionCall{ Arguments: vm.stack[vm.sp-n : vm.sp], This: vm.stack[vm.sp-n-2], @@ -1895,7 +1898,7 @@ func (_retStashless) exec(vm *vm) { type newFunc struct { prg *Program - name string + name unistring.String length uint32 strict bool @@ -1911,13 +1914,14 @@ func (n *newFunc) exec(vm *vm) { vm.pc++ } -type bindName string +type bindName unistring.String func (d bindName) exec(vm *vm) { + name := unistring.String(d) if vm.stash != nil { - vm.stash.createBinding(string(d)) + vm.stash.createBinding(name) } else { - vm.r.globalObject.self._putProp(string(d), _undefined, true, true, false) + vm.r.globalObject.self._putProp(name, _undefined, true, true, false) } vm.pc++ } @@ -2239,11 +2243,11 @@ func (_retFinally) exec(vm *vm) { vm.pc++ } -type enterCatch string +type enterCatch unistring.String func (varName enterCatch) exec(vm *vm) { - vm.stash.names = map[string]uint32{ - string(varName): 0, + vm.stash.names = map[unistring.String]uint32{ + unistring.String(varName): 0, } vm.pc++ } @@ -2335,7 +2339,7 @@ func (formalArgs createArgs) exec(vm *vm) { c = vm.args } for ; i < c; i++ { - args._put(strconv.Itoa(i), &mappedProperty{ + args._put(unistring.String(strconv.Itoa(i)), &mappedProperty{ valueProperty: valueProperty{ writable: true, configurable: true, @@ -2346,7 +2350,7 @@ func (formalArgs createArgs) exec(vm *vm) { } for _, v := range vm.stash.extraArgs { - args._put(strconv.Itoa(i), v) + args._put(unistring.String(strconv.Itoa(i)), v) i++ } @@ -2365,12 +2369,12 @@ func (formalArgs createArgsStrict) exec(vm *vm) { c = vm.args } for _, v := range vm.stash.values[:c] { - args._put(strconv.Itoa(i), v) + args._put(unistring.String(strconv.Itoa(i)), v) i++ } for _, v := range vm.stash.extraArgs { - args._put(strconv.Itoa(i), v) + args._put(unistring.String(strconv.Itoa(i)), v) i++ } @@ -2426,7 +2430,7 @@ func (jmp enumNext) exec(vm *vm) { l := len(vm.iterStack) - 1 item, n := vm.iterStack[l].f() if n != nil { - vm.iterStack[l].val = newStringValue(item.name) + vm.iterStack[l].val = stringValueFromRaw(item.name) vm.iterStack[l].f = n vm.pc++ } else {