diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index 8a235b7dc9..b41de5c5e1 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -104,7 +104,7 @@ public override IEnumerable> GetOwnPro } } - internal override IEnumerable GetInitialOwnStringPropertyKeys() + internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() { if (_length != null) { diff --git a/Jint/Native/Json/JsonParser.cs b/Jint/Native/Json/JsonParser.cs index baf69adc95..f23e9aae49 100644 --- a/Jint/Native/Json/JsonParser.cs +++ b/Jint/Native/Json/JsonParser.cs @@ -174,8 +174,9 @@ private string ScanPunctuatorValue(int start, char code) private Token ScanNumericLiteral(ref State state) { var sb = state.TokenBuffer; - int start = _index; - char ch = _source.CharCodeAt(_index); + var start = _index; + var ch = _source.CharCodeAt(_index); + var canBeInteger = true; // Number start with a - if (ch == '-') @@ -186,7 +187,7 @@ private Token ScanNumericLiteral(ref State state) if (ch != '.') { - char firstCharacter = ch; + var firstCharacter = ch; sb.Append(ch); ch = _source.CharCodeAt(++_index); @@ -194,6 +195,7 @@ private Token ScanNumericLiteral(ref State state) // Octal number starts with '0'. if (sb.Length == 1 && firstCharacter == '0') { + canBeInteger = false; // decimal number starts with '0' such as '09' is illegal. if (ch > 0 && IsDecimalDigit(ch)) { @@ -210,6 +212,7 @@ private Token ScanNumericLiteral(ref State state) if (ch == '.') { + canBeInteger = false; sb.Append(ch); _index++; @@ -220,11 +223,12 @@ private Token ScanNumericLiteral(ref State state) } } - if (ch == 'e' || ch == 'E') + if (ch is 'e' or 'E') { + canBeInteger = false; sb.Append(ch); ch = _source.CharCodeAt(++_index); - if (ch == '+' || ch == '-') + if (ch is '+' or '-') { sb.Append(ch); ch = _source.CharCodeAt(++_index); @@ -243,9 +247,19 @@ private Token ScanNumericLiteral(ref State state) } } - string number = sb.ToString(); + var number = sb.ToString(); sb.Clear(); - JsNumber value = new JsNumber(double.Parse(number, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture)); + + JsNumber value; + if (canBeInteger && long.TryParse(number, out var longResult) && longResult != -0) + { + value = JsNumber.Create(longResult); + } + else + { + value = new JsNumber(double.Parse(number, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture)); + } + return CreateToken(Tokens.Number, number, '\0', value, new TextRange(start, _index)); } diff --git a/Jint/Native/Json/JsonSerializer.cs b/Jint/Native/Json/JsonSerializer.cs index 458755875f..7d3d2d5917 100644 --- a/Jint/Native/Json/JsonSerializer.cs +++ b/Jint/Native/Json/JsonSerializer.cs @@ -53,19 +53,16 @@ public JsValue Serialize(JsValue value, JsValue replacer, JsValue space) var wrapper = _engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); wrapper.DefineOwnProperty(JsString.Empty, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); - SerializeTarget target = new SerializeTarget(); - try - { - if (SerializeJSONProperty(JsString.Empty, wrapper, ref target) == SerializeResult.Undefined) - { - return JsValue.Undefined; - } - return new JsString(target.Json.ToString()); - } - finally + using var jsonBuilder = StringBuilderPool.Rent(); + using var numberBuilder = StringBuilderPool.Rent(); + + var target = new SerializerState(jsonBuilder.Builder, numberBuilder.Builder); + if (SerializeJSONProperty(JsString.Empty, wrapper, ref target) == SerializeResult.Undefined) { - target.Return(); + return JsValue.Undefined; } + + return new JsString(target.Json.ToString()); } private void SetupReplacer(JsValue replacer) @@ -157,21 +154,19 @@ private static string BuildSpacingGap(JsValue space) /// /// https://tc39.es/ecma262/#sec-serializejsonproperty /// - private SerializeResult SerializeJSONProperty(JsValue key, JsValue holder, ref SerializeTarget target) + private SerializeResult SerializeJSONProperty(JsValue key, JsValue holder, ref SerializerState target) { var value = ReadUnwrappedValue(key, holder); if (ReferenceEquals(value, JsValue.Null)) { - target.Json.Append(JsString.NullString.ToString()); + target.Json.Append(JsString.NullString); return SerializeResult.NotUndefined; } if (value.IsBoolean()) { - target.Json.Append(((JsBoolean) value)._value - ? JsString.TrueString.ToString() - : JsString.FalseString.ToString()); + target.Json.Append(((JsBoolean) value)._value ? "true" : "false"); return SerializeResult.NotUndefined; } @@ -183,13 +178,20 @@ private SerializeResult SerializeJSONProperty(JsValue key, JsValue holder, ref S if (value.IsNumber()) { - var doubleValue = TypeConverter.ToNumber(value); + var doubleValue = ((JsNumber) value)._value; + + if (value.IsInteger()) + { + target.Json.Append((long) doubleValue); + return SerializeResult.NotUndefined; + } + var isFinite = !double.IsNaN(doubleValue) && !double.IsInfinity(doubleValue); if (isFinite) { if (TypeConverter.CanBeStringifiedAsLong(doubleValue)) { - target.Json.Append(TypeConverter.ToString((long) doubleValue)); + target.Json.Append((long) doubleValue); return SerializeResult.NotUndefined; } @@ -234,6 +236,12 @@ private SerializeResult SerializeJSONProperty(JsValue key, JsValue holder, ref S private JsValue ReadUnwrappedValue(JsValue key, JsValue holder) { var value = holder.Get(key); + + if (value._type <= InternalTypes.Integer && _replacerFunction.IsUndefined()) + { + return value; + } + var isBigInt = value is BigIntInstance || value.IsBigInt(); if (value.IsObject() || isBigInt) { @@ -295,17 +303,16 @@ private static bool CanSerializesAsArray(ObjectInstance value) /// /// https://tc39.es/ecma262/#sec-quotejsonstring /// - private static void QuoteJSONString(string value, ref SerializeTarget target) + private static void QuoteJSONString(string value, ref SerializerState target) { - int len = value.Length; - if (len == 0) + if (value.Length == 0) { target.Json.Append("\"\""); return; } target.Json.Append('"'); - for (var i = 0; i < len; i++) + for (var i = 0; i < value.Length; i++) { var c = value[i]; switch (c) @@ -358,7 +365,7 @@ private static void QuoteJSONString(string value, ref SerializeTarget target) /// /// https://tc39.es/ecma262/#sec-serializejsonarray /// - private void SerializeJSONArray(ObjectInstance value, ref SerializeTarget target) + private void SerializeJSONArray(ObjectInstance value, ref SerializerState target) { var len = TypeConverter.ToUint32(value.Get(CommonProperties.Length)); if (len == 0) @@ -424,7 +431,7 @@ private void SerializeJSONArray(ObjectInstance value, ref SerializeTarget target /// /// https://tc39.es/ecma262/#sec-serializejsonobject /// - private void SerializeJSONObject(ObjectInstance value, ref SerializeTarget target) + private void SerializeJSONObject(ObjectInstance value, ref SerializerState target) { var enumeration = _propertyList is null ? PropertyEnumeration.FromObjectInstance(value) @@ -444,9 +451,10 @@ private void SerializeJSONObject(ObjectInstance value, ref SerializeTarget targe const string separator = ","; - bool hasPrevious = false; - foreach (var p in enumeration.Keys) + var hasPrevious = false; + for (var i = 0; i < enumeration.Keys.Count; i++) { + var p = enumeration.Keys[i]; int position = target.Json.Length; if (hasPrevious) @@ -500,30 +508,17 @@ private void SerializeJSONObject(ObjectInstance value, ref SerializeTarget targe _indent = stepback; } - private readonly ref struct SerializeTarget + private readonly ref struct SerializerState { - private readonly StringBuilderPool.BuilderWrapper _jsonBuilder; - private readonly StringBuilderPool.BuilderWrapper _numberBuilder; - - public SerializeTarget() + public SerializerState(StringBuilder jsonBuilder, StringBuilder numberBuffer) { - _jsonBuilder = StringBuilderPool.Rent(); - _numberBuilder = StringBuilderPool.Rent(); - Json = _jsonBuilder.Builder; - NumberBuffer = _numberBuilder.Builder; + Json = jsonBuilder; + NumberBuffer = numberBuffer; } - public StringBuilder Json { get; } - - public StringBuilder NumberBuffer { get; } - - public DtoaBuilder DtoaBuilder { get; } = TypeConverter.CreateDtoaBuilderForDouble(); - - public void Return() - { - _jsonBuilder.Dispose(); - _numberBuilder.Dispose(); - } + public readonly StringBuilder Json; + public readonly StringBuilder NumberBuffer; + public readonly DtoaBuilder DtoaBuilder = TypeConverter.CreateDtoaBuilderForDouble(); } private enum SerializeResult @@ -534,7 +529,7 @@ private enum SerializeResult private readonly struct PropertyEnumeration { - private PropertyEnumeration(IEnumerable keys, bool isEmpty) + private PropertyEnumeration(List keys, bool isEmpty) { Keys = keys; IsEmpty = isEmpty; @@ -545,26 +540,27 @@ public static PropertyEnumeration FromList(List keys) public static PropertyEnumeration FromObjectInstance(ObjectInstance instance) { - List allKeys = instance.GetOwnPropertyKeys(Types.String); + var allKeys = instance.GetOwnPropertyKeys(Types.String); RemoveUnserializableProperties(instance, allKeys); return new PropertyEnumeration(allKeys, allKeys.Count == 0); } private static void RemoveUnserializableProperties(ObjectInstance instance, List keys) { - keys.RemoveAll(key => + for (var i = 0; i < keys.Count; i++) { - if (!key.IsString()) + var key = keys[i]; + var desc = instance.GetOwnProperty(key); + if (desc == PropertyDescriptor.Undefined || !desc.Enumerable) { - return true; + keys.RemoveAt(i); + i--; } - var desc = instance.GetOwnProperty(key); - return desc == PropertyDescriptor.Undefined || !desc.Enumerable; - }); + } } - public IEnumerable Keys { get; } + public List Keys { get; } public bool IsEmpty { get; } } diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 27f7269d56..847042a419 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -225,16 +225,66 @@ public virtual List GetOwnPropertyKeys(Types types = Types.String | Typ { EnsureInitialized(); + var returningSymbols = (types & Types.Symbol) != 0 && _symbols?.Count > 0; + var returningStringKeys = (types & Types.String) != 0 && _properties?.Count > 0; + var propertyKeys = new List(); if ((types & Types.String) != 0) { - propertyKeys.AddRange(GetInitialOwnStringPropertyKeys()); + var initialOwnStringPropertyKeys = GetInitialOwnStringPropertyKeys(); + if (!ReferenceEquals(initialOwnStringPropertyKeys, System.Linq.Enumerable.Empty())) + { + propertyKeys.AddRange(initialOwnStringPropertyKeys); + } + } + + // check fast case where we don't need to sort, which should be the common case + if (!returningSymbols) + { + if (!returningStringKeys) + { + return propertyKeys; + } + + var propertyKeyCount = propertyKeys.Count; + propertyKeys.Capacity += _properties!.Count; + foreach (var pair in _properties) + { + // check if we can rely on the property name not being an unsigned number + var c = pair.Key.Name.Length > 0 ? pair.Key.Name[0] : 'a'; + if (char.IsDigit(c) && propertyKeyCount + _properties.Count > 1) + { + // jump to slow path, return list to original state + propertyKeys.RemoveRange(propertyKeyCount, propertyKeys.Count - propertyKeyCount); + return GetOwnPropertyKeysSorted(propertyKeys, returningStringKeys, returningSymbols); + } + propertyKeys.Add(new JsString(pair.Key.Name)); + } + + // seems good + return propertyKeys; + } + + if ((types & Types.String) == 0 && (types & Types.Symbol) != 0) + { + // only symbols requested + if (_symbols != null) + { + foreach (var pair in _symbols!) + { + propertyKeys.Add(pair.Key); + } + } + return propertyKeys; } - var keys = new List(_properties?.Count ?? 0 + _symbols?.Count ?? 0 + propertyKeys.Count); - List? symbolKeys = null; + return GetOwnPropertyKeysSorted(propertyKeys, returningStringKeys, returningSymbols); + } - if ((types & Types.String) != 0 && _properties != null) + private List GetOwnPropertyKeysSorted(List initialOwnPropertyKeys, bool returningStringKeys, bool returningSymbols) + { + var keys = new List(_properties?.Count ?? 0 + _symbols?.Count ?? 0 + initialOwnPropertyKeys.Count); + if (returningStringKeys && _properties != null) { foreach (var pair in _properties) { @@ -247,28 +297,22 @@ public virtual List GetOwnPropertyKeys(Types types = Types.String | Typ } else { - propertyKeys.Add(new JsString(propertyName)); + initialOwnPropertyKeys.Add(new JsString(propertyName)); } } } keys.Sort((v1, v2) => TypeConverter.ToNumber(v1).CompareTo(TypeConverter.ToNumber(v2))); - keys.AddRange(propertyKeys); + keys.AddRange(initialOwnPropertyKeys); - if ((types & Types.Symbol) != 0 && _symbols != null) + if (returningSymbols) { - foreach (var pair in _symbols) + foreach (var pair in _symbols!) { - symbolKeys ??= new List(); - symbolKeys.Add(pair.Key); + keys.Add(pair.Key); } } - if (symbolKeys != null) - { - keys.AddRange(symbolKeys); - } - return keys; } diff --git a/Jint/Native/String/StringInstance.cs b/Jint/Native/String/StringInstance.cs index 0bef246459..73b80d8556 100644 --- a/Jint/Native/String/StringInstance.cs +++ b/Jint/Native/String/StringInstance.cs @@ -33,7 +33,7 @@ private static bool IsInt32(double d, out int intValue) return false; } - public override PropertyDescriptor GetOwnProperty(JsValue property) + public sealed override PropertyDescriptor GetOwnProperty(JsValue property) { if (property == CommonProperties.Infinity) { @@ -66,7 +66,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) return new PropertyDescriptor(str[index], PropertyFlag.OnlyEnumerable); } - public override IEnumerable> GetOwnProperties() + public sealed override IEnumerable> GetOwnProperties() { foreach (var entry in base.GetOwnProperties()) { @@ -79,12 +79,12 @@ public override IEnumerable> GetOwnPro } } - internal override IEnumerable GetInitialOwnStringPropertyKeys() + internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() { - return new[] { JsString.LengthString }; + yield return JsString.LengthString; } - public override List GetOwnPropertyKeys(Types types) + public sealed override List GetOwnPropertyKeys(Types types) { var keys = new List(StringData.Length + 1); if ((types & Types.String) != 0) @@ -105,7 +105,7 @@ public override List GetOwnPropertyKeys(Types types) return keys; } - protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) + protected internal sealed override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { if (property == CommonProperties.Length) { @@ -117,7 +117,7 @@ protected internal override void SetOwnProperty(JsValue property, PropertyDescri } } - public override void RemoveOwnProperty(JsValue property) + public sealed override void RemoveOwnProperty(JsValue property) { if (property == CommonProperties.Length) { diff --git a/Jint/Pooling/StringBuilderPool.cs b/Jint/Pooling/StringBuilderPool.cs index ad1c9fe53f..c5184d7ac0 100644 --- a/Jint/Pooling/StringBuilderPool.cs +++ b/Jint/Pooling/StringBuilderPool.cs @@ -47,7 +47,7 @@ public void Dispose() var builder = Builder; // do not store builders that are too large. - if (builder.Capacity <= 1024) + if (builder.Capacity <= 1024 * 1024) { builder.Clear(); _pool.Free(builder); diff --git a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs index 4aa887f832..06cceee156 100644 --- a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs @@ -170,7 +170,7 @@ public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, objec } } - return new ReflectionDescriptor(engine, this, target, false); + return new ReflectionDescriptor(engine, this, target, enumerable: true); } } } diff --git a/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs b/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs index 5d8b4e1274..0e8e02ab9a 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs @@ -1,7 +1,6 @@ using Esprima.Ast; using Jint.Native; using Jint.Native.Function; -using Jint.Native.Object; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter.Expressions;