From 7fd48931316011f2e4ed7f4114467cb6b5067919 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Fri, 25 Aug 2023 08:00:08 +0300 Subject: [PATCH 01/44] Optimize engine construction (#1621) --- Jint.Benchmark/EngineConstructionBenchmark.cs | 22 +- Jint/Collections/HybridDictionary.cs | 6 + Jint/Collections/PropertyDictionary.cs | 4 + Jint/Collections/StringDictionarySlim.cs | 9 + Jint/Engine.cs | 27 ++- Jint/Native/Global/GlobalObject.Properties.cs | 188 ++++++++++++++++++ Jint/Native/Global/GlobalObject.cs | 109 +--------- Jint/Native/Object/ObjectInstance.cs | 2 + Jint/Options.cs | 13 +- .../Runtime/Descriptors/PropertyDescriptor.cs | 3 + .../Specialized/LazyPropertyDescriptor.cs | 2 + 11 files changed, 256 insertions(+), 129 deletions(-) create mode 100644 Jint/Native/Global/GlobalObject.Properties.cs diff --git a/Jint.Benchmark/EngineConstructionBenchmark.cs b/Jint.Benchmark/EngineConstructionBenchmark.cs index 56522b1307..dc1d79fd6b 100644 --- a/Jint.Benchmark/EngineConstructionBenchmark.cs +++ b/Jint.Benchmark/EngineConstructionBenchmark.cs @@ -1,24 +1,36 @@ using BenchmarkDotNet.Attributes; using Esprima; using Esprima.Ast; +using Jint.Native; namespace Jint.Benchmark; [MemoryDiagnoser] public class EngineConstructionBenchmark { - private readonly Script _program; + private Script _program; + private Script _simple; - public EngineConstructionBenchmark() + [GlobalSetup] + public void GlobalSetup() { var parser = new JavaScriptParser(); - _program = parser.ParseScript("return [].length + ''.length"); + _program = parser.ParseScript("([].length + ''.length)"); + _simple = parser.ParseScript("1"); + new Engine().Evaluate(_program); } [Benchmark] - public double BuildEngine() + public Engine BuildEngine() { var engine = new Engine(); - return engine.Evaluate(_program).AsNumber(); + return engine; + } + + [Benchmark] + public JsValue EvaluateSimple() + { + var engine = new Engine(); + return engine.Evaluate(_simple); } } diff --git a/Jint/Collections/HybridDictionary.cs b/Jint/Collections/HybridDictionary.cs index 618e6883b8..4b27fae70b 100644 --- a/Jint/Collections/HybridDictionary.cs +++ b/Jint/Collections/HybridDictionary.cs @@ -28,6 +28,12 @@ public HybridDictionary(int initialSize, bool checkExistingKeys) } } + protected HybridDictionary(StringDictionarySlim dictionary) + { + _checkExistingKeys = true; + _dictionary = dictionary; + } + public TValue this[Key key] { get diff --git a/Jint/Collections/PropertyDictionary.cs b/Jint/Collections/PropertyDictionary.cs index daea0f27fa..41a8c54397 100644 --- a/Jint/Collections/PropertyDictionary.cs +++ b/Jint/Collections/PropertyDictionary.cs @@ -11,5 +11,9 @@ public PropertyDictionary() public PropertyDictionary(int capacity, bool checkExistingKeys) : base(capacity, checkExistingKeys) { } + + public PropertyDictionary(StringDictionarySlim properties) : base(properties) + { + } } } diff --git a/Jint/Collections/StringDictionarySlim.cs b/Jint/Collections/StringDictionarySlim.cs index fab269533a..29b534e7d3 100644 --- a/Jint/Collections/StringDictionarySlim.cs +++ b/Jint/Collections/StringDictionarySlim.cs @@ -171,6 +171,15 @@ public ref TValue GetOrAddValueRef(Key key) return ref AddKey(key, bucketIndex); } + /// + /// Adds a new item and expects key to not to exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddDangerous(in Key key, TValue value) + { + AddKey(key, key.HashCode & (_buckets.Length - 1)) = value; + } + public ref TValue this[Key key] { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 7fa5e8317d..b974a902b0 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -26,16 +26,18 @@ namespace Jint /// public sealed partial class Engine : IDisposable { + private static readonly Options _defaultEngineOptions = new(); + private readonly ParserOptions _defaultParserOptions; private readonly JavaScriptParser _defaultParser; - internal readonly ExecutionContextStack _executionContexts; + private readonly ExecutionContextStack _executionContexts; private JsValue _completionValue = JsValue.Undefined; internal EvaluationContext? _activeEvaluationContext; private readonly EventLoop _eventLoop = new(); - private readonly Agent _agent = new Agent(); + private readonly Agent _agent = new(); // lazy properties private DebugHandler? _debugHandler; @@ -51,7 +53,7 @@ public sealed partial class Engine : IDisposable internal readonly JsValueArrayPool _jsValueArrayPool; internal readonly ExtensionMethodCache _extensionMethods; - public ITypeConverter ClrTypeConverter { get; internal set; } = null!; + public ITypeConverter ClrTypeConverter { get; internal set; } // cache of types used when resolving CLR type names internal readonly Dictionary TypeCache = new(); @@ -73,7 +75,7 @@ public sealed partial class Engine : IDisposable /// /// Constructs a new engine instance. /// - public Engine() : this((Action?) null) + public Engine() : this(null, null) { } @@ -81,14 +83,14 @@ public Engine() : this((Action?) null) /// Constructs a new engine instance and allows customizing options. /// public Engine(Action? options) - : this((engine, opts) => options?.Invoke(opts)) + : this(null, options != null ? (_, opts) => options.Invoke(opts) : null) { } /// /// Constructs a new engine with a custom instance. /// - public Engine(Options options) : this((e, o) => e.Options = options) + public Engine(Options options) : this(options, null) { } @@ -96,14 +98,21 @@ public Engine(Options options) : this((e, o) => e.Options = options) /// Constructs a new engine instance and allows customizing options. /// /// The provided engine instance in callback is not guaranteed to be fully configured - public Engine(Action options) + public Engine(Action options) : this(null, options) + { + } + + private Engine(Options? options, Action? configure) { Advanced = new AdvancedOperations(this); + ClrTypeConverter = new DefaultTypeConverter(this); _executionContexts = new ExecutionContextStack(2); - Options = new Options(); - options?.Invoke(this, Options); + // we can use default options if there's no action to modify it + Options = options ?? (configure is not null ? new Options() : _defaultEngineOptions); + + configure?.Invoke(this, Options); _extensionMethods = ExtensionMethodCache.Build(Options.Interop.ExtensionMethodTypes); diff --git a/Jint/Native/Global/GlobalObject.Properties.cs b/Jint/Native/Global/GlobalObject.Properties.cs new file mode 100644 index 0000000000..a8ae037981 --- /dev/null +++ b/Jint/Native/Global/GlobalObject.Properties.cs @@ -0,0 +1,188 @@ +using Jint.Collections; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Descriptors.Specialized; +using Jint.Runtime.Interop; + +namespace Jint.Native.Global; + +public partial class GlobalObject +{ + private static readonly Key propertyAggregateError = "AggregateError"; + private static readonly Key propertyArray = "Array"; + private static readonly Key propertyArrayBuffer = "ArrayBuffer"; + private static readonly Key propertyAtomics = "Atomics"; + private static readonly Key propertyBigInt = "BigInt"; + private static readonly Key propertyBigInt64Array = "BigInt64Array"; + private static readonly Key propertyBigUint64Array = "BigUint64Array"; + private static readonly Key propertyBoolean = "Boolean"; + private static readonly Key propertyDataView = "DataView"; + private static readonly Key propertyDate = "Date"; + private static readonly Key propertyError = "Error"; + private static readonly Key propertyEvalError = "EvalError"; + private static readonly Key propertyFinalizationRegistry = "FinalizationRegistry"; + private static readonly Key propertyFloat32Array = "Float32Array"; + private static readonly Key propertyFloat64Array = "Float64Array"; + private static readonly Key propertyFunction = "Function"; + private static readonly Key propertyInt16Array = "Int16Array"; + private static readonly Key propertyInt32Array = "Int32Array"; + private static readonly Key propertyInt8Array = "Int8Array"; + //private static readonly Key propertyIntl = "Intl"; + private static readonly Key propertyJSON = "JSON"; + private static readonly Key propertyMap = "Map"; + private static readonly Key propertyMath = "Math"; + private static readonly Key propertyNumber = "Number"; + private static readonly Key propertyObject = "Object"; + private static readonly Key propertyPromise = "Promise"; + private static readonly Key propertyProxy = "Proxy"; + private static readonly Key propertyRangeError = "RangeError"; + private static readonly Key propertyReferenceError = "ReferenceError"; + private static readonly Key propertyReflect = "Reflect"; + private static readonly Key propertyRegExp = "RegExp"; + private static readonly Key propertySet = "Set"; + private static readonly Key propertyShadowRealm = "ShadowRealm"; + private static readonly Key propertySharedArrayBuffer = "SharedArrayBuffer"; + private static readonly Key propertyString = "String"; + private static readonly Key propertySymbol = "Symbol"; + private static readonly Key propertySyntaxError = "SyntaxError"; + private static readonly Key propertyTypeError = "TypeError"; + private static readonly Key propertyTypedArray = "TypedArray"; + private static readonly Key propertyURIError = "URIError"; + private static readonly Key propertyUint16Array = "Uint16Array"; + private static readonly Key propertyUint32Array = "Uint32Array"; + private static readonly Key propertyUint8Array = "Uint8Array"; + private static readonly Key propertyUint8ClampedArray = "Uint8ClampedArray"; + private static readonly Key propertyWeakMap = "WeakMap"; + private static readonly Key propertyWeakRef = "WeakRef"; + private static readonly Key propertyWeakSet = "WeakSet"; + private static readonly Key propertyNaN = "NaN"; + private static readonly Key propertyInfinity = "Infinity"; + private static readonly Key propertyUndefined = "undefined"; + private static readonly Key propertyParseInt = "parseInt"; + private static readonly Key propertyParseFloat = "parseFloat"; + private static readonly Key propertyIsNaN = "isNaN"; + private static readonly Key propertyIsFinite = "isFinite"; + private static readonly Key propertyDecodeURI = "decodeURI"; + private static readonly Key propertyDecodeURIComponent = "decodeURIComponent"; + private static readonly Key propertyEncodeURI = "encodeURI"; + private static readonly Key propertyEncodeURIComponent = "encodeURIComponent"; + private static readonly Key propertyEscape = "escape"; + private static readonly Key propertyUnescape = "unescape"; + private static readonly Key propertyGlobalThis = "globalThis"; + private static readonly Key propertyEval = "eval"; + private static readonly Key propertyToString = "toString"; + + private static readonly PropertyDescriptor _propertyDescriptorNan = new(JsNumber.DoubleNaN, PropertyFlag.AllForbidden); + private static readonly PropertyDescriptor _propertyDescriptorPositiveInfinity = new(JsNumber.DoublePositiveInfinity, PropertyFlag.AllForbidden); + private static readonly PropertyDescriptor _propertyDescriptorUndefined = new(Undefined, PropertyFlag.AllForbidden); + + protected override void Initialize() + { + const PropertyFlag LengthFlags = PropertyFlag.Configurable; + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + + var properties = new StringDictionarySlim(64); + properties.AddDangerous(propertyAggregateError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.AggregateError, PropertyFlags)); + properties.AddDangerous(propertyArray, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Array, PropertyFlags)); + properties.AddDangerous(propertyArrayBuffer, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ArrayBuffer, PropertyFlags)); + properties.AddDangerous(propertyAtomics, new LazyPropertyDescriptor(this, static state => Undefined, PropertyFlags)); + properties.AddDangerous(propertyBigInt, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt, PropertyFlags)); + properties.AddDangerous(propertyBigInt64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt64Array, PropertyFlags)); + properties.AddDangerous(propertyBigUint64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigUint64Array, PropertyFlags)); + properties.AddDangerous(propertyBoolean, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Boolean, PropertyFlags)); + properties.AddDangerous(propertyDataView, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.DataView, PropertyFlags)); + properties.AddDangerous(propertyDate, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Date, PropertyFlags)); + properties.AddDangerous(propertyError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Error, PropertyFlags)); + properties.AddDangerous(propertyEvalError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.EvalError, PropertyFlags)); + properties.AddDangerous(propertyFinalizationRegistry, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.FinalizationRegistry, PropertyFlags)); + properties.AddDangerous(propertyFloat32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, PropertyFlags)); + properties.AddDangerous(propertyFloat64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, PropertyFlags)); + properties.AddDangerous(propertyFunction, new PropertyDescriptor(_realm.Intrinsics.Function, PropertyFlags)); + properties.AddDangerous(propertyInt16Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int16Array, PropertyFlags)); + properties.AddDangerous(propertyInt32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int32Array, PropertyFlags)); + properties.AddDangerous(propertyInt8Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int8Array, PropertyFlags)); + // TODO properties.AddDapropertygerous(propertyIntl, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Intl, propertyFlags)); + properties.AddDangerous(propertyJSON, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Json, PropertyFlags)); + properties.AddDangerous(propertyMap, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Map, PropertyFlags)); + properties.AddDangerous(propertyMath, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Math, PropertyFlags)); + properties.AddDangerous(propertyNumber, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Number, PropertyFlags)); + properties.AddDangerous(propertyObject, new PropertyDescriptor(_realm.Intrinsics.Object, PropertyFlags)); + properties.AddDangerous(propertyPromise, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Promise, PropertyFlags)); + properties.AddDangerous(propertyProxy, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Proxy, PropertyFlags)); + properties.AddDangerous(propertyRangeError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RangeError, PropertyFlags)); + properties.AddDangerous(propertyReferenceError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ReferenceError, PropertyFlags)); + properties.AddDangerous(propertyReflect, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Reflect, PropertyFlags)); + properties.AddDangerous(propertyRegExp, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RegExp, PropertyFlags)); + properties.AddDangerous(propertySet, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Set, PropertyFlags)); + properties.AddDangerous(propertyShadowRealm, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ShadowRealm, PropertyFlags)); + properties.AddDangerous(propertySharedArrayBuffer, new LazyPropertyDescriptor(this, static state => Undefined, PropertyFlags)); + properties.AddDangerous(propertyString, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.String, PropertyFlags)); + properties.AddDangerous(propertySymbol, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Symbol, PropertyFlags)); + properties.AddDangerous(propertySyntaxError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.SyntaxError, PropertyFlags)); + properties.AddDangerous(propertyTypeError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypeError, PropertyFlags)); + properties.AddDangerous(propertyTypedArray, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypedArray, PropertyFlags)); + properties.AddDangerous(propertyURIError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.UriError, PropertyFlags)); + properties.AddDangerous(propertyUint16Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint16Array, PropertyFlags)); + properties.AddDangerous(propertyUint32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint32Array, PropertyFlags)); + properties.AddDangerous(propertyUint8Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8Array, PropertyFlags)); + properties.AddDangerous(propertyUint8ClampedArray, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8ClampedArray, PropertyFlags)); + properties.AddDangerous(propertyWeakMap, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakMap, PropertyFlags)); + properties.AddDangerous(propertyWeakRef, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakRef, PropertyFlags)); + properties.AddDangerous(propertyWeakSet, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakSet, PropertyFlags)); + + properties.AddDangerous(propertyNaN, _propertyDescriptorNan); + properties.AddDangerous(propertyInfinity, _propertyDescriptorPositiveInfinity); + properties.AddDangerous(propertyUndefined, _propertyDescriptorUndefined); + properties.AddDangerous(propertyParseInt, new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseInt", ParseInt, 2, LengthFlags), PropertyFlags)); + properties.AddDangerous(propertyParseFloat, new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseFloat", ParseFloat, 1, LengthFlags), PropertyFlags)); + properties.AddDangerous(propertyIsNaN, new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isNaN", IsNaN, 1, LengthFlags), PropertyFlags)); + properties.AddDangerous(propertyIsFinite, new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isFinite", IsFinite, 1, LengthFlags), PropertyFlags)); + + properties.AddDangerous(propertyDecodeURI, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "decodeURI", global.DecodeUri, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyDecodeURIComponent, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "decodeURIComponent", global.DecodeUriComponent, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyEncodeURI, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "encodeURI", global.EncodeUri, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyEncodeURIComponent, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "encodeURIComponent", global.EncodeUriComponent, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyEscape, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "escape", global.Escape, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyUnescape, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "unescape", global.Unescape, 1, LengthFlags); + }, PropertyFlags)); + + properties.AddDangerous(propertyGlobalThis, new PropertyDescriptor(this, PropertyFlags)); + properties.AddDangerous(propertyEval, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Eval, PropertyFlag.Configurable | PropertyFlag.Writable)); + + // toString is not mentioned or actually required in spec, but some tests rely on it + properties.AddDangerous(propertyToString, new LazyPropertyDescriptor(this, static state => + { + var global = (GlobalObject) state!; + return new ClrFunctionInstance(global._engine, "toString", global.ToStringString, 1); + }, PropertyFlags)); + + SetProperties(properties); + } +} diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index 5390fc0e05..4496af979b 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -13,7 +13,7 @@ namespace Jint.Native.Global { - public sealed class GlobalObject : ObjectInstance + public sealed partial class GlobalObject : ObjectInstance { private readonly Realm _realm; private readonly StringBuilder _stringBuilder = new(); @@ -25,113 +25,6 @@ internal GlobalObject( _realm = realm; } - protected override void Initialize() - { - const PropertyFlag lengthFlags = PropertyFlag.Configurable; - const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; - - var properties = new PropertyDictionary(56, checkExistingKeys: false) - { - ["AggregateError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.AggregateError, propertyFlags), - ["Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Array, propertyFlags), - ["ArrayBuffer"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ArrayBuffer, propertyFlags), - ["Atomics"] = new LazyPropertyDescriptor(this, static state => Undefined, propertyFlags), - ["BigInt"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt, propertyFlags), - ["BigInt64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt64Array, propertyFlags), - ["BigUint64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigUint64Array, propertyFlags), - ["Boolean"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Boolean, propertyFlags), - ["DataView"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.DataView, propertyFlags), - ["Date"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Date, propertyFlags), - ["Error"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Error, propertyFlags), - ["EvalError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.EvalError, propertyFlags), - ["FinalizationRegistry"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.FinalizationRegistry, propertyFlags), - ["Float32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, propertyFlags), - ["Float64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, propertyFlags), - ["Function"] = new PropertyDescriptor(_realm.Intrinsics.Function, propertyFlags), - ["Int16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int16Array, propertyFlags), - ["Int32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int32Array, propertyFlags), - ["Int8Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int8Array, propertyFlags), - // TODO ["Intl"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Intl, propertyFlags), - ["JSON"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Json, propertyFlags), - ["Map"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Map, propertyFlags), - ["Math"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Math, propertyFlags), - ["Number"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Number, propertyFlags), - ["Object"] = new PropertyDescriptor(_realm.Intrinsics.Object, propertyFlags), - ["Promise"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Promise, propertyFlags), - ["Proxy"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Proxy, propertyFlags), - ["RangeError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RangeError, propertyFlags), - ["ReferenceError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ReferenceError, propertyFlags), - ["Reflect"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Reflect, propertyFlags), - ["RegExp"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RegExp, propertyFlags), - ["Set"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Set, propertyFlags), - ["ShadowRealm"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ShadowRealm, propertyFlags), - ["SharedArrayBuffer"] = new LazyPropertyDescriptor(this, static state => Undefined, propertyFlags), - ["String"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.String, propertyFlags), - ["Symbol"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Symbol, propertyFlags), - ["SyntaxError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.SyntaxError, propertyFlags), - ["TypeError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypeError, propertyFlags), - ["TypedArray"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypedArray, propertyFlags), - ["URIError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.UriError, propertyFlags), - ["Uint16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint16Array, propertyFlags), - ["Uint32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint32Array, propertyFlags), - ["Uint8Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8Array, propertyFlags), - ["Uint8ClampedArray"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8ClampedArray, propertyFlags), - ["WeakMap"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakMap, propertyFlags), - ["WeakRef"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakRef, propertyFlags), - ["WeakSet"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakSet, propertyFlags), - - - ["NaN"] = new PropertyDescriptor(double.NaN, PropertyFlag.AllForbidden), - ["Infinity"] = new PropertyDescriptor(double.PositiveInfinity, PropertyFlag.AllForbidden), - ["undefined"] = new PropertyDescriptor(Undefined, PropertyFlag.AllForbidden), - ["parseInt"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseInt", ParseInt, 2, lengthFlags), propertyFlags), - ["parseFloat"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseFloat", ParseFloat, 1, lengthFlags), propertyFlags), - ["isNaN"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isNaN", IsNaN, 1, lengthFlags), propertyFlags), - ["isFinite"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isFinite", IsFinite, 1, lengthFlags), propertyFlags), - ["decodeURI"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "decodeURI", global.DecodeUri, 1, lengthFlags); - }, propertyFlags), - ["decodeURIComponent"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "decodeURIComponent", global.DecodeUriComponent, 1, lengthFlags); - }, propertyFlags), - ["encodeURI"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "encodeURI", global.EncodeUri, 1, lengthFlags); - }, propertyFlags), - ["encodeURIComponent"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "encodeURIComponent", global.EncodeUriComponent, 1, lengthFlags); - }, propertyFlags), - ["escape"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "escape", global.Escape, 1, lengthFlags); - }, propertyFlags), - ["unescape"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "unescape", global.Unescape, 1, lengthFlags); - }, propertyFlags), - ["globalThis"] = new PropertyDescriptor(this, propertyFlags), - ["eval"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Eval, PropertyFlag.Configurable | PropertyFlag.Writable), - - // toString is not mentioned or actually required in spec, but some tests rely on it - ["toString"] = new LazyPropertyDescriptor(this, static state => - { - var global = (GlobalObject) state!; - return new ClrFunctionInstance(global._engine, "toString", global.ToStringString, 1); - }, propertyFlags) - }; - - SetProperties(properties); - } - private JsValue ToStringString(JsValue thisObject, JsValue[] arguments) { return _realm.Intrinsics.Object.PrototypeObject.ToObjectString(thisObject, Arguments.Empty); diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 4bdb5fba63..ec09c45cca 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -130,6 +130,8 @@ internal static IConstructor SpeciesConstructor(ObjectInstance o, IConstructor d return null; } + internal void SetProperties(StringDictionarySlim properties) => SetProperties(new PropertyDictionary(properties)); + internal void SetProperties(PropertyDictionary? properties) { if (properties != null) diff --git a/Jint/Options.cs b/Jint/Options.cs index 4224613a3a..56f7b4d236 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -21,8 +21,10 @@ namespace Jint public class Options { - private ITimeSystem? _timeSystem; + private static readonly CultureInfo _defaultCulture = CultureInfo.CurrentCulture; + private static readonly TimeZoneInfo _defaultTimeZone = TimeZoneInfo.Local; + private ITimeSystem? _timeSystem; internal List> _configurations { get; } = new(); /// @@ -58,7 +60,7 @@ public class Options /// /// The culture the engine runs on, defaults to current culture. /// - public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture; + public CultureInfo Culture { get; set; } = _defaultCulture; /// @@ -73,7 +75,7 @@ public ITimeSystem TimeSystem /// /// The time zone the engine runs on, defaults to local. Same as setting DefaultTimeSystem with the time zone. /// - public TimeZoneInfo TimeZone { get; set; } = TimeZoneInfo.Local; + public TimeZoneInfo TimeZone { get; set; } = _defaultTimeZone; /// /// Reference resolver allows customizing behavior for reference resolving. This can be useful in cases where @@ -106,7 +108,7 @@ internal void Apply(Engine engine) { foreach (var configuration in _configurations) { - configuration?.Invoke(engine); + configuration(engine); } // add missing bits if needed @@ -145,9 +147,6 @@ internal void Apply(Engine engine) } engine.ModuleLoader = Modules.ModuleLoader; - - // ensure defaults - engine.ClrTypeConverter ??= new DefaultTypeConverter(engine); } private static void AttachExtensionMethodsToPrototypes(Engine engine) diff --git a/Jint/Runtime/Descriptors/PropertyDescriptor.cs b/Jint/Runtime/Descriptors/PropertyDescriptor.cs index 9f32fa7030..f9dcf6fe71 100644 --- a/Jint/Runtime/Descriptors/PropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/PropertyDescriptor.cs @@ -18,11 +18,13 @@ public PropertyDescriptor() : this(PropertyFlag.None) { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected PropertyDescriptor(PropertyFlag flags) { _flags = flags; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal PropertyDescriptor(JsValue? value, PropertyFlag flags) : this(flags) { if ((_flags & PropertyFlag.CustomJsValue) != 0) @@ -32,6 +34,7 @@ protected internal PropertyDescriptor(JsValue? value, PropertyFlag flags) : this _value = value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public PropertyDescriptor(JsValue? value, bool? writable, bool? enumerable, bool? configurable) { if ((_flags & PropertyFlag.CustomJsValue) != 0) diff --git a/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs index 7c2eaec1df..2d13df852f 100644 --- a/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Jint.Native; namespace Jint.Runtime.Descriptors.Specialized @@ -7,6 +8,7 @@ internal sealed class LazyPropertyDescriptor : PropertyDescriptor private readonly object? _state; private readonly Func _resolver; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal LazyPropertyDescriptor(object? state, Func resolver, PropertyFlag flags) : base(null, flags | PropertyFlag.CustomJsValue) { From 553fdb86ecd628f74718fcec37ec26147647ba8e Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Tue, 29 Aug 2023 19:41:59 +0300 Subject: [PATCH 02/44] Improve array access performance (#1625) * always have JsValue in array and use sparse as helper to remove type checks * faster paths for HasProperty against arrays * remove JsArray constructor taking PropertyDescriptor[] --- .../RavenApiUsageTests.cs | 28 - Jint/Native/Array/ArrayInstance.cs | 562 +++++++++--------- Jint/Native/Array/ArrayOperations.cs | 23 +- Jint/Native/Array/ArrayPrototype.cs | 4 +- Jint/Native/JsArray.cs | 13 - Jint/Native/Object/ObjectInstance.cs | 5 +- Jint/Runtime/TypeConverter.cs | 25 +- 7 files changed, 303 insertions(+), 357 deletions(-) diff --git a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs index 5143642fa3..9ceb48f621 100644 --- a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs +++ b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs @@ -3,7 +3,6 @@ using Jint.Native; using Jint.Native.Function; using Jint.Runtime; -using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Tests.PublicInterface; @@ -57,20 +56,6 @@ public void CanChangeMaxStatementValue() Assert.Equal(321, constraint.MaxStatements); } - [Fact] - public void CanConstructArrayInstanceFromDescriptorArray() - { - var descriptors = new[] - { - new PropertyDescriptor(42, writable: false, enumerable: false, configurable: false), - }; - - var engine = new Engine(); - var array = new JsArray(engine, descriptors); - Assert.Equal(1L, array.Length); - Assert.Equal(42, array[0]); - } - [Fact] public void CanGetPropertyDescriptor() { @@ -97,15 +82,6 @@ public void CanInjectConstructedObjects() TestArrayAccess(engine, array1, "array1"); - var array3 = new JsArray(engine, new[] - { - new PropertyDescriptor(JsNumber.Create(1), true, true, true), - new PropertyDescriptor(JsNumber.Create(2), true, true, true), - new PropertyDescriptor(JsNumber.Create(3), true, true, true), - }); - engine.SetValue("array3", array3); - TestArrayAccess(engine, array3, "array3"); - engine.SetValue("obj", obj); Assert.Equal("test", engine.Evaluate("obj.name")); @@ -117,10 +93,6 @@ public void CanInjectConstructedObjects() Assert.Equal(0, engine.Evaluate("emptyArray.length")); Assert.Equal(1, engine.Evaluate("emptyArray.push(1); return emptyArray.length")); - engine.SetValue("emptyArray", new JsArray(engine, Array.Empty())); - Assert.Equal(0, engine.Evaluate("emptyArray.length")); - Assert.Equal(1, engine.Evaluate("emptyArray.push(1); return emptyArray.length")); - engine.SetValue("date", new JsDate(engine, new DateTime(2022, 10, 20))); Assert.Equal(2022, engine.Evaluate("date.getFullYear()")); } diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index f81b528179..1a75e47145 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -15,34 +15,30 @@ public class ArrayInstance : ObjectInstance, IEnumerable private const int MaxDenseArrayLength = 10_000_000; // we have dense and sparse, we usually can start with dense and fall back to sparse when necessary - // entries are lazy and can be either of type PropertyDescriptor or plain JsValue while there is no need for extra info - internal object?[]? _dense; - private Dictionary? _sparse; + // when we have plain JsValues, _denseValues is used - if any operation occurs which requires setting more property flags + // we convert to _sparse and _denseValues is set to null - it will be a slow array + internal JsValue?[]? _dense; + + private Dictionary? _sparse; private ObjectChangeFlags _objectChangeFlags; - private bool _isObjectArray = true; private protected ArrayInstance(Engine engine, InternalTypes type) : base(engine, type: type) { - _dense = System.Array.Empty(); + _dense = System.Array.Empty(); } private protected ArrayInstance(Engine engine, uint capacity = 0, uint length = 0) : base(engine, type: InternalTypes.Object | InternalTypes.Array) { - _prototype = engine.Realm.Intrinsics.Array.PrototypeObject; - - if (capacity > engine.Options.Constraints.MaxArraySize) - { - ThrowMaximumArraySizeReachedException(engine, capacity); - } + InitializePrototypeAndValidateCapacity(engine, capacity); if (capacity < MaxDenseArrayLength) { - _dense = capacity > 0 ? new object?[capacity] : System.Array.Empty(); + _dense = capacity > 0 ? new JsValue?[capacity] : System.Array.Empty(); } else { - _sparse = new Dictionary(1024); + _sparse = new Dictionary(1024); } _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable); @@ -50,42 +46,20 @@ private protected ArrayInstance(Engine engine, uint capacity = 0, uint length = private protected ArrayInstance(Engine engine, JsValue[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) { - Initialize(engine, items); - } - - private protected ArrayInstance(Engine engine, PropertyDescriptor[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) - { - Initialize(engine, items); - } + InitializePrototypeAndValidateCapacity(engine, capacity: 0); - private protected ArrayInstance(Engine engine, object[] items) : base(engine, type: InternalTypes.Object | InternalTypes.Array) - { - Initialize(engine, items); + _dense = items; + _length = new PropertyDescriptor(items.Length, PropertyFlag.OnlyWritable); } - private void Initialize(Engine engine, T[] items) where T : class + private void InitializePrototypeAndValidateCapacity(Engine engine, uint capacity) { - if (items.Length > engine.Options.Constraints.MaxArraySize) - { - ThrowMaximumArraySizeReachedException(engine, (uint) items.Length); - } - _prototype = engine.Realm.Intrinsics.Array.PrototypeObject; - _isObjectArray = typeof(T) == typeof(object); - int length; - if (items == null || items.Length == 0) + if (capacity > 0 && capacity > engine.Options.Constraints.MaxArraySize) { - _dense = System.Array.Empty(); - length = 0; - } - else - { - _dense = items; - length = items.Length; + ThrowMaximumArraySizeReachedException(engine, capacity); } - - _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable); } public sealed override bool IsArrayLike => true; @@ -133,149 +107,155 @@ internal bool CanUseFastAccess public sealed override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc) { + if (property == CommonProperties.Length) + { + return DefineLength(desc); + } + var isArrayIndex = IsArrayIndex(property, out var index); TrackChanges(property, desc, isArrayIndex); if (isArrayIndex) { + ConvertToSparse(); return DefineOwnProperty(index, desc); } - if (property == CommonProperties.Length) + return base.DefineOwnProperty(property, desc); + } + + private bool DefineLength(PropertyDescriptor desc) + { + var value = desc.Value; + if (ReferenceEquals(value, null)) { - var value = desc.Value; - if (ReferenceEquals(value, null)) - { - return base.DefineOwnProperty(CommonProperties.Length, desc); - } + return base.DefineOwnProperty(CommonProperties.Length, desc); + } - var newLenDesc = new PropertyDescriptor(desc); - uint newLen = TypeConverter.ToUint32(value); - if (newLen != TypeConverter.ToNumber(value)) - { - ExceptionHelper.ThrowRangeError(_engine.Realm); - } + var newLenDesc = new PropertyDescriptor(desc); + uint newLen = TypeConverter.ToUint32(value); + if (newLen != TypeConverter.ToNumber(value)) + { + ExceptionHelper.ThrowRangeError(_engine.Realm); + } - var oldLenDesc = _length; - var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc!.Value); + var oldLenDesc = _length; + var oldLen = (uint) TypeConverter.ToNumber(oldLenDesc!.Value); - newLenDesc.Value = newLen; - if (newLen >= oldLen) - { - return base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - } + newLenDesc.Value = newLen; + if (newLen >= oldLen) + { + return base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + } - if (!oldLenDesc.Writable) - { - return false; - } + if (!oldLenDesc.Writable) + { + return false; + } - bool newWritable; - if (!newLenDesc.WritableSet || newLenDesc.Writable) - { - newWritable = true; - } - else - { - newWritable = false; - newLenDesc.Writable = true; - } + bool newWritable; + if (!newLenDesc.WritableSet || newLenDesc.Writable) + { + newWritable = true; + } + else + { + newWritable = false; + newLenDesc.Writable = true; + } - var succeeded = base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - if (!succeeded) - { - return false; - } + var succeeded = base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + if (!succeeded) + { + return false; + } - var count = _dense?.Length ?? _sparse!.Count; - if (count < oldLen - newLen) + var count = _dense?.Length ?? _sparse!.Count; + if (count < oldLen - newLen) + { + if (_dense != null) { - if (_dense != null) + for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex) { - for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex) + if (_dense[keyIndex] == null) { - if (_dense[keyIndex] == null) - { - continue; - } + continue; + } - // is it the index of the array - if (keyIndex >= newLen && keyIndex < oldLen) + // is it the index of the array + if (keyIndex >= newLen && keyIndex < oldLen) + { + var deleteSucceeded = Delete(keyIndex); + if (!deleteSucceeded) { - var deleteSucceeded = Delete(keyIndex); - if (!deleteSucceeded) + newLenDesc.Value = keyIndex + 1; + if (!newWritable) { - newLenDesc.Value = keyIndex + 1; - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } + + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; } } } - else + } + else + { + // in the case of sparse arrays, treat each concrete element instead of + // iterating over all indexes + var keys = new List(_sparse!.Keys); + var keysCount = keys.Count; + for (var i = 0; i < keysCount; i++) { - // in the case of sparse arrays, treat each concrete element instead of - // iterating over all indexes - var keys = new List(_sparse!.Keys); - var keysCount = keys.Count; - for (var i = 0; i < keysCount; i++) - { - var keyIndex = keys[i]; + var keyIndex = keys[i]; - // is it the index of the array - if (keyIndex >= newLen && keyIndex < oldLen) + // is it the index of the array + if (keyIndex >= newLen && keyIndex < oldLen) + { + var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex)); + if (!deleteSucceeded) { - var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex)); - if (!deleteSucceeded) + newLenDesc.Value = JsNumber.Create(keyIndex + 1); + if (!newWritable) { - newLenDesc.Value = JsNumber.Create(keyIndex + 1); - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } + + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; } } } } - else + } + else + { + while (newLen < oldLen) { - while (newLen < oldLen) + // algorithm as per the spec + oldLen--; + var deleteSucceeded = Delete(oldLen); + if (!deleteSucceeded) { - // algorithm as per the spec - oldLen--; - var deleteSucceeded = Delete(oldLen); - if (!deleteSucceeded) + newLenDesc.Value = oldLen + 1; + if (!newWritable) { - newLenDesc.Value = oldLen + 1; - if (!newWritable) - { - newLenDesc.Writable = false; - } - - base.DefineOwnProperty(CommonProperties.Length, newLenDesc); - return false; + newLenDesc.Writable = false; } - } - } - if (!newWritable) - { - base.DefineOwnProperty(CommonProperties.Length, new PropertyDescriptor(value: null, PropertyFlag.WritableSet)); + base.DefineOwnProperty(CommonProperties.Length, newLenDesc); + return false; + } } + } - return true; + if (!newWritable) + { + base.DefineOwnProperty(CommonProperties.Length, new PropertyDescriptor(value: null, PropertyFlag.WritableSet)); } - return base.DefineOwnProperty(property, desc); + return true; } private bool DefineOwnProperty(uint index, PropertyDescriptor desc) @@ -397,39 +377,28 @@ public sealed override IEnumerable> Ge if (temp != null) { var length = System.Math.Min(temp.Length, GetLength()); - for (var i = 0; i < length; i++) + for (uint i = 0; i < length; i++) { var value = temp[i]; if (value != null) { - if (value is not PropertyDescriptor descriptor) + if (_sparse is null || !_sparse.TryGetValue(i, out var descriptor) || descriptor is null) { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) - { - temp = _dense!; - } - temp[i] = descriptor = new PropertyDescriptor((JsValue) value, PropertyFlag.ConfigurableEnumerableWritable); + _sparse ??= new Dictionary(); + _sparse[i] = descriptor = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } yield return new KeyValuePair(TypeConverter.ToString(i), descriptor); } } } - else + else if (_sparse != null) { - foreach (var entry in _sparse!) + foreach (var entry in _sparse) { var value = entry.Value; if (value is not null) { - if (value is not PropertyDescriptor descriptor) - { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) - { - temp = _dense!; - } - _sparse[entry.Key] = descriptor = new PropertyDescriptor((JsValue) value, PropertyFlag.ConfigurableEnumerableWritable); - } - yield return new KeyValuePair(TypeConverter.ToString(entry.Key), descriptor); + yield return new KeyValuePair(TypeConverter.ToString(entry.Key), value); } } } @@ -454,7 +423,7 @@ public sealed override PropertyDescriptor GetOwnProperty(JsValue property) if (IsArrayIndex(property, out var index)) { - if (TryGetDescriptor(index, out var result)) + if (TryGetDescriptor(index, createIfMissing: true, out var result)) { return result; } @@ -497,33 +466,23 @@ public sealed override JsValue Get(JsValue property, JsValue receiver) public sealed override bool Set(JsValue property, JsValue value, JsValue receiver) { var isSafeSelfTarget = IsSafeSelfTarget(receiver); - if (isSafeSelfTarget && IsArrayIndex(property, out var index)) + if (isSafeSelfTarget && CanUseFastAccess) { - var temp = _dense; - if (temp is not null && CanUseFastAccess) + if (IsArrayIndex(property, out var index)) { - var current = index < temp.Length ? temp[index] : null; - if (current is not PropertyDescriptor p || p.IsDefaultArrayValueDescriptor()) - { - SetIndexValue(index, value, true); - return true; - } + SetIndexValue(index, value, updateLength: true); + return true; } - // slower and more allocating - if (TryGetDescriptor(index, out var descriptor)) + if (property == CommonProperties.Length + && _length is { Writable: true } + && value is JsNumber jsNumber + && jsNumber.IsInteger() + && jsNumber._value <= MaxDenseArrayLength + && jsNumber._value >= GetLength()) { - if (descriptor.IsDefaultArrayValueDescriptor()) - { - // fast path with direct write without allocations - descriptor.Value = value; - return true; - } - } - else if (CanUseFastAccess) - { - // we know it's to be written to own array backing field as new value - SetIndexValue(index, value, true); + // we don't need explicit resize + _length.Value = jsNumber; return true; } } @@ -544,6 +503,27 @@ public sealed override bool HasProperty(JsValue property) return base.HasProperty(property); } + internal bool HasProperty(ulong index) + { + if (index < uint.MaxValue) + { + var temp = _dense; + if (temp != null) + { + if (index < (uint) temp.Length && temp[index] is not null) + { + return true; + } + } + else if (_sparse!.ContainsKey((uint) index)) + { + return true; + } + } + + return base.HasProperty(index); + } + protected internal sealed override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { var isArrayIndex = IsArrayIndex(property, out var index); @@ -568,11 +548,15 @@ private void TrackChanges(JsValue property, PropertyDescriptor desc, bool isArra if (isArrayIndex) { - if (!desc.IsDefaultArrayValueDescriptor()) + if (!desc.IsDefaultArrayValueDescriptor() && desc.Flags != PropertyFlag.None) { _objectChangeFlags |= ObjectChangeFlags.NonDefaultDataDescriptorUsage; } - _objectChangeFlags |= ObjectChangeFlags.ArrayIndex; + + if (GetType() != typeof(JsArray)) + { + _objectChangeFlags |= ObjectChangeFlags.ArrayIndex; + } } else { @@ -712,8 +696,7 @@ internal bool Delete(uint index) { if (index < (uint) temp.Length) { - var value = temp[index]; - if (value is JsValue || value is PropertyDescriptor { Configurable: true }) + if (!TryGetDescriptor(index, createIfMissing: false, out var descriptor) || descriptor.Configurable) { temp[index] = null; return true; @@ -721,7 +704,7 @@ internal bool Delete(uint index) } } - if (!TryGetDescriptor(index, out var desc)) + if (!TryGetDescriptor(index, createIfMissing: false, out var desc)) { return true; } @@ -754,7 +737,7 @@ internal bool DeleteAt(uint index) return false; } - private bool TryGetDescriptor(uint index, [NotNullWhen(true)] out PropertyDescriptor? descriptor) + private bool TryGetDescriptor(uint index, bool createIfMissing, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { descriptor = null; var temp = _dense; @@ -763,37 +746,30 @@ private bool TryGetDescriptor(uint index, [NotNullWhen(true)] out PropertyDescri if (index < (uint) temp.Length) { var value = temp[index]; - if (value is JsValue jsValue) + if (value != null) { - if (EnsureCompatibleDense(typeof(PropertyDescriptor))) + if (_sparse is null || !_sparse.TryGetValue(index, out descriptor) || descriptor is null) { - temp = _dense!; + if (!createIfMissing) + { + return false; + } + _sparse ??= new Dictionary(); + _sparse[index] = descriptor = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } - temp[index] = descriptor = new PropertyDescriptor(jsValue, PropertyFlag.ConfigurableEnumerableWritable); - } - else if (value is PropertyDescriptor propertyDescriptor) - { - descriptor = propertyDescriptor; - } - } - return descriptor != null; - } - if (_sparse!.TryGetValue(index, out var sparseValue)) - { - if (sparseValue is JsValue jsValue) - { - _sparse[index] = descriptor = new PropertyDescriptor(jsValue, PropertyFlag.ConfigurableEnumerableWritable); - } - else if (sparseValue is PropertyDescriptor propertyDescriptor) - { - descriptor = propertyDescriptor; + descriptor.Value = value; + return true; + } } + return false; } + _sparse?.TryGetValue(index, out descriptor); return descriptor is not null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryGetValue(uint index, out JsValue value) { value = GetValue(index, unwrapFromNonDataDescriptor: true)!; @@ -803,6 +779,12 @@ internal bool TryGetValue(uint index, out JsValue value) return true; } + return TryGetValueUnlikely(index, out value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private bool TryGetValueUnlikely(uint index, out JsValue value) + { if (!CanUseFastAccess) { // slow path must be checked for prototype @@ -825,54 +807,75 @@ internal bool TryGetValue(uint index, out JsValue value) return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private JsValue? GetValue(uint index, bool unwrapFromNonDataDescriptor) { - object? value = null; var temp = _dense; if (temp != null) { if (index < (uint) temp.Length) { - value = temp[index]; + return temp[index]; } - } - else - { - _sparse!.TryGetValue(index, out value); + return null; } - if (value is JsValue jsValue) - { - return jsValue; - } + return GetValueUnlikely(index, unwrapFromNonDataDescriptor); + } - if (value is PropertyDescriptor propertyDescriptor) + [MethodImpl(MethodImplOptions.NoInlining)] + private JsValue? GetValueUnlikely(uint index, bool unwrapFromNonDataDescriptor) + { + JsValue? value = null; + if (_sparse!.TryGetValue(index, out var descriptor) && descriptor != null) { - return propertyDescriptor.IsDataDescriptor() || unwrapFromNonDataDescriptor ? UnwrapJsValue(propertyDescriptor) : null; + value = descriptor.IsDataDescriptor() || unwrapFromNonDataDescriptor + ? UnwrapJsValue(descriptor) + : null; } - return null; + return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteArrayValue(uint index, object? value) + private void WriteArrayValue(uint index, PropertyDescriptor descriptor) { - var dense = _dense; - if (dense != null && index < (uint) dense.Length) + var temp = _dense; + if (temp != null && descriptor.IsDefaultArrayValueDescriptor()) { - if (value is not null && !_isObjectArray && EnsureCompatibleDense(value is JsValue ? typeof(JsValue) : typeof(PropertyDescriptor))) + if (index < (uint) temp.Length) { - dense = _dense; + temp[index] = descriptor.Value; + } + else + { + WriteArrayValueUnlikely(index, descriptor.Value); } - dense![index] = value; } else { - WriteArrayValueUnlikely(index, value); + WriteArrayValueUnlikely(index, descriptor); } } - private void WriteArrayValueUnlikely(uint index, object? value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteArrayValue(uint index, JsValue? value) + { + var temp = _dense; + if (temp != null) + { + if (index < (uint) temp.Length) + { + temp[index] = value; + return; + } + } + + WriteArrayValueUnlikely(index, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void WriteArrayValueUnlikely(uint index, JsValue? value) { // calculate eagerly so we know if we outgrow var dense = _dense; @@ -892,50 +895,45 @@ private void WriteArrayValueUnlikely(uint index, object? value) } else { - if (dense != null) - { - ConvertToSparse(); - } - - _sparse![index] = value; + ConvertToSparse(); + _sparse![index] = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } } - /// - /// Converts to object array when needed. Returns true if conversion was made. - /// - private bool EnsureCompatibleDense(Type expectedElementType) + private void WriteArrayValueUnlikely(uint index, PropertyDescriptor? value) { - if (!_isObjectArray) + if (_sparse == null) { - return CheckConversionUnlikely(expectedElementType); + ConvertToSparse(); } - return false; + _sparse![index] = value; } - private bool CheckConversionUnlikely(Type expectedElementType) + private void ConvertToSparse() { - var currentElementType = _dense!.GetType().GetElementType(); - if (currentElementType != typeof(object) && !expectedElementType.IsAssignableFrom(currentElementType)) + // need to move data + var temp = _dense; + + if (temp is null) { - // triggers conversion for array - EnsureCapacity((uint) _dense.Length, force: true); - return true; + return; } - return false; - } - - private void ConvertToSparse() - { - _sparse = new Dictionary(_dense!.Length <= 1024 ? _dense.Length : 0); - // need to move data - for (uint i = 0; i < (uint) _dense.Length; ++i) + _sparse ??= new Dictionary(); + for (uint i = 0; i < (uint) temp.Length; ++i) { - if (_dense[i] != null) + var value = temp[i]; + if (value != null) { - _sparse[i] = _dense[i]; + _sparse.TryGetValue(i, out var descriptor); + descriptor ??= new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); + descriptor.Value = value; + _sparse[i] = descriptor; + } + else + { + _sparse.Remove(i); } } @@ -962,10 +960,9 @@ internal void EnsureCapacity(uint capacity, bool force = false) } // need to grow - var newArray = new object[capacity]; + var newArray = new JsValue[capacity]; System.Array.Copy(dense, newArray, dense.Length); _dense = newArray; - _isObjectArray = true; } public JsValue[] ToArray() @@ -1020,14 +1017,7 @@ private IEnumerable Enumerate() var value = temp[i]; if (value != null) { - if (value is not PropertyDescriptor descriptor) - { - yield return new IndexedEntry(i, (JsValue) value); - } - else - { - yield return new IndexedEntry(i, descriptor.Value); - } + yield return new IndexedEntry(i, value); } } } @@ -1035,17 +1025,10 @@ private IEnumerable Enumerate() { foreach (var entry in _sparse!) { - var value = entry.Value; - if (value is not null) + var descriptor = entry.Value; + if (descriptor is not null) { - if (value is not PropertyDescriptor descriptor) - { - yield return new IndexedEntry((int) entry.Key, (JsValue) value); - } - else - { - yield return new IndexedEntry((int) entry.Key, descriptor.Value); - } + yield return new IndexedEntry((int) entry.Key, descriptor.Value); } } } @@ -1069,7 +1052,7 @@ public void Push(JsValue value) } else { - WriteValueSlow(n, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); + WriteValueSlow(n, value); } // check if we can set length fast without breaking ECMA specification @@ -1146,15 +1129,15 @@ private bool CanSetLength() } [MethodImpl(MethodImplOptions.NoInlining)] - private void WriteValueSlow(double n, PropertyDescriptor desc) + private void WriteValueSlow(double n, JsValue value) { - if (n < uint.MaxValue) + if (n < ArrayOperations.MaxArrayLength) { - WriteArrayValue((uint) n, desc); + WriteArrayValue((uint) n, value); } else { - DefinePropertyOrThrow((uint) n, desc); + DefinePropertyOrThrow((uint) n, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable)); } } @@ -1325,21 +1308,18 @@ internal void CopyValues(JsArray source, uint sourceStartIndex, uint targetStart } var dense = _dense; - if (dense != null && sourceDense != null - && (uint) dense.Length >= targetStartIndex + length - && dense[targetStartIndex] is null) + if (dense != null + && sourceDense != null + && (uint) dense.Length >= targetStartIndex + length + && dense[targetStartIndex] is null) { uint j = 0; for (var i = sourceStartIndex; i < sourceStartIndex + length; ++i, j++) { - object? sourceValue; + JsValue? sourceValue; if (i < (uint) sourceDense.Length && sourceDense[i] != null) { sourceValue = sourceDense[i]; - if (sourceValue is PropertyDescriptor propertyDescriptor) - { - sourceValue = UnwrapJsValue(propertyDescriptor); - } } else { diff --git a/Jint/Native/Array/ArrayOperations.cs b/Jint/Native/Array/ArrayOperations.cs index 19493e32f9..96b7020a27 100644 --- a/Jint/Native/Array/ArrayOperations.cs +++ b/Jint/Native/Array/ArrayOperations.cs @@ -3,7 +3,6 @@ using Jint.Native.Object; using Jint.Native.TypedArray; using Jint.Runtime; -using Jint.Runtime.Descriptors; namespace Jint.Native.Array { @@ -70,7 +69,7 @@ public virtual JsValue[] GetAll( public abstract bool TryGetValue(ulong index, out JsValue value); - public bool HasProperty(ulong index) => Target.HasProperty(index); + public abstract bool HasProperty(ulong index); public abstract void CreateDataPropertyOrThrow(ulong index, JsValue value); @@ -201,6 +200,8 @@ public override void Set(ulong index, JsValue value, bool updateLength = false, public override void DeletePropertyOrThrow(ulong index) => _target.DeletePropertyOrThrow(JsString.Create(index)); + + public override bool HasProperty(ulong index) => Target.HasProperty(index); } private sealed class ArrayInstanceOperations : ArrayOperations @@ -244,22 +245,18 @@ public override JsValue[] GetAll(Types elementTypes, bool skipHoles = false) var jsValues = new JsValue[n]; for (uint i = 0; i < (uint) jsValues.Length; i++) { - var prop = _target._dense[i]; - if (prop is null) - { - prop = _target.Prototype?.Get(i) ?? JsValue.Undefined; - } - else if (prop is not JsValue) + var value = _target._dense[i]; + if (value is null) { - prop = _target.UnwrapJsValue((PropertyDescriptor) prop); + value = _target.Prototype?.Get(i) ?? JsValue.Undefined; } - if (prop is JsValue jsValue && (jsValue.Type & elementTypes) == 0) + if ((value.Type & elementTypes) == 0) { ExceptionHelper.ThrowTypeErrorNoEngine("invalid type"); } - jsValues[writeIndex++] = (JsValue?) prop ?? JsValue.Undefined; + jsValues[writeIndex++] = (JsValue?) value ?? JsValue.Undefined; } return jsValues; @@ -273,6 +270,8 @@ public override void CreateDataPropertyOrThrow(ulong index, JsValue value) public override void Set(ulong index, JsValue value, bool updateLength = false, bool throwOnError = true) => _target.SetIndexValue((uint) index, value, updateLength); + + public override bool HasProperty(ulong index) => _target.HasProperty(index); } private sealed class TypedArrayInstanceOperations : ArrayOperations @@ -336,6 +335,8 @@ public override void Set(ulong index, JsValue value, bool updateLength = false, public override void DeletePropertyOrThrow(ulong index) => _target.DeletePropertyOrThrow(index); + + public override bool HasProperty(ulong index) => _target.HasProperty(index); } } diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index 1d48f6e3eb..bdc1d4d341 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -1648,10 +1648,10 @@ public JsValue Pop(JsValue thisObject, JsValue[] arguments) return element; } - private object[] CreateBackingArray(ulong length) + private JsValue[] CreateBackingArray(ulong length) { ValidateArrayLength(length); - return new object[length]; + return new JsValue[length]; } private void ValidateArrayLength(ulong length) diff --git a/Jint/Native/JsArray.cs b/Jint/Native/JsArray.cs index f17d82c622..26068a839b 100644 --- a/Jint/Native/JsArray.cs +++ b/Jint/Native/JsArray.cs @@ -1,5 +1,4 @@ using Jint.Native.Array; -using Jint.Runtime.Descriptors; namespace Jint.Native; @@ -22,16 +21,4 @@ public JsArray(Engine engine, uint capacity = 0, uint length = 0) : base(engine, public JsArray(Engine engine, JsValue[] items) : base(engine, items) { } - - /// - /// Possibility to construct valid array fast, requires that supplied array does not have holes. - /// The array will be owned and modified by Jint afterwards. - /// - public JsArray(Engine engine, PropertyDescriptor[] items) : base(engine, items) - { - } - - internal JsArray(Engine engine, object[] items) : base(engine, items) - { - } } diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index ec09c45cca..b95ab8fd64 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -555,6 +555,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) return SetUnlikely(property, value, receiver); } + [MethodImpl(MethodImplOptions.NoInlining)] private bool SetUnlikely(JsValue property, JsValue value, JsValue receiver) { var ownDesc = GetOwnProperty(property); @@ -1510,7 +1511,7 @@ internal virtual ulong GetSmallestIndex(ulong length) } var min = length; - foreach (var entry in Properties) + foreach (var entry in GetOwnProperties()) { if (ulong.TryParse(entry.Key.ToString(), out var index)) { @@ -1520,7 +1521,7 @@ internal virtual ulong GetSmallestIndex(ulong length) if (Prototype?.Properties != null) { - foreach (var entry in Prototype.Properties) + foreach (var entry in Prototype.GetOwnProperties()) { if (ulong.TryParse(entry.Key.ToString(), out var index)) { diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index ece06bf412..3b6f7e88c6 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -845,40 +845,45 @@ public static uint ToIndex(Realm realm, JsValue value) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(long i) { - return i >= 0 && i < intToString.Length - ? intToString[i] + var temp = intToString; + return (ulong) i < (ulong) temp.Length + ? temp[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(int i) { - return i >= 0 && i < intToString.Length - ? intToString[i] + var temp = intToString; + return (uint) i < (uint) temp.Length + ? temp[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(uint i) { - return i < (uint) intToString.Length - ? intToString[i] + var temp = intToString; + return i < (uint) temp.Length + ? temp[i] : i.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(char c) { - return c >= 0 && c < charToString.Length - ? charToString[c] + var temp = charToString; + return (uint) c < (uint) temp.Length + ? temp[c] : c.ToString(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(ulong i) { - return i >= 0 && i < (ulong) intToString.Length - ? intToString[i] + var temp = intToString; + return i < (ulong) temp.Length + ? temp[i] : i.ToString(); } From 9dd79279b1a2d16e5988da57d01e7914aa9b7b5f Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Tue, 29 Aug 2023 20:52:14 +0300 Subject: [PATCH 03/44] Update benchmark results and packages (#1622) --- Jint.Benchmark/EngineComparisonBenchmark.cs | 2 + Jint.Benchmark/Jint.Benchmark.csproj | 6 +- Jint.Benchmark/README.md | 154 +++++++++--------- .../Jint.Tests.CommonScripts.csproj | 6 +- .../Jint.Tests.PublicInterface.csproj | 13 +- Jint.Tests.Test262/Jint.Tests.Test262.csproj | 6 +- Jint.Tests/Jint.Tests.csproj | 11 +- Jint/Jint.csproj | 2 +- Jint/Native/Global/GlobalObject.cs | 3 - Jint/Runtime/Interop/ObjectWrapper.cs | 1 - 10 files changed, 104 insertions(+), 100 deletions(-) diff --git a/Jint.Benchmark/EngineComparisonBenchmark.cs b/Jint.Benchmark/EngineComparisonBenchmark.cs index ecaf7d2aa6..2277284711 100644 --- a/Jint.Benchmark/EngineComparisonBenchmark.cs +++ b/Jint.Benchmark/EngineComparisonBenchmark.cs @@ -1,5 +1,6 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; using Esprima; using Esprima.Ast; @@ -7,6 +8,7 @@ namespace Jint.Benchmark; [RankColumn] [MemoryDiagnoser] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByParams)] [HideColumns("Error", "Gen0", "Gen1", "Gen2")] [BenchmarkCategory("EngineComparison")] diff --git a/Jint.Benchmark/Jint.Benchmark.csproj b/Jint.Benchmark/Jint.Benchmark.csproj index a29e3894d7..fa88bf720e 100644 --- a/Jint.Benchmark/Jint.Benchmark.csproj +++ b/Jint.Benchmark/Jint.Benchmark.csproj @@ -24,10 +24,10 @@ - + - - + + \ No newline at end of file diff --git a/Jint.Benchmark/README.md b/Jint.Benchmark/README.md index a2867e9a37..9bacc72f8a 100644 --- a/Jint.Benchmark/README.md +++ b/Jint.Benchmark/README.md @@ -9,91 +9,91 @@ dotnet run -c Release --allCategories EngineComparison * tests are run in global engine strict mode, as YantraJS always uses strict mode which improves performance * `Jint` and `Jint_ParsedScript` shows the difference between always parsing the script source file and reusing parsed `Script` instance. -Last updated 2023-03-26 +Last updated 2023-08-29 * Jint main * Jurassic 3.2.6 -* NiL.JS 2.5.1650 -* YantraJS.Core 1.2.117 +* NiL.JS 2.5.1665 +* YantraJS.Core 1.2.179 +``` -``` ini - -BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1483/22H2/2022Update/SunValley2) +BenchmarkDotNet v0.13.7, Windows 11 (10.0.23531.1001) AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores -.NET SDK=7.0.202 - [Host] : .NET 6.0.15 (6.0.1523.11507), X64 RyuJIT AVX2 - DefaultJob : .NET 6.0.15 (6.0.1523.11507), X64 RyuJIT AVX2 +.NET SDK 8.0.100-preview.7.23376.3 + [Host] : .NET 6.0.21 (6.0.2123.36311), X64 RyuJIT AVX2 + DefaultJob : .NET 6.0.21 (6.0.2123.36311), X64 RyuJIT AVX2 ``` -| Method | FileName | Mean | StdDev | Rank | Allocated | -|------------------ |--------------------- |-----------------:|---------------:|-----:|--------------:| -| **Jint** | **array-stress** | **10,799.249 μs** | **24.4948 μs** | **4** | **7473326 B** | -| Jint_ParsedScript | array-stress | 10,568.342 μs | 44.6133 μs | 3 | 7446494 B | -| Jurassic | array-stress | 11,280.104 μs | 27.9486 μs | 5 | 11926463 B | -| NilJS | array-stress | 5,600.761 μs | 74.2339 μs | 2 | 4241527 B | -| YantraJS | array-stress | 4,891.973 μs | 18.8130 μs | 1 | 6518316 B | -| | | | | | | -| **Jint** | **dromaeo-3d-cube** | **25,234.322 μs** | **28.3103 μs** | **4** | **6307004 B** | -| Jint_ParsedScript | dromaeo-3d-cube | 24,227.022 μs | 30.4230 μs | 3 | 6000208 B | -| Jurassic | dromaeo-3d-cube | 40,155.874 μs | 83.9446 μs | 5 | 10925739 B | -| NilJS | dromaeo-3d-cube | 9,203.608 μs | 18.2137 μs | 2 | 4671638 B | -| YantraJS | dromaeo-3d-cube | 4,478.599 μs | 35.6857 μs | 1 | 8885293 B | -| | | | | | | -| **Jint** | **dromaeo-core-eval** | **5,496.472 μs** | **20.8861 μs** | **2** | **359439 B** | -| Jint_ParsedScript | dromaeo-core-eval | 5,555.814 μs | 29.8913 μs | 2 | 339647 B | -| Jurassic | dromaeo-core-eval | 13,739.640 μs | 52.5257 μs | 4 | 2971062 B | -| NilJS | dromaeo-core-eval | 2,099.576 μs | 13.3939 μs | 1 | 1637011 B | -| YantraJS | dromaeo-core-eval | 8,068.664 μs | 41.8267 μs | 3 | 37131162 B | -| | | | | | | -| **Jint** | **dromaeo-object-array** | **68,157.350 μs** | **106.5991 μs** | **4** | **103974805 B** | -| Jint_ParsedScript | dromaeo-object-array | 70,379.819 μs | 412.9971 μs | 5 | 103928047 B | -| Jurassic | dromaeo-object-array | 41,917.036 μs | 147.6689 μs | 1 | 26433545 B | -| NilJS | dromaeo-object-array | 50,941.149 μs | 116.1891 μs | 3 | 16518497 B | -| YantraJS | dromaeo-object-array | 43,952.908 μs | 52.7966 μs | 2 | 25538081 B | -| | | | | | | -| **Jint** | **droma(...)egexp [21]** | **202,513.639 μs** | **6,280.4683 μs** | **2** | **169538072 B** | -| Jint_ParsedScript | droma(...)egexp [21] | 194,203.576 μs | 2,648.5440 μs | 1 | 176401621 B | -| Jurassic | droma(...)egexp [21] | 673,099.103 μs | 19,449.6381 μs | 4 | 845159056 B | -| NilJS | droma(...)egexp [21] | 498,404.131 μs | 9,389.0173 μs | 3 | 785342384 B | -| YantraJS | droma(...)egexp [21] | 1,021,021.213 μs | 10,171.5386 μs | 5 | 965363992 B | -| | | | | | | -| **Jint** | **droma(...)tring [21]** | **513,139.116 μs** | **21,598.0545 μs** | **2** | **1353627632 B** | -| Jint_ParsedScript | droma(...)tring [21] | 598,346.054 μs | 62,241.8961 μs | 4 | 1353603760 B | -| Jurassic | droma(...)tring [21] | 547,882.282 μs | 15,131.8012 μs | 3 | 1492956848 B | -| NilJS | droma(...)tring [21] | 380,880.402 μs | 17,953.6040 μs | 1 | 1410785672 B | -| YantraJS | droma(...)tring [21] | 3,146,801.586 μs | 75,534.4740 μs | 5 | 16097426568 B | -| | | | | | | -| **Jint** | **droma(...)ase64 [21]** | **54,972.605 μs** | **123.8465 μs** | **3** | **6764151 B** | -| Jint_ParsedScript | droma(...)ase64 [21] | 53,534.261 μs | 272.9451 μs | 2 | 6665682 B | -| Jurassic | droma(...)ase64 [21] | 69,111.283 μs | 166.1184 μs | 4 | 76105181 B | -| NilJS | droma(...)ase64 [21] | 40,131.392 μs | 385.5791 μs | 1 | 20074818 B | -| YantraJS | droma(...)ase64 [21] | 108,860.979 μs | 697.7800 μs | 5 | 778591469 B | -| | | | | | | -| **Jint** | **evaluation** | **28.773 μs** | **0.0494 μs** | **2** | **36792 B** | -| Jint_ParsedScript | evaluation | 11.296 μs | 0.0295 μs | 1 | 27072 B | -| Jurassic | evaluation | 1,291.843 μs | 3.3295 μs | 5 | 430510 B | -| NilJS | evaluation | 45.473 μs | 0.1385 μs | 3 | 24032 B | -| YantraJS | evaluation | 120.995 μs | 0.3959 μs | 4 | 177876 B | -| | | | | | | -| **Jint** | **linq-js** | **1,774.118 μs** | **8.6162 μs** | **2** | **1303058 B** | -| Jint_ParsedScript | linq-js | 90.567 μs | 0.2234 μs | 1 | 213218 B | -| Jurassic | linq-js | 39,199.309 μs | 614.5447 μs | 4 | 9525761 B | -| NilJS | linq-js | 7,630.382 μs | 11.3679 μs | 3 | 4226480 B | -| YantraJS | linq-js | NA | NA | ? | - | -| | | | | | | -| **Jint** | **minimal** | **4.937 μs** | **0.0103 μs** | **3** | **14664 B** | -| Jint_ParsedScript | minimal | 3.124 μs | 0.0123 μs | 1 | 13168 B | -| Jurassic | minimal | 238.384 μs | 0.6775 μs | 5 | 395506 B | -| NilJS | minimal | 4.751 μs | 0.0103 μs | 2 | 4928 B | -| YantraJS | minimal | 117.579 μs | 0.5920 μs | 4 | 173770 B | -| | | | | | | -| **Jint** | **stopwatch** | **368,697.936 μs** | **1,043.7863 μs** | **4** | **52946080 B** | -| Jint_ParsedScript | stopwatch | 408,149.533 μs | 1,052.2644 μs | 5 | 52912288 B | -| Jurassic | stopwatch | 211,731.362 μs | 544.2606 μs | 2 | 160704435 B | -| NilJS | stopwatch | 238,188.624 μs | 480.6546 μs | 3 | 76378157 B | -| YantraJS | stopwatch | 78,043.391 μs | 228.5660 μs | 1 | 264535415 B | +| Method | FileName | Mean | StdDev | Median | Rank | Allocated | +|------------------ |--------------------- |-----------------:|---------------:|-----------------:|-----:|---------------:| +| YantraJS | array-stress | 6,246.687 μs | 28.4141 μs | 6,240.110 μs | 1 | 6533.07 KB | +| NilJS | array-stress | 7,629.064 μs | 21.4722 μs | 7,626.207 μs | 2 | 4533.76 KB | +| Jint | array-stress | 11,304.127 μs | 24.2301 μs | 11,301.966 μs | 3 | 7111.4 KB | +| Jint_ParsedScript | array-stress | 11,928.758 μs | 78.8122 μs | 11,974.534 μs | 4 | 7085.34 KB | +| Jurassic | array-stress | 13,568.895 μs | 49.1134 μs | 13,548.192 μs | 5 | 11646.96 KB | +| | | | | | | | +| YantraJS | dromaeo-3d-cube | NA | NA | NA | ? | NA | +| NilJS | dromaeo-3d-cube | 11,262.315 μs | 39.4986 μs | 11,253.403 μs | 1 | 4694.63 KB | +| Jint_ParsedScript | dromaeo-3d-cube | 26,401.931 μs | 35.0936 μs | 26,404.000 μs | 2 | 5894.78 KB | +| Jint | dromaeo-3d-cube | 27,799.875 μs | 24.2606 μs | 27,802.781 μs | 3 | 6191.02 KB | +| Jurassic | dromaeo-3d-cube | 49,539.274 μs | 137.7463 μs | 49,615.655 μs | 4 | 10671.74 KB | +| | | | | | | | +| NilJS | dromaeo-core-eval | 2,638.637 μs | 8.3816 μs | 2,642.709 μs | 1 | 1598.78 KB | +| Jint | dromaeo-core-eval | 5,986.834 μs | 23.0157 μs | 5,978.144 μs | 2 | 350.31 KB | +| Jint_ParsedScript | dromaeo-core-eval | 6,049.918 μs | 47.3595 μs | 6,052.202 μs | 2 | 331.04 KB | +| YantraJS | dromaeo-core-eval | 15,121.048 μs | 33.6780 μs | 15,127.921 μs | 3 | 36547.42 KB | +| Jurassic | dromaeo-core-eval | 16,809.782 μs | 6.8618 μs | 16,811.078 μs | 4 | 2901.46 KB | +| | | | | | | | +| Jurassic | dromaeo-object-array | 50,652.934 μs | 193.8856 μs | 50,633.500 μs | 1 | 25814.2 KB | +| YantraJS | dromaeo-object-array | 59,426.130 μs | 492.9887 μs | 59,443.689 μs | 2 | 24745.94 KB | +| Jint_ParsedScript | dromaeo-object-array | 66,402.409 μs | 254.3555 μs | 66,323.750 μs | 3 | 100747.98 KB | +| Jint | dromaeo-object-array | 67,909.973 μs | 374.0128 μs | 67,856.262 μs | 4 | 100792.72 KB | +| NilJS | dromaeo-object-array | 76,716.933 μs | 335.0924 μs | 76,717.700 μs | 5 | 17698.13 KB | +| | | | | | | | +| Jint_ParsedScript | droma(...)egexp [21] | 298,212.421 μs | 4,821.1841 μs | 297,469.850 μs | 1 | 165381.18 KB | +| Jint | droma(...)egexp [21] | 308,929.771 μs | 7,106.6296 μs | 307,314.500 μs | 2 | 168713.91 KB | +| NilJS | droma(...)egexp [21] | 603,665.040 μs | 4,970.4874 μs | 605,048.300 μs | 3 | 768274.13 KB | +| Jurassic | droma(...)egexp [21] | 841,737.444 μs | 30,514.3096 μs | 839,741.400 μs | 4 | 825832.59 KB | +| YantraJS | droma(...)egexp [21] | 1,202,044.984 μs | 25,781.0535 μs | 1,198,305.600 μs | 5 | 941580.41 KB | +| | | | | | | | +| NilJS | droma(...)tring [21] | 417,303.358 μs | 13,929.0807 μs | 418,108.100 μs | 1 | 1377743.66 KB | +| Jint_ParsedScript | droma(...)tring [21] | 563,559.551 μs | 33,924.5891 μs | 551,658.750 μs | 2 | 1322031.27 KB | +| Jint | droma(...)tring [21] | 572,808.661 μs | 30,017.2335 μs | 570,151.650 μs | 2 | 1322176.1 KB | +| Jurassic | droma(...)tring [21] | 692,137.075 μs | 28,047.3722 μs | 698,963.900 μs | 3 | 1457949.11 KB | +| YantraJS | droma(...)tring [21] | 4,060,814.093 μs | 60,908.6909 μs | 4,079,384.300 μs | 4 | 15718148.67 KB | +| | | | | | | | +| NilJS | droma(...)ase64 [21] | 47,816.450 μs | 138.7136 μs | 47,770.455 μs | 1 | 19605.27 KB | +| Jint | droma(...)ase64 [21] | 65,790.989 μs | 272.8843 μs | 65,817.512 μs | 2 | 6772.48 KB | +| Jint_ParsedScript | droma(...)ase64 [21] | 66,114.687 μs | 146.3463 μs | 66,118.562 μs | 2 | 6680.29 KB | +| Jurassic | droma(...)ase64 [21] | 84,478.585 μs | 323.0873 μs | 84,454.725 μs | 3 | 74321.7 KB | +| YantraJS | droma(...)ase64 [21] | 235,350.207 μs | 955.7123 μs | 235,605.333 μs | 4 | 760629.53 KB | +| | | | | | | | +| Jint_ParsedScript | evaluation | 13.655 μs | 0.0306 μs | 13.663 μs | 1 | 26.46 KB | +| Jint | evaluation | 34.425 μs | 0.1069 μs | 34.444 μs | 2 | 35.89 KB | +| NilJS | evaluation | 56.276 μs | 0.1443 μs | 56.288 μs | 3 | 23.47 KB | +| YantraJS | evaluation | 129.051 μs | 0.5500 μs | 129.043 μs | 4 | 462.57 KB | +| Jurassic | evaluation | 1,611.643 μs | 2.3082 μs | 1,611.278 μs | 5 | 420.41 KB | +| | | | | | | | +| YantraJS | linq-js | NA | NA | NA | ? | NA | +| Jint_ParsedScript | linq-js | 116.014 μs | 0.6197 μs | 115.770 μs | 1 | 215.13 KB | +| Jint | linq-js | 2,248.864 μs | 6.9181 μs | 2,247.281 μs | 2 | 1274.64 KB | +| NilJS | linq-js | 9,450.914 μs | 26.1239 μs | 9,452.424 μs | 3 | 4127.79 KB | +| Jurassic | linq-js | 46,341.837 μs | 310.1326 μs | 46,370.700 μs | 4 | 9305.52 KB | +| | | | | | | | +| Jint_ParsedScript | minimal | 3.203 μs | 0.0178 μs | 3.196 μs | 1 | 12.9 KB | +| Jint | minimal | 5.230 μs | 0.0169 μs | 5.229 μs | 2 | 14.34 KB | +| NilJS | minimal | 6.055 μs | 0.0276 μs | 6.041 μs | 3 | 4.81 KB | +| YantraJS | minimal | 123.197 μs | 2.7249 μs | 122.828 μs | 4 | 458.56 KB | +| Jurassic | minimal | 297.292 μs | 0.6073 μs | 297.435 μs | 5 | 386.24 KB | +| | | | | | | | +| YantraJS | stopwatch | 112,530.838 μs | 449.4615 μs | 112,460.320 μs | 1 | 258622.36 KB | +| Jurassic | stopwatch | 247,085.684 μs | 1,162.2628 μs | 246,727.867 μs | 2 | 156937.08 KB | +| NilJS | stopwatch | 295,878.240 μs | 2,000.4676 μs | 295,209.200 μs | 3 | 97361.17 KB | +| Jint_ParsedScript | stopwatch | 471,369.071 μs | 1,578.5815 μs | 471,148.200 μs | 4 | 53023.28 KB | +| Jint | stopwatch | 472,028.947 μs | 3,209.3311 μs | 471,611.400 μs | 4 | 53044.52 KB | Benchmarks with issues: -EngineComparisonBenchmark.YantraJS: DefaultJob [FileName=linq-js] \ No newline at end of file +EngineComparisonBenchmark.YantraJS: DefaultJob [FileName=dromaeo-3d-cube] +EngineComparisonBenchmark.YantraJS: DefaultJob [FileName=linq-js] diff --git a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj index e7a235e631..3032ed81dd 100644 --- a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj +++ b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj @@ -16,10 +16,10 @@ - - + + - + diff --git a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj index 272dd0c4dc..f191524e97 100644 --- a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj +++ b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj @@ -22,13 +22,16 @@ - - + + - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Jint.Tests.Test262/Jint.Tests.Test262.csproj b/Jint.Tests.Test262/Jint.Tests.Test262.csproj index e7e7e5d8ab..0e3f08d1e6 100644 --- a/Jint.Tests.Test262/Jint.Tests.Test262.csproj +++ b/Jint.Tests.Test262/Jint.Tests.Test262.csproj @@ -16,10 +16,10 @@ - - + + - + diff --git a/Jint.Tests/Jint.Tests.csproj b/Jint.Tests/Jint.Tests.csproj index 549f914646..4403b74345 100644 --- a/Jint.Tests/Jint.Tests.csproj +++ b/Jint.Tests/Jint.Tests.csproj @@ -25,13 +25,16 @@ - + - + - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index cc591bc37b..fbddda8229 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index 4496af979b..cd931e2f19 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -3,13 +3,10 @@ using System.Runtime.CompilerServices; using System.Text; using Esprima; -using Jint.Collections; using Jint.Native.Object; using Jint.Native.String; using Jint.Runtime; using Jint.Runtime.Descriptors; -using Jint.Runtime.Descriptors.Specialized; -using Jint.Runtime.Interop; namespace Jint.Native.Global { diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index 2b76866843..725feaf21a 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -1,4 +1,3 @@ -using System; using System.Collections; using System.Globalization; using System.Reflection; From d48ebd50ba5af240f484a3763227d2a53999a365 Mon Sep 17 00:00:00 2001 From: viruscamp Date: Thu, 31 Aug 2023 16:36:24 +0800 Subject: [PATCH 04/44] Interface type wrapper can call object methods. (#1629) Fix #1626 --- .../Runtime/InteropExplicitTypeTests.cs | 60 ++++++++++++++++++- Jint/Runtime/Interop/TypeResolver.cs | 17 +++++- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs index 859294dec1..eef485018a 100644 --- a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs +++ b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs @@ -1,5 +1,6 @@ namespace Jint.Tests.Runtime; +using Jint.Native; using Jint.Runtime.Interop; public class InteropExplicitTypeTests @@ -154,9 +155,9 @@ public NullabeStruct() } public string name = "NullabeStruct"; - public string Name { get => name; } + public string Name => name; - string I1.Name { get => "NullabeStruct as I1"; } + string I1.Name => "NullabeStruct as I1"; } public class NullableHolder @@ -172,7 +173,7 @@ public void TypedObjectWrapperForNullableType() _engine.SetValue("nullableHolder", nullableHolder); _engine.SetValue("nullabeStruct", new NullabeStruct()); - Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), Native.JsValue.Null); + Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), JsValue.Null); _engine.Evaluate("nullableHolder.NullabeStruct = nullabeStruct"); Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct.Name"), nullableHolder.NullabeStruct?.Name); } @@ -288,4 +289,57 @@ public void ClrHelperObjectToType() }); Assert.Equal("Invalid when Engine.Options.Interop.AllowGetType == false", ex.Message); } + + public interface ICallObjectMethodFromInterface + { + ICallObjectMethodFromInterface Instance { get; } + // hide Object.GetHashCode + string GetHashCode(); + // overload Object.Equals + string Equals(); + } + public class CallObjectMethodFromInterface : ICallObjectMethodFromInterface + { + public ICallObjectMethodFromInterface Instance => this; + public override string ToString() => nameof(CallObjectMethodFromInterface); + public new string GetHashCode() => "new GetHashCode, hide Object.GetHashCode"; + public string Equals() => "overload Object.Equals"; + } + + // issue#1626 ToString method is now unavailable in some CLR Interop scenarios + [Fact] + public void CallObjectMethodFromInterfaceWrapper() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.ToString(), _engine.Evaluate("inst.Instance.ToString()")); + } + + [Fact] + public void CallInterfaceMethodWhichHideObjectMethod() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.GetHashCode(), _engine.Evaluate("inst.Instance.GetHashCode()")); + } + + [Fact(Skip = "TODO, no solution now.")] + public void CallObjectMethodHiddenByInterface() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal( + (inst.Instance as object).GetHashCode(), + _engine.Evaluate("clrHelper.unwrap(inst.Instance).GetHashCode()") + ); + } + + [Fact] + public void CallInterfaceMethodWhichOverloadObjectMethod() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.Equals(), _engine.Evaluate("inst.Instance.Equals()")); + Assert.Equal(inst.Instance.Equals(inst), _engine.Evaluate("inst.Instance.Equals(inst)")); + } } diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index af40e876ac..45c0e45d29 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -267,11 +267,11 @@ internal bool TryFindMemberAccessor( // if no properties were found then look for a method List? methods = null; - foreach (var m in type.GetMethods(bindingFlags)) + void AddMethod(MethodInfo m) { if (!Filter(engine, m)) { - continue; + return; } foreach (var name in typeResolverMemberNameCreator(m)) @@ -283,6 +283,10 @@ internal bool TryFindMemberAccessor( } } } + foreach (var m in type.GetMethods(bindingFlags)) + { + AddMethod(m); + } // TPC: need to grab the extension methods here - for overloads if (engine._extensionMethods.TryGetExtensionMethods(type, out var extensionMethods)) @@ -297,6 +301,15 @@ internal bool TryFindMemberAccessor( } } + // Add Object methods to interface + if (type.IsInterface) + { + foreach (var m in typeof(object).GetMethods(bindingFlags)) + { + AddMethod(m); + } + } + if (methods?.Count > 0) { accessor = new MethodAccessor(type, memberName, MethodDescriptor.Build(methods)); From 073497d32e798edaf2ce3c68307e7cf8d57dd29e Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 3 Sep 2023 21:17:26 +0300 Subject: [PATCH 05/44] Upgrade to Esprima 3.0.0 (#1630) --- Jint/EsprimaExtensions.cs | 1 - Jint/HoistingScope.cs | 1 - Jint/Jint.csproj | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Jint/EsprimaExtensions.cs b/Jint/EsprimaExtensions.cs index dd0f8047da..c3cc87f85f 100644 --- a/Jint/EsprimaExtensions.cs +++ b/Jint/EsprimaExtensions.cs @@ -91,7 +91,6 @@ internal static bool IsFunctionDefinition(this T node) where T : Node return type is Nodes.FunctionExpression or Nodes.ArrowFunctionExpression - or Nodes.ArrowParameterPlaceHolder or Nodes.ClassExpression; } diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index 7d3a1fc0d8..7fde8159ae 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -281,7 +281,6 @@ public void Visit(Node node, Node? parent) if (childType != Nodes.FunctionDeclaration && childType != Nodes.ArrowFunctionExpression - && childType != Nodes.ArrowParameterPlaceHolder && childType != Nodes.FunctionExpression && !childNode.ChildNodes.IsEmpty()) { diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index fbddda8229..9abaee5412 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -16,7 +16,7 @@ - + From 29b67f809fff85cefbcd591b64651868b26f14ce Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 10 Sep 2023 21:34:54 +0300 Subject: [PATCH 06/44] Upgrade to Esprima 3.0.1 (#1635) --- Jint/Jint.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index 9abaee5412..2baa5a7fac 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -16,7 +16,7 @@ - + From da88d1ea53d836710aa47c24a3a2fbdf65fe911d Mon Sep 17 00:00:00 2001 From: Tony Han Date: Tue, 26 Sep 2023 13:58:22 +0800 Subject: [PATCH 07/44] Include interface methods when searching for interop targets (#1638) Co-authored-by: hyzx86 Co-authored-by: Marko Lahma --- .../Runtime/InteropInterfaceExtendTests.cs | 151 ++++++++++++++++++ Jint/Runtime/Interop/TypeResolver.cs | 15 +- 2 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 Jint.Tests/Runtime/InteropInterfaceExtendTests.cs diff --git a/Jint.Tests/Runtime/InteropInterfaceExtendTests.cs b/Jint.Tests/Runtime/InteropInterfaceExtendTests.cs new file mode 100644 index 0000000000..fc50064581 --- /dev/null +++ b/Jint.Tests/Runtime/InteropInterfaceExtendTests.cs @@ -0,0 +1,151 @@ +using Jint.Runtime; + +namespace Jint.Tests.Runtime; + +public class InterfaceTests +{ + public interface I0 + { + string NameI0 { get; } + string OverloadSuperMethod(); + string SubPropertySuperMethod(); + } + + public interface I1 : I0 + { + string Name { get; } + string OverloadSuperMethod(int x); + new string SubPropertySuperMethod { get; } + } + + public class Super + { + public string Name { get; } = "Super"; + } + + public class CI1 : Super, I1 + { + public new string Name { get; } = "CI1"; + + public string NameI0 { get; } = "I0.Name"; + + string I1.Name { get; } = "CI1 as I1"; + + string I1.SubPropertySuperMethod { get; } = "I1.SubPropertySuperMethod"; + + public string OverloadSuperMethod() + { + return "I0.OverloadSuperMethod()"; + } + + public string OverloadSuperMethod(int x) + { + return $"I1.OverloadSuperMethod(int {x})"; + } + + public string SubPropertySuperMethod() + { + return "I0.SubPropertySuperMethod()"; + } + } + + public class Indexer + { + private readonly T t; + + public Indexer(T t) + { + this.t = t; + } + + public T this[int index] + { + get { return t; } + } + } + + public class InterfaceHolder + { + public InterfaceHolder() + { + var ci1 = new CI1(); + this.ci1 = ci1; + this.i1 = ci1; + this.super = ci1; + + this.IndexerCI1 = new Indexer(ci1); + this.IndexerI1 = new Indexer(ci1); + this.IndexerSuper = new Indexer(ci1); + } + + public readonly CI1 ci1; + public readonly I1 i1; + public readonly Super super; + + public CI1 CI1 { get => ci1; } + public I1 I1 { get => i1; } + public Super Super { get => super; } + + public CI1 GetCI1() => ci1; + public I1 GetI1() => i1; + public Super GetSuper() => super; + + public Indexer IndexerCI1 { get; } + public Indexer IndexerI1 { get; } + public Indexer IndexerSuper { get; } + } + + private readonly Engine _engine; + private readonly InterfaceHolder holder; + + public InterfaceTests() + { + holder = new InterfaceHolder(); + _engine = new Engine(cfg => cfg.AllowClr( + typeof(CI1).Assembly, + typeof(Console).Assembly, + typeof(File).Assembly)) + .SetValue("log", new Action(Console.WriteLine)) + .SetValue("assert", new Action(Assert.True)) + .SetValue("equal", new Action(Assert.Equal)) + .SetValue("holder", holder) + ; + } + + [Fact] + public void CallSuperPropertyFromInterface() + { + Assert.Equal(holder.I1.NameI0, _engine.Evaluate("holder.I1.NameI0")); + } + + [Fact] + public void CallOverloadSuperMethod() + { + Assert.Equal( + holder.I1.OverloadSuperMethod(1), + _engine.Evaluate("holder.I1.OverloadSuperMethod(1)")); + Assert.Equal( + holder.I1.OverloadSuperMethod(), + _engine.Evaluate("holder.I1.OverloadSuperMethod()")); + } + + [Fact] + public void CallSubPropertySuperMethod_SubProperty() + { + Assert.Equal( + holder.I1.SubPropertySuperMethod, + _engine.Evaluate("holder.I1.SubPropertySuperMethod")); + } + + [Fact] + public void CallSubPropertySuperMethod_SuperMethod() + { + var ex = Assert.Throws(() => + { + Assert.Equal( + holder.I1.SubPropertySuperMethod(), + _engine.Evaluate("holder.I1.SubPropertySuperMethod()")); + }); + Assert.Equal("Property 'SubPropertySuperMethod' of object is not a function", ex.Message); + } +} diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index 45c0e45d29..94de267184 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -283,21 +283,26 @@ void AddMethod(MethodInfo m) } } } + foreach (var m in type.GetMethods(bindingFlags)) { AddMethod(m); } + foreach (var iface in type.GetInterfaces()) + { + foreach (var m in iface.GetMethods()) + { + AddMethod(m); + } + } + // TPC: need to grab the extension methods here - for overloads if (engine._extensionMethods.TryGetExtensionMethods(type, out var extensionMethods)) { foreach (var methodInfo in extensionMethods) { - if (memberNameComparer.Equals(methodInfo.Name, memberName)) - { - methods ??= new List(); - methods.Add(methodInfo); - } + AddMethod(methodInfo); } } From 1697fc39f39c831bac3161b73690b380f02676d2 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 1 Oct 2023 13:24:07 -0700 Subject: [PATCH 08/44] Fix rest/spread bug with empty object (#1642) --- Jint.Tests/Runtime/DestructuringTests.cs | 6 ++++++ Jint/Runtime/Environments/FunctionEnvironmentRecord.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Jint.Tests/Runtime/DestructuringTests.cs b/Jint.Tests/Runtime/DestructuringTests.cs index 4c1ac13cea..f62264cc7b 100644 --- a/Jint.Tests/Runtime/DestructuringTests.cs +++ b/Jint.Tests/Runtime/DestructuringTests.cs @@ -62,4 +62,10 @@ public void WithNestedRest() { _engine.Execute("return function([x, ...[y, ...z]]) { equal(1, x); equal(2, y); equal('3,4', z + ''); }([1, 2, 3, 4]);"); } + + [Fact] + public void EmptyRest() + { + _engine.Execute("function test({ ...props }){}; test({});"); + } } diff --git a/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs b/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs index ed5ade0f2f..20c593bab6 100644 --- a/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs @@ -221,7 +221,7 @@ private void HandleObjectPattern(EvaluationContext context, bool initiallyEmpty, { if (((RestElement) property).Argument is Identifier restIdentifier) { - var rest = _engine.Realm.Intrinsics.Object.Construct(argumentObject.Properties!.Count - processedProperties!.Count); + var rest = _engine.Realm.Intrinsics.Object.Construct((argumentObject.Properties?.Count ?? 0) - processedProperties!.Count); argumentObject.CopyDataProperties(rest, processedProperties); SetItemSafely(restIdentifier.Name, rest, initiallyEmpty); } From c905f53c998ba7723e41a0c18e56268cfce5ad6a Mon Sep 17 00:00:00 2001 From: Umut Akkaya Date: Thu, 12 Oct 2023 07:58:18 +0300 Subject: [PATCH 09/44] Add experimental support for Task to Promise conversion (#1567) This commit fixes an issue where an `async Task` (be it a method or a delegate) would end up being marshalled directly to JS, giving a `Task` to the user, instead of `undefined`, which is what is returned for "synchronous tasks", i.e. any Task-returning invokable function that does not generate an async state machine of its own (that is to say, any function that returns `Task`, not `async Task`. This commit fixes the issue by checking if a Task's result is equal to `VoidTaskResult`, which is an internal type used by the runtime to indicate a void-returning Task, such as that from an `async Task` method/delegate --------- Co-authored-by: Velvet Toroyashi <42438262+VelvetToroyashi@users.noreply.github.com> --- Jint.Tests/Runtime/AwaitTests.cs | 68 +++++++++++++++++++++++++ Jint/Runtime/Interop/DelegateWrapper.cs | 56 +++++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/Jint.Tests/Runtime/AwaitTests.cs b/Jint.Tests/Runtime/AwaitTests.cs index c1fa20d28e..f3a840e3cd 100644 --- a/Jint.Tests/Runtime/AwaitTests.cs +++ b/Jint.Tests/Runtime/AwaitTests.cs @@ -10,4 +10,72 @@ public void AwaitPropagationAgainstPrimitiveValue() result = result.UnwrapIfPromise(); Assert.Equal("1", result); } + + [Fact] + public void ShouldTaskConvertedToPromiseInJS() + { + Engine engine = new(); + engine.SetValue("callable", Callable); + var result = engine.Evaluate("callable().then(x=>x*2)"); + result = result.UnwrapIfPromise(); + Assert.Equal(2, result); + + static async Task Callable() + { + await Task.Delay(10); + Assert.True(true); + return 1; + } + } + + [Fact] + public void ShouldTaskCatchWhenCancelled() + { + Engine engine = new(); + CancellationTokenSource cancel = new(); + cancel.Cancel(); + engine.SetValue("token", cancel.Token); + engine.SetValue("callable", Callable); + engine.SetValue("assert", new Action(Assert.True)); + var result = engine.Evaluate("callable(token).then(_ => assert(false)).catch(_ => assert(true))"); + result = result.UnwrapIfPromise(); + static async Task Callable(CancellationToken token) + { + await Task.FromCanceled(token); + } + } + + [Fact] + public void ShouldTaskCatchWhenThrowError() + { + Engine engine = new(); + engine.SetValue("callable", Callable); + engine.SetValue("assert", new Action(Assert.True)); + var result = engine.Evaluate("callable().then(_ => assert(false)).catch(_ => assert(true))"); + + static async Task Callable() + { + await Task.Delay(10); + throw new Exception(); + } + } + + [Fact] + public void ShouldTaskAwaitCurrentStack() + { + //https://github.com/sebastienros/jint/issues/514#issuecomment-1507127509 + Engine engine = new(); + string log = ""; + engine.SetValue("myAsyncMethod", new Func(async () => + { + await Task.Delay(1000); + log += "1"; + })); + engine.SetValue("myAsyncMethod2", new Action(() => + { + log += "2"; + })); + engine.Execute("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();"); + Assert.Equal("12", log); + } } diff --git a/Jint/Runtime/Interop/DelegateWrapper.cs b/Jint/Runtime/Interop/DelegateWrapper.cs index 4a33e324a0..36bd49b898 100644 --- a/Jint/Runtime/Interop/DelegateWrapper.cs +++ b/Jint/Runtime/Interop/DelegateWrapper.cs @@ -130,13 +130,65 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArgumen try { - return FromObject(Engine, _d.DynamicInvoke(parameters)); + var result = _d.DynamicInvoke(parameters); + if (result is not Task task) + { + return FromObject(Engine, result); + } + return ConvertTaskToPromise(task); } catch (TargetInvocationException exception) { - ExceptionHelper.ThrowMeaningfulException(_engine, exception); + ExceptionHelper.ThrowMeaningfulException(Engine, exception); throw; } } + + private JsValue ConvertTaskToPromise(Task task) + { + var (promise, resolve, reject) = Engine.RegisterPromise(); + task = task.ContinueWith(continuationAction => + { + if (continuationAction.IsFaulted) + { + reject(FromObject(Engine, continuationAction.Exception)); + } + else if (continuationAction.IsCanceled) + { + reject(FromObject(Engine, new ExecutionCanceledException())); + } + else + { + // Special case: Marshal `async Task` as undefined, as this is `Task` at runtime + // See https://github.com/sebastienros/jint/pull/1567#issuecomment-1681987702 + if (Task.CompletedTask.Equals(continuationAction)) + { + resolve(FromObject(Engine, JsValue.Undefined)); + return; + } + + var result = continuationAction.GetType().GetProperty(nameof(Task.Result)); + if (result is not null) + { + resolve(FromObject(Engine, result.GetValue(continuationAction))); + } + else + { + resolve(FromObject(Engine, JsValue.Undefined)); + } + } + }); + + Engine.AddToEventLoop(() => + { + if (!task.IsCompleted) + { + // Task.Wait has the potential of inlining the task's execution on the current thread; avoid this. + ((IAsyncResult) task).AsyncWaitHandle.WaitOne(); + } + }); + + return promise; + } } } From 48d512f567cc2e5305c0200fe0ad14dc15504229 Mon Sep 17 00:00:00 2001 From: viruscamp Date: Fri, 13 Oct 2023 15:06:40 +0800 Subject: [PATCH 10/44] Improve Proxy conformance and support CLR interop better (#1645) provide detailed message for exceptions in JsProxy do not use ReferenceEquals on data property value comparing improve `JsValue.SameValue` to compare `ObjectWrapper` add a new test `ProxyHandlerGetDataPropertyShouldNotCheckClrType` for `JsString` and `ConcatenatedString` --- Jint.Tests/Runtime/ProxyTests.cs | 241 ++++++++++++++++++++++++++++++- Jint/Native/JsValue.cs | 15 +- Jint/Native/Proxy/JsProxy.cs | 36 +++-- 3 files changed, 277 insertions(+), 15 deletions(-) diff --git a/Jint.Tests/Runtime/ProxyTests.cs b/Jint.Tests/Runtime/ProxyTests.cs index 25d71f3bbb..3685956996 100644 --- a/Jint.Tests/Runtime/ProxyTests.cs +++ b/Jint.Tests/Runtime/ProxyTests.cs @@ -1,4 +1,7 @@ -namespace Jint.Tests.Runtime; +using Jint.Native.Error; +using Jint.Runtime; + +namespace Jint.Tests.Runtime; public class ProxyTests { @@ -183,4 +186,240 @@ public void ConstructHandlerInvariant() Assert.True(_engine.Evaluate(Script).AsBoolean()); } + + [Fact] + public void ProxyHandlerGetDataPropertyShouldNotUseReferenceEquals() + { + // There are two JsString which should be treat as same value, + // but they are not ReferenceEquals. + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + configurable: false, + value: 'in', + }); + const handler = { + get(target, property, receiver) { + return 'Jint'.substring(1,3); + } + }; + let p = new Proxy(o, handler); + let pv = p.value; + """); + } + + [Fact] + public void ProxyHandlerGetDataPropertyShouldNotCheckClrType() + { + // There are a JsString and a ConcatenatedString which should be treat as same value, + // but they are different CLR Type. + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + configurable: false, + value: 'Jint', + }); + const handler = { + get(target, property, receiver) { + return 'Ji'.concat('nt'); + } + }; + let p = new Proxy(o, handler); + let pv = p.value; + """); + } + + class TestClass + { + public static readonly TestClass Instance = new TestClass(); + public string StringValue => "StringValue"; + public int IntValue => 42424242; // avoid small numbers cache + public TestClass ObjectWrapper => Instance; + } + + [Fact] + public void ProxyClrPropertyPrimitiveString() + { + _engine.SetValue("testClass", TestClass.Instance); + var result = _engine.Evaluate(""" + const handler = { + get(target, property, receiver) { + return Reflect.get(target, property, receiver); + } + }; + const p = new Proxy(testClass, handler); + return p.StringValue; + """); + Assert.Equal(TestClass.Instance.StringValue, result.AsString()); + } + + [Fact] + public void ProxyClrPropertyPrimitiveInt() + { + _engine.SetValue("testClass", TestClass.Instance); + var result = _engine.Evaluate(""" + const handler = { + get(target, property, receiver) { + return Reflect.get(target, property, receiver); + } + }; + const p = new Proxy(testClass, handler); + return p.IntValue; + """); + Assert.Equal(TestClass.Instance.IntValue, result.AsInteger()); + } + + [Fact] + public void ProxyClrPropertyObjectWrapper() + { + _engine.SetValue("testClass", TestClass.Instance); + var result = _engine.Evaluate(""" + const handler = { + get(target, property, receiver) { + return Reflect.get(target, property, receiver); + } + }; + const p = new Proxy(testClass, handler); + return p.ObjectWrapper; + """); + } + + private static ErrorPrototype TypeErrorPrototype(Engine engine) + => engine.Realm.Intrinsics.TypeError.PrototypeObject; + + private static void AssertJsTypeError(Engine engine, JavaScriptException ex, string msg) + { + Assert.Same(TypeErrorPrototype(engine), ex.Error.AsObject().Prototype); + Assert.Equal(msg, ex.Message); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get#invariants + // The value reported for a property must be the same as + // the value ofthe corresponding target object property, + // if the target object property is + // a non-writable, non-configurable own data property. + [Fact] + public void ProxyHandlerGetInvariantsDataPropertyReturnsDifferentValue() + { + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + writable: false, + configurable: false, + value: 42, + }); + const handler = { + get(target, property, receiver) { + return 32; + } + }; + let p = new Proxy(o, handler); + """); + var ex = Assert.Throws(() => _engine.Evaluate("p.value")); + AssertJsTypeError(_engine, ex, "'get' on proxy: property 'value' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '42' but got '32')"); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get#invariants + // The value reported for a property must be undefined, + // if the corresponding target object property is + // a non-configurable own accessor property + // that has undefined as its [[Get]] attribute. + [Fact] + public void ProxyHandlerGetInvariantsAccessorPropertyWithoutGetButReturnsValue() + { + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + configurable: false, + set() {}, + }); + const handler = { + get(target, property, receiver) { + return 32; + } + }; + let p = new Proxy(o, handler); + """); + var ex = Assert.Throws(() => _engine.Evaluate("p.value")); + AssertJsTypeError(_engine, ex, "'get' on proxy: property 'value' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '32')"); + } + + private const string ScriptProxyHandlerSetInvariantsDataPropertyImmutable = """ + let o = Object.defineProperty({}, 'value', { + writable: false, + configurable: false, + value: 42, + }); + const handler = { + set(target, property, value, receiver) { + return true; + } + }; + let p = new Proxy(o, handler); + """; + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set#invariants + // Cannot change the value of a property to be different from + // the value of the corresponding target object property, + // if the corresponding target object property is + // a non-writable, non-configurable data property. + [Fact] + public void ProxyHandlerSetInvariantsDataPropertyImmutableChangeValue() + { + _engine.Execute(ScriptProxyHandlerSetInvariantsDataPropertyImmutable); + var ex = Assert.Throws(() => _engine.Evaluate("p.value = 32")); + AssertJsTypeError(_engine, ex, "'set' on proxy: trap returned truish for property 'value' which exists in the proxy target as a non-configurable and non-writable data property with a different value"); + } + + [Fact] + public void ProxyHandlerSetInvariantsDataPropertyImmutableSetSameValue() + { + _engine.Execute(ScriptProxyHandlerSetInvariantsDataPropertyImmutable); + _engine.Evaluate("p.value = 42"); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set#invariants + // Cannot set the value of a property, + // if the corresponding target object property is + // a non-configurable accessor property + // that has undefined as its [[Set]] attribute. + [Fact] + public void ProxyHandlerSetInvariantsAccessorPropertyWithoutSetChange() + { + _engine.Execute(""" + let o = Object.defineProperty({}, 'value', { + configurable: false, + get() { return 42; }, + }); + const handler = { + set(target, property, value, receiver) { + return true; + } + }; + let p = new Proxy(o, handler); + """); + var ex = Assert.Throws(() => _engine.Evaluate("p.value = 42")); + AssertJsTypeError(_engine, ex, "'set' on proxy: trap returned truish for property 'value' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter"); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set#invariants + // In strict mode, a false return value from the set() handler + // will throw a TypeError exception. + [Fact] + public void ProxyHandlerSetInvariantsReturnsFalseInStrictMode() + { + var ex = Assert.Throws(() => _engine.Evaluate(""" + 'use strict'; + let p = new Proxy({}, { set: () => false }); + p.value = 42; + """)); + // V8: "'set' on proxy: trap returned falsish for property 'value'", + AssertJsTypeError(_engine, ex, "Cannot assign to read only property 'value' of [object Object]"); + } + + [Fact] + public void ProxyHandlerSetInvariantsReturnsFalseInNonStrictMode() + { + _engine.Evaluate(""" + // 'use strict'; + let p = new Proxy({}, { set: () => false }); + p.value = 42; + """); + } } diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 6499e0d58d..23cb199e8a 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -9,6 +9,7 @@ using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; +using Jint.Runtime.Interop; namespace Jint.Native { @@ -465,6 +466,11 @@ internal virtual bool OrdinaryHasInstance(JsValue v) internal static bool SameValue(JsValue x, JsValue y) { + if (ReferenceEquals(x, y)) + { + return true; + } + var typea = x.Type; var typeb = y.Type; @@ -510,8 +516,15 @@ internal static bool SameValue(JsValue x, JsValue y) return true; case Types.Symbol: return x == y; + case Types.Object: + if (x is ObjectWrapper xo && y is ObjectWrapper yo) + { + return ReferenceEquals(xo.Target, yo.Target) + && xo.ClrType == yo.ClrType; + } + return false; default: - return ReferenceEquals(x, y); + return false; } } diff --git a/Jint/Native/Proxy/JsProxy.cs b/Jint/Native/Proxy/JsProxy.cs index 463cf05529..1607632b32 100644 --- a/Jint/Native/Proxy/JsProxy.cs +++ b/Jint/Native/Proxy/JsProxy.cs @@ -2,6 +2,7 @@ using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; namespace Jint.Native.Proxy { @@ -134,13 +135,21 @@ public override JsValue Get(JsValue property, JsValue receiver) var targetDesc = target.GetOwnProperty(property); if (targetDesc != PropertyDescriptor.Undefined) { - if (targetDesc.IsDataDescriptor() && !targetDesc.Configurable && !targetDesc.Writable && !ReferenceEquals(result, targetDesc._value)) + if (targetDesc.IsDataDescriptor()) { - ExceptionHelper.ThrowTypeError(_engine.Realm); + var targetValue = targetDesc.Value; + if (!targetDesc.Configurable && !targetDesc.Writable && !SameValue(result, targetValue)) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, $"'get' on proxy: property '{property}' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '{targetValue}' but got '{result}')"); + } } - if (targetDesc.IsAccessorDescriptor() && !targetDesc.Configurable && (targetDesc.Get ?? Undefined).IsUndefined() && !result.IsUndefined()) + + if (targetDesc.IsAccessorDescriptor()) { - ExceptionHelper.ThrowTypeError(_engine.Realm, $"'get' on proxy: property '{property}' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '{result}')"); + if (!targetDesc.Configurable && (targetDesc.Get ?? Undefined).IsUndefined() && !result.IsUndefined()) + { + ExceptionHelper.ThrowTypeError(_engine.Realm, $"'get' on proxy: property '{property}' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '{result}')"); + } } } @@ -152,7 +161,7 @@ public override JsValue Get(JsValue property, JsValue receiver) /// public override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol) { - if (!TryCallHandler(TrapOwnKeys, new JsValue[] { _target }, out var result)) + if (!TryCallHandler(TrapOwnKeys, new[] { _target }, out var result)) { return _target.GetOwnPropertyKeys(types); } @@ -321,14 +330,15 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) return false; } - var targetDesc = _target.GetOwnProperty(property); + var targetDesc = _target.GetOwnProperty(property); if (targetDesc != PropertyDescriptor.Undefined) { if (targetDesc.IsDataDescriptor() && !targetDesc.Configurable && !targetDesc.Writable) { - if (targetDesc.Value != value) + var targetValue = targetDesc.Value; + if (!SameValue(targetValue, value)) { - ExceptionHelper.ThrowTypeError(_engine.Realm); + ExceptionHelper.ThrowTypeError(_engine.Realm, $"'set' on proxy: trap returned truish for property '{property}' which exists in the proxy target as a non-configurable and non-writable data property with a different value"); } } @@ -336,7 +346,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) { if ((targetDesc.Set ?? Undefined).IsUndefined()) { - ExceptionHelper.ThrowTypeError(_engine.Realm); + ExceptionHelper.ThrowTypeError(_engine.Realm, $"'set' on proxy: trap returned truish for property '{property}' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter"); } } } @@ -405,7 +415,7 @@ private static bool IsCompatiblePropertyDescriptor(bool extensible, PropertyDesc /// public override bool HasProperty(JsValue property) { - if (!TryCallHandler(TrapHas, new [] { _target, TypeConverter.ToPropertyKey(property) }, out var jsValue)) + if (!TryCallHandler(TrapHas, new[] { _target, TypeConverter.ToPropertyKey(property) }, out var jsValue)) { return _target.HasProperty(property); } @@ -474,7 +484,7 @@ public override bool Delete(JsValue property) /// public override bool PreventExtensions() { - if (!TryCallHandler(TrapPreventExtensions, new JsValue[] { _target }, out var result)) + if (!TryCallHandler(TrapPreventExtensions, new[] { _target }, out var result)) { return _target.PreventExtensions(); } @@ -496,7 +506,7 @@ public override bool Extensible { get { - if (!TryCallHandler(TrapIsExtensible, new JsValue[] { _target }, out var result)) + if (!TryCallHandler(TrapIsExtensible, new[] { _target }, out var result)) { return _target.Extensible; } @@ -516,7 +526,7 @@ public override bool Extensible /// protected internal override ObjectInstance? GetPrototypeOf() { - if (!TryCallHandler(TrapGetProtoTypeOf, new JsValue[] { _target }, out var handlerProto )) + if (!TryCallHandler(TrapGetProtoTypeOf, new[] { _target }, out var handlerProto)) { return _target.Prototype; } From e5d60cb58fdcbd84478b819e318b81d9ae8527a2 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Fri, 13 Oct 2023 08:48:44 +0100 Subject: [PATCH 11/44] Support converting JsTypedArray instances under interop (#1646) Also some utility extensions for float typed arrays. Added tests for changes. --- Jint.Tests/Runtime/EngineTests.cs | 15 +++++++++ Jint.Tests/Runtime/TypedArrayInteropTests.cs | 28 ++++++++++++++++ Jint/JsValueExtensions.cs | 34 ++++++++++++++++++++ Jint/Native/Object/ObjectInstance.cs | 22 +++++++++++++ 4 files changed, 99 insertions(+) diff --git a/Jint.Tests/Runtime/EngineTests.cs b/Jint.Tests/Runtime/EngineTests.cs index e4ec411d0f..d898f99a8e 100644 --- a/Jint.Tests/Runtime/EngineTests.cs +++ b/Jint.Tests/Runtime/EngineTests.cs @@ -3037,6 +3037,21 @@ public void ImportModuleShouldTriggerBeforeEvaluateEvents() Assert.Equal(2, beforeEvaluateTriggeredCount); } + [Fact] + public void ShouldConvertJsTypedArraysCorrectly() + { + var engine = new Engine(); + + var float32 = new float [] { 42f, 23 }; + + engine.SetValue("float32", float32); + engine.SetValue("testFloat32Array", new Action(v => Assert.Equal(v, float32))); + + engine.Evaluate(@" + testFloat32Array(new Float32Array(float32)); + "); + } + private static void TestBeforeEvaluateEvent(Action call, string expectedSource) { var engine = new Engine(); diff --git a/Jint.Tests/Runtime/TypedArrayInteropTests.cs b/Jint.Tests/Runtime/TypedArrayInteropTests.cs index 772936d11c..1fee21b382 100644 --- a/Jint.Tests/Runtime/TypedArrayInteropTests.cs +++ b/Jint.Tests/Runtime/TypedArrayInteropTests.cs @@ -120,6 +120,34 @@ public void CanInteropWithBigUint64() Assert.Equal(source, fromEngine.AsBigUint64Array()); } + [Fact] + public void CanInteropWithFloat32() + { + var engine = new Engine(); + var source = new float[] { 42f, 12f }; + + engine.SetValue("testSubject", engine.Realm.Intrinsics.Float32Array.Construct(source)); + ValidateCreatedTypeArray(engine, "Float32Array"); + + var fromEngine = engine.GetValue("testSubject"); + Assert.True(fromEngine.IsFloat32Array()); + Assert.Equal(source, fromEngine.AsFloat32Array()); + } + + [Fact] + public void CanInteropWithFloat64() + { + var engine = new Engine(); + var source = new double[] { 42f, 12f }; + + engine.SetValue("testSubject", engine.Realm.Intrinsics.Float64Array.Construct(source)); + ValidateCreatedTypeArray(engine, "Float64Array"); + + var fromEngine = engine.GetValue("testSubject"); + Assert.True(fromEngine.IsFloat64Array()); + Assert.Equal(source, fromEngine.AsFloat64Array()); + } + private static void ValidateCreatedTypeArray(Engine engine, string arrayName) { Assert.Equal(arrayName, engine.Evaluate("testSubject.constructor.name").AsString()); diff --git a/Jint/JsValueExtensions.cs b/Jint/JsValueExtensions.cs index ab861f194a..74a9c708c3 100644 --- a/Jint/JsValueExtensions.cs +++ b/Jint/JsValueExtensions.cs @@ -391,6 +391,40 @@ public static ulong[] AsBigUint64Array(this JsValue value) return ((JsTypedArray) value).ToNativeArray(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFloat32Array(this JsValue value) + { + return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float32 }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float[] AsFloat32Array(this JsValue value) + { + if (!value.IsFloat32Array()) + { + ThrowWrongTypeException(value, "Float32Array"); + } + + return ((JsTypedArray) value).ToNativeArray(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFloat64Array(this JsValue value) + { + return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float64 }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double[] AsFloat64Array(this JsValue value) + { + if (!value.IsFloat64Array()) + { + ThrowWrongTypeException(value, "Float64Array"); + } + + return ((JsTypedArray) value).ToNativeArray(); + } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index b95ab8fd64..4c7d7e8735 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -10,6 +10,7 @@ using Jint.Native.RegExp; using Jint.Native.String; using Jint.Native.Symbol; +using Jint.Native.TypedArray; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; @@ -1054,6 +1055,27 @@ private object ToObject(ObjectTraverseStack stack) converted = result; break; } + + if (this is JsTypedArray typedArrayInstance) + { + converted = typedArrayInstance._arrayElementType switch + { + TypedArrayElementType.Int8 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Int16 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Int32 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.BigInt64 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Float32 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Float64 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Uint8 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Uint8C => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Uint16 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.Uint32 => typedArrayInstance.ToNativeArray(), + TypedArrayElementType.BigUint64 => typedArrayInstance.ToNativeArray(), + _ => throw new ArgumentOutOfRangeException() + }; + + break; + } if (this is BigIntInstance bigIntInstance) { From 8ebdc342f686d12041899cc0a5541607d9530a98 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 14 Oct 2023 10:37:28 +0300 Subject: [PATCH 12/44] Optimize URI decode and encode (#1647) * dispatch via custom error signaling to remove costly throws * minimize allocations --- Jint.Tests.CommonScripts/SunSpiderTests.cs | 2 +- Jint/Engine.cs | 6 + Jint/Native/Global/GlobalObject.cs | 227 +++++++++++------- Jint/Runtime/ExceptionHelper.cs | 17 +- .../Interpreter/JintFunctionDefinition.cs | 3 +- Jint/Runtime/Interpreter/JintStatementList.cs | 17 ++ .../Statements/JintReturnStatement.cs | 3 +- 7 files changed, 172 insertions(+), 103 deletions(-) diff --git a/Jint.Tests.CommonScripts/SunSpiderTests.cs b/Jint.Tests.CommonScripts/SunSpiderTests.cs index 0c4d2e27c7..2025738ade 100644 --- a/Jint.Tests.CommonScripts/SunSpiderTests.cs +++ b/Jint.Tests.CommonScripts/SunSpiderTests.cs @@ -10,7 +10,7 @@ private static void RunTest(string source) { var engine = new Engine() .SetValue("log", new Action(Console.WriteLine)) - .SetValue("assert", new Action((condition, message) => Assert.True(condition, message))); + .SetValue("assert", new Action((condition, message) => Assert.That(condition, message))); try { diff --git a/Jint/Engine.cs b/Jint/Engine.cs index b974a902b0..be39814a89 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -34,6 +34,7 @@ public sealed partial class Engine : IDisposable private readonly ExecutionContextStack _executionContexts; private JsValue _completionValue = JsValue.Undefined; internal EvaluationContext? _activeEvaluationContext; + internal ErrorDispatchInfo? _error; private readonly EventLoop _eventLoop = new(); @@ -1554,6 +1555,11 @@ private ObjectInstance Construct( return result; } + internal void SignalError(ErrorDispatchInfo error) + { + _error = error; + } + public void Dispose() { if (_objectWrapperCache is null) diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index cd931e2f19..c3d053d956 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -272,21 +272,10 @@ public static JsValue IsFinite(JsValue thisObject, JsValue[] arguments) return true; } - private static readonly HashSet UriReserved = new HashSet - { - ';', '/', '?', ':', '@', '&', '=', '+', '$', ',' - }; - - private static readonly HashSet UriUnescaped = new HashSet - { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', '.', '!', - '~', '*', '\'', '(', ')' - }; - - private static readonly HashSet UnescapedUriSet = new HashSet(UriReserved.Concat(UriUnescaped).Concat(new[] { '#' })); - private static readonly HashSet ReservedUriSet = new HashSet(UriReserved.Concat(new[] { '#' })); + private static readonly string UriReserved = new (new [] { ';', '/', '?', ':', '@', '&', '=', '+', '$', ',' }); + private static readonly string UriUnescaped = new(new [] { '-', '_', '.', '!', '~', '*', '\'', '(', ')' }); + private static readonly string UnescapedUriSet = UriReserved + UriUnescaped + '#'; + private static readonly string ReservedUriSet = UriReserved + '#'; private const string HexaMap = "0123456789ABCDEF"; @@ -320,17 +309,18 @@ public JsValue EncodeUriComponent(JsValue thisObject, JsValue[] arguments) return Encode(uriString, UriUnescaped); } - private string Encode(string uriString, HashSet unescapedUriSet) + private JsValue Encode(string uriString, string unescapedUriSet) { var strLen = uriString.Length; _stringBuilder.EnsureCapacity(uriString.Length); _stringBuilder.Clear(); + var buffer = new byte[4]; for (var k = 0; k < strLen; k++) { var c = uriString[k]; - if (unescapedUriSet != null && unescapedUriSet.Contains(c)) + if (c is >= 'a' and <= 'z' || c is >= 'A' and <= 'Z' || c is >= '0' and <= '9' || unescapedUriSet.IndexOf(c) != -1) { _stringBuilder.Append(c); } @@ -338,7 +328,7 @@ private string Encode(string uriString, HashSet unescapedUriSet) { if (c >= 0xDC00 && c <= 0xDBFF) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } int v; @@ -351,70 +341,58 @@ private string Encode(string uriString, HashSet unescapedUriSet) k++; if (k == strLen) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - var kChar = (int)uriString[k]; - if (kChar < 0xDC00 || kChar > 0xDFFF) + var kChar = (int) uriString[k]; + if (kChar is < 0xDC00 or > 0xDFFF) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } v = (c - 0xD800) * 0x400 + (kChar - 0xDC00) + 0x10000; } - byte[] octets = System.Array.Empty(); - - if (v >= 0 && v <= 0x007F) - { - // 00000000 0zzzzzzz -> 0zzzzzzz - octets = new[] { (byte)v }; - } - else if (v <= 0x07FF) - { - // 00000yyy yyzzzzzz -> 110yyyyy ; 10zzzzzz - octets = new[] - { - (byte)(0xC0 | (v >> 6)), - (byte)(0x80 | (v & 0x3F)) - }; - } - else if (v <= 0xD7FF) - { - // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz - octets = new[] - { - (byte)(0xE0 | (v >> 12)), - (byte)(0x80 | ((v >> 6) & 0x3F)), - (byte)(0x80 | (v & 0x3F)) - }; - } - else if (v <= 0xDFFF) - { - ExceptionHelper.ThrowUriError(_realm); - } - else if (v <= 0xFFFF) + var length = 1; + switch (v) { - octets = new[] - { - (byte) (0xE0 | (v >> 12)), - (byte) (0x80 | ((v >> 6) & 0x3F)), - (byte) (0x80 | (v & 0x3F)) - }; - } - else - { - octets = new[] - { - (byte) (0xF0 | (v >> 18)), - (byte) (0x80 | (v >> 12 & 0x3F)), - (byte) (0x80 | (v >> 6 & 0x3F)), - (byte) (0x80 | (v >> 0 & 0x3F)) - }; + case >= 0 and <= 0x007F: + // 00000000 0zzzzzzz -> 0zzzzzzz + buffer[0] = (byte) v; + break; + case <= 0x07FF: + // 00000yyy yyzzzzzz -> 110yyyyy ; 10zzzzzz + length = 2; + buffer[0] = (byte) (0xC0 | (v >> 6)); + buffer[1] = (byte) (0x80 | (v & 0x3F)); + break; + case <= 0xD7FF: + // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz + length = 3; + buffer[0] = (byte) (0xE0 | (v >> 12)); + buffer[1] = (byte) (0x80 | ((v >> 6) & 0x3F)); + buffer[2] = (byte) (0x80 | (v & 0x3F)); + break; + case <= 0xDFFF: + goto uriError; + case <= 0xFFFF: + length = 3; + buffer[0] = (byte) (0xE0 | (v >> 12)); + buffer[1] = (byte) (0x80 | ((v >> 6) & 0x3F)); + buffer[2] = (byte) (0x80 | (v & 0x3F)); + break; + default: + length = 4; + buffer[0] = (byte) (0xF0 | (v >> 18)); + buffer[1] = (byte) (0x80 | (v >> 12 & 0x3F)); + buffer[2] = (byte) (0x80 | (v >> 6 & 0x3F)); + buffer[3] = (byte) (0x80 | (v >> 0 & 0x3F)); + break; } - foreach (var octet in octets) + for (var i = 0; i < length; i++) { + var octet = buffer[i]; var x1 = HexaMap[octet / 16]; var x2 = HexaMap[octet % 16]; _stringBuilder.Append('%').Append(x1).Append(x2); @@ -423,6 +401,10 @@ private string Encode(string uriString, HashSet unescapedUriSet) } return _stringBuilder.ToString(); + +uriError: + _engine.SignalError(ExceptionHelper.CreateUriError(_realm, "URI malformed")); + return null!; } public JsValue DecodeUri(JsValue thisObject, JsValue[] arguments) @@ -439,14 +421,18 @@ public JsValue DecodeUriComponent(JsValue thisObject, JsValue[] arguments) return Decode(componentString, null); } - private string Decode(string uriString, HashSet? reservedSet) + private JsValue Decode(string uriString, string? reservedSet) { var strLen = uriString.Length; _stringBuilder.EnsureCapacity(strLen); _stringBuilder.Clear(); - var octets = System.Array.Empty(); +#if SUPPORTS_SPAN_PARSE + Span octets = stackalloc byte[4]; +#else + var octets = new byte[4]; +#endif for (var k = 0; k < strLen; k++) { @@ -460,21 +446,23 @@ private string Decode(string uriString, HashSet? reservedSet) var start = k; if (k + 2 >= strLen) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - if (!IsValidHexaChar(uriString[k + 1]) || !IsValidHexaChar(uriString[k + 2])) + var c1 = uriString[k + 1]; + var c2 = uriString[k + 2]; + if (!IsValidHexaChar(c1) || !IsValidHexaChar(c2)) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - var B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16); + var B = StringToIntBase16(uriString.AsSpan(k + 1, 2)); k += 2; if ((B & 0x80) == 0) { C = (char)B; - if (reservedSet == null || !reservedSet.Contains(C)) + if (reservedSet == null || reservedSet.IndexOf(C) == -1) { _stringBuilder.Append(C); } @@ -486,22 +474,18 @@ private string Decode(string uriString, HashSet? reservedSet) else { var n = 0; - for (; ((B << n) & 0x80) != 0; n++) ; + for (; ((B << n) & 0x80) != 0; n++); if (n == 1 || n > 4) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - octets = octets.Length == n - ? octets - : new byte[n]; - octets[0] = B; if (k + (3 * (n - 1)) >= strLen) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } for (var j = 1; j < n; j++) @@ -509,20 +493,22 @@ private string Decode(string uriString, HashSet? reservedSet) k++; if (uriString[k] != '%') { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - if (!IsValidHexaChar(uriString[k + 1]) || !IsValidHexaChar(uriString[k + 2])) + c1 = uriString[k + 1]; + c2 = uriString[k + 2]; + if (!IsValidHexaChar(c1) || !IsValidHexaChar(c2)) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } - B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16); + B = StringToIntBase16(uriString.AsSpan(k + 1, 2)); // B & 11000000 != 10000000 if ((B & 0xC0) != 0x80) { - ExceptionHelper.ThrowUriError(_realm); + goto uriError; } k += 2; @@ -530,12 +516,73 @@ private string Decode(string uriString, HashSet? reservedSet) octets[j] = B; } - _stringBuilder.Append(Encoding.UTF8.GetString(octets, 0, octets.Length)); +#if SUPPORTS_SPAN_PARSE + _stringBuilder.Append(Encoding.UTF8.GetString(octets.Slice(0, n))); +#else + _stringBuilder.Append(Encoding.UTF8.GetString(octets, 0, n)); +#endif } } } return _stringBuilder.ToString(); + +uriError: + _engine.SignalError(ExceptionHelper.CreateUriError(_realm, "URI malformed")); + return null!; + } + + private static byte StringToIntBase16(ReadOnlySpan s) + { + var i = 0; + var length = s.Length; + + if (s[i] == '+') + { + i++; + } + + if (i + 1 < length && s[i] == '0') + { + if (s[i + 1] == 'x' || s[i + 1] == 'X') + { + i += 2; + } + } + + uint result = 0; + while (i < s.Length && IsDigit(s[i], 16, out var value)) + { + result = result * 16 + (uint) value; + i++; + } + + return (byte) (int) result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsDigit(char c, int radix, out int result) + { + int tmp; + if ((uint)(c - '0') <= 9) + { + result = tmp = c - '0'; + } + else if ((uint)(c - 'A') <= 'Z' - 'A') + { + result = tmp = c - 'A' + 10; + } + else if ((uint)(c - 'a') <= 'z' - 'a') + { + result = tmp = c - 'a' + 10; + } + else + { + result = -1; + return false; + } + + return tmp < radix; } /// diff --git a/Jint/Runtime/ExceptionHelper.cs b/Jint/Runtime/ExceptionHelper.cs index 14eaee8130..68f02d9880 100644 --- a/Jint/Runtime/ExceptionHelper.cs +++ b/Jint/Runtime/ExceptionHelper.cs @@ -11,6 +11,11 @@ namespace Jint.Runtime { + /// + /// Wraps known runtime type error information. + /// + internal sealed record ErrorDispatchInfo(ErrorConstructor ErrorConstructor, string? Message = null); + internal static class ExceptionHelper { [DoesNotReturn] @@ -77,11 +82,9 @@ public static void ThrowRangeError(Realm realm, string? message = null) throw new JavaScriptException(realm.Intrinsics.RangeError, message).SetJavaScriptLocation(location); } - [DoesNotReturn] - public static void ThrowUriError(Realm realm) + public static ErrorDispatchInfo CreateUriError(Realm realm, string message) { - var location = realm.GlobalObject.Engine.GetLastSyntaxElement()?.Location ?? default; - throw new JavaScriptException(realm.Intrinsics.UriError).SetJavaScriptLocation(location); + return new ErrorDispatchInfo(realm.Intrinsics.UriError, message); } [DoesNotReturn] @@ -132,12 +135,6 @@ public static void ThrowPromiseRejectedException(JsValue error) throw new PromiseRejectedException(error); } - [DoesNotReturn] - public static void ThrowJavaScriptException(JsValue value) - { - throw new JavaScriptException(value); - } - [DoesNotReturn] public static void ThrowJavaScriptException(Engine engine, JsValue value, in Completion result) { diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index f68388eae6..7903f5ba10 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -48,7 +48,8 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun AsyncFunctionStart(context, promiseCapability, context => { context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList); - return new Completion(CompletionType.Return, _bodyExpression.GetValue(context), _bodyExpression._expression); + var jsValue = _bodyExpression.GetValue(context).Clone(); + return new Completion(CompletionType.Return, jsValue, _bodyExpression._expression); }); result = new Completion(CompletionType.Return, promiseCapability.PromiseInstance, Function.Body); } diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index 74f84a3e90..52bd55b0a0 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -92,6 +92,10 @@ public Completion Execute(EvaluationContext context) if (c.Value is null) { c = s.Execute(context); + if (context.Engine._error is not null) + { + return HandleError(context.Engine, s); + } } if (c.Type != CompletionType.Normal) @@ -138,6 +142,19 @@ private static Completion HandleException(EvaluationContext context, Exception e throw exception; } + private static Completion HandleError(Engine engine, JintStatement? s) + { + var error = engine._error!; + engine._error = null; + return CreateThrowCompletion(error.ErrorConstructor, error.Message, engine._lastSyntaxElement ?? s!._statement); + } + + private static Completion CreateThrowCompletion(ErrorConstructor errorConstructor, string? message, SyntaxElement s) + { + var error = errorConstructor.Construct(message); + return new Completion(CompletionType.Throw, error, s); + } + private static Completion CreateThrowCompletion(ErrorConstructor errorConstructor, Exception e, SyntaxElement s) { var error = errorConstructor.Construct(e.Message); diff --git a/Jint/Runtime/Interpreter/Statements/JintReturnStatement.cs b/Jint/Runtime/Interpreter/Statements/JintReturnStatement.cs index b71b8181cd..26e170fbcb 100644 --- a/Jint/Runtime/Interpreter/Statements/JintReturnStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintReturnStatement.cs @@ -24,6 +24,7 @@ protected override void Initialize(EvaluationContext context) protected override Completion ExecuteInternal(EvaluationContext context) { - return new Completion(CompletionType.Return, _argument?.GetValue(context) ?? JsValue.Undefined, _statement); + var value = _argument is not null ? _argument.GetValue(context).Clone() : JsValue.Undefined; + return new Completion(CompletionType.Return, value, _statement); } } From 89a1b615f6b8f77367765df8099392ac52afeda2 Mon Sep 17 00:00:00 2001 From: viruscamp Date: Tue, 17 Oct 2023 00:23:47 +0800 Subject: [PATCH 13/44] PropertyDescriptor improvements (#1648) * PropertyDescriptor improvements add PropertyFlag.NonData now PropertyDescriptor's for Clr Field, Property and Indexer are accessor property descriptors * add a test ClrPropertySideEffect to illustrate what the last commit for * minor format changes --- Jint.Tests/Runtime/PropertyDescriptorTests.cs | 353 ++++++++++++++++++ Jint.Tests/Runtime/ProxyTests.cs | 21 ++ .../Runtime/TestClasses/IndexerProperty.cs | 49 +++ Jint/Native/Argument/ArgumentsInstance.cs | 2 +- Jint/Native/Function/FunctionPrototype.cs | 4 +- .../Native/Function/ScriptFunctionInstance.cs | 2 +- .../Descriptors/GetSetPropertyDescriptor.cs | 11 +- .../Runtime/Descriptors/PropertyDescriptor.cs | 8 +- Jint/Runtime/Descriptors/PropertyFlag.cs | 3 + .../Specialized/ClrAccessDescriptor.cs | 1 + .../Specialized/LazyPropertyDescriptor.cs | 1 + .../Specialized/ReflectionDescriptor.cs | 44 ++- .../Interop/Reflection/IndexerAccessor.cs | 2 + .../Interop/Reflection/MethodAccessor.cs | 9 +- .../Interop/Reflection/NestedTypeAccessor.cs | 12 +- .../Interop/Reflection/PropertyAccessor.cs | 2 + .../Interop/Reflection/ReflectionAccessor.cs | 2 + 17 files changed, 493 insertions(+), 33 deletions(-) create mode 100644 Jint.Tests/Runtime/PropertyDescriptorTests.cs create mode 100644 Jint.Tests/Runtime/TestClasses/IndexerProperty.cs diff --git a/Jint.Tests/Runtime/PropertyDescriptorTests.cs b/Jint.Tests/Runtime/PropertyDescriptorTests.cs new file mode 100644 index 0000000000..1561a29869 --- /dev/null +++ b/Jint.Tests/Runtime/PropertyDescriptorTests.cs @@ -0,0 +1,353 @@ +using Jint.Native; +using Jint.Native.Argument; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Descriptors.Specialized; +using Jint.Runtime.Interop; +using Jint.Tests.TestClasses; + +namespace Jint.Tests.Runtime; + +public class PropertyDescriptorTests +{ + public class TestClass + { + public static readonly TestClass Instance = new TestClass(); + public string Method() => "Method"; + public class NestedType { } + + public readonly int fieldReadOnly = 8; + public int field = 42; + + public string PropertyReadOnly => "PropertyReadOnly"; + public string PropertyWriteOnly { set { } } + public string PropertyReadWrite { get; set; } = "PropertyReadWrite"; + + public IndexedPropertyReadOnly IndexerReadOnly { get; } + = new((idx) => 42); + public IndexedPropertyWriteOnly IndexerWriteOnly { get; } + = new((idx, v) => { }); + public IndexedProperty IndexerReadWrite { get; } + = new((idx) => 42, (idx, v) => { }); + } + + private readonly Engine _engine; + + private readonly bool checkType = true; + + public PropertyDescriptorTests() + { + _engine = new Engine(cfg => cfg.AllowClr( + typeof(TestClass).Assembly, + typeof(Console).Assembly, + typeof(File).Assembly)) + .SetValue("log", new Action(Console.WriteLine)) + .SetValue("assert", new Action(Assert.True)) + .SetValue("equal", new Action(Assert.Equal)) + .SetValue("testClass", TestClass.Instance) + ; + } + + [Fact] + public void PropertyDescriptorReadOnly() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + value: 42, + writable: false + }) + """).AsObject().GetOwnProperty("value"); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + Assert.Equal(false, pd.Writable); + Assert.Null(pd.Get); + Assert.Null(pd.Set); + } + + [Fact] + public void PropertyDescriptorReadWrite() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + value: 42, + writable: true + }) + """).AsObject().GetOwnProperty("value"); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + Assert.Equal(true, pd.Writable); + Assert.Null(pd.Get); + Assert.Null(pd.Set); + } + + [Fact] + public void UndefinedPropertyDescriptor() + { + var pd = PropertyDescriptor.Undefined; + // PropertyDescriptor.UndefinedPropertyDescriptor is private + //if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void AllForbiddenDescriptor() + { + var pd = _engine.Evaluate("Object.getPrototypeOf('s')").AsObject().GetOwnProperty("length"); + if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + } + + [Fact] + public void LazyPropertyDescriptor() + { + var pd = _engine.Evaluate("globalThis").AsObject().GetOwnProperty("decodeURI"); + if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + } + + [Fact] + public void ThrowerPropertyDescriptor() + { + var pd = _engine.Evaluate("Object.getPrototypeOf(function() {})").AsObject().GetOwnProperty("arguments"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void GetSetPropertyDescriptorGetOnly() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + get() {} + }) + """).AsObject().GetOwnProperty("value"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + Assert.NotNull(pd.Get); + Assert.Null(pd.Set); + } + + [Fact] + public void GetSetPropertyDescriptorSetOnly() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + set() {} + }) + """).AsObject().GetOwnProperty("value"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + Assert.Null(pd.Get); + Assert.NotNull(pd.Set); + } + + [Fact] + public void GetSetPropertyDescriptorGetSet() + { + var pd = _engine.Evaluate(""" + Object.defineProperty({}, 'value', { + get() {}, + set() {} + }) + """).AsObject().GetOwnProperty("value"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + Assert.NotNull(pd.Get); + Assert.NotNull(pd.Set); + } + + [Fact] + public void ClrAccessDescriptor() + { + JsValue ExtractClrAccessDescriptor(JsValue jsArugments) + { + var pd = ((ArgumentsInstance) jsArugments).ParameterMap.GetOwnProperty("0"); + return new ObjectWrapper(_engine, pd); + } + _engine.SetValue("ExtractClrAccessDescriptor", ExtractClrAccessDescriptor); + var pdobj = _engine.Evaluate(""" + (function(a) { + return ExtractClrAccessDescriptor(arguments); + })(42) + """); + var pd = (PropertyDescriptor) ((ObjectWrapper) pdobj).Target; + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void PropertyDescriptorMethod() + { + var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'Method')"); + CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("Method"); + // use PropertyDescriptor to wrap method directly + //if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + } + + [Fact] + public void PropertyDescriptorNestedType() + { + var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'NestedType')"); + CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("NestedType"); + // use PropertyDescriptor to wrap nested type directly + //if (checkType) Assert.IsType(pd); + Assert.Equal(false, pd.IsAccessorDescriptor()); + Assert.Equal(true, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorFieldReadOnly() + { + var pdField = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'fieldReadOnly')"); + CheckPropertyDescriptor(pdField, false, true, false, false, true, false); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("fieldReadOnly"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorField() + { + var pdField = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'field')"); + CheckPropertyDescriptor(pdField, false, true, true, false, true, true); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("field"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorPropertyReadOnly() + { + var pdPropertyReadOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyReadOnly')"); + CheckPropertyDescriptor(pdPropertyReadOnly, false, true, false, false, true, false); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyReadOnly"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorPropertyWriteOnly() + { + var pdPropertyWriteOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyWriteOnly')"); + CheckPropertyDescriptor(pdPropertyWriteOnly, false, true, true, false, false, true); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyWriteOnly"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorPropertyReadWrite() + { + var pdPropertyReadWrite = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyReadWrite')"); + CheckPropertyDescriptor(pdPropertyReadWrite, false, true, true, false, true, true); + + var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyReadWrite"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorIndexerReadOnly() + { + var pdIndexerReadOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerReadOnly, '1')"); + CheckPropertyDescriptor(pdIndexerReadOnly, false, true, false, false, true, false); + + var pd1 = _engine.Evaluate("testClass.IndexerReadOnly"); + var pd = pd1.AsObject().GetOwnProperty("1"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorIndexerWriteOnly() + { + var pdIndexerWriteOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerWriteOnly, '1')"); + CheckPropertyDescriptor(pdIndexerWriteOnly, false, true, true, false, false, true); + + var pd = _engine.Evaluate("testClass.IndexerWriteOnly").AsObject().GetOwnProperty("1"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + [Fact] + public void ReflectionDescriptorIndexerReadWrite() + { + var pdIndexerReadWrite = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerReadWrite, 1)"); + CheckPropertyDescriptor(pdIndexerReadWrite, false, true, true, false, true, true); + + var pd = _engine.Evaluate("testClass.IndexerReadWrite").AsObject().GetOwnProperty("1"); + if (checkType) Assert.IsType(pd); + Assert.Equal(true, pd.IsAccessorDescriptor()); + Assert.Equal(false, pd.IsDataDescriptor()); + } + + private void CheckPropertyDescriptor( + JsValue jsPropertyDescriptor, + bool configurable, + bool enumerable, + bool writable, + bool hasValue, + bool hasGet, + bool hasSet + ) + { + var pd = jsPropertyDescriptor.AsObject(); + + Assert.Equal(configurable, pd["configurable"].AsBoolean()); + Assert.Equal(enumerable, pd["enumerable"].AsBoolean()); + if (writable) + { + var writableActual = pd["writable"]; + if (!writableActual.IsUndefined()) + { + Assert.True(writableActual.AsBoolean()); + } + } + + Assert.Equal(hasValue, !pd["value"].IsUndefined()); + Assert.Equal(hasGet, !pd["get"].IsUndefined()); + Assert.Equal(hasSet, !pd["set"].IsUndefined()); + } + + [Fact] + public void DefinePropertyFromAccesorToData() + { + var pd = _engine.Evaluate(""" + let o = {}; + Object.defineProperty(o, 'foo', { + get() { return 1; }, + configurable: true + }); + Object.defineProperty(o, 'foo', { + value: 101 + }); + return Object.getOwnPropertyDescriptor(o, 'foo'); + """); + Assert.Equal(101, pd.AsObject().Get("value").AsInteger()); + CheckPropertyDescriptor(pd, true, false, false, true, false, false); + } +} diff --git a/Jint.Tests/Runtime/ProxyTests.cs b/Jint.Tests/Runtime/ProxyTests.cs index 3685956996..e569e8ae4f 100644 --- a/Jint.Tests/Runtime/ProxyTests.cs +++ b/Jint.Tests/Runtime/ProxyTests.cs @@ -233,6 +233,9 @@ class TestClass public string StringValue => "StringValue"; public int IntValue => 42424242; // avoid small numbers cache public TestClass ObjectWrapper => Instance; + + private int x = 1; + public int PropertySideEffect => x++; } [Fact] @@ -422,4 +425,22 @@ public void ProxyHandlerSetInvariantsReturnsFalseInNonStrictMode() p.value = 42; """); } + + [Fact] + public void ClrPropertySideEffect() + { + _engine.SetValue("testClass", TestClass.Instance); + _engine.Execute(""" + const handler = { + get(target, property, receiver) { + return 2; + } + }; + const p = new Proxy(testClass, handler); + """); + + Assert.Equal(1, TestClass.Instance.PropertySideEffect); // first call to PropertySideEffect + Assert.Equal(2, _engine.Evaluate("p.PropertySideEffect").AsInteger()); // no call to PropertySideEffect + Assert.Equal(2, TestClass.Instance.PropertySideEffect); // second call to PropertySideEffect + } } diff --git a/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs b/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs new file mode 100644 index 0000000000..899e3cfa51 --- /dev/null +++ b/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs @@ -0,0 +1,49 @@ +namespace Jint.Tests.TestClasses; + +public class IndexedProperty +{ + Action Setter { get; } + Func Getter { get; } + + public IndexedProperty(Func getter, Action setter) + { + Getter = getter; + Setter = setter; + } + + public TValue this[TIndex i] + { + get => Getter(i); + set => Setter(i, value); + } +} + +public class IndexedPropertyReadOnly +{ + Func Getter { get; } + + public IndexedPropertyReadOnly(Func getter) + { + Getter = getter; + } + + public TValue this[TIndex i] + { + get => Getter(i); + } +} + +public class IndexedPropertyWriteOnly +{ + Action Setter { get; } + + public IndexedPropertyWriteOnly(Action setter) + { + Setter = setter; + } + + public TValue this[TIndex i] + { + set => Setter(i, value); + } +} diff --git a/Jint/Native/Argument/ArgumentsInstance.cs b/Jint/Native/Argument/ArgumentsInstance.cs index 41d83967a9..3bbe3b7032 100644 --- a/Jint/Native/Argument/ArgumentsInstance.cs +++ b/Jint/Native/Argument/ArgumentsInstance.cs @@ -66,7 +66,7 @@ protected override void Initialize() CreateDataProperty(JsString.Create(i), val); } - DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.CustomJsValue)); + DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.None)); } else { diff --git a/Jint/Native/Function/FunctionPrototype.cs b/Jint/Native/Function/FunctionPrototype.cs index 7ea20ac7e5..8bc50f9460 100644 --- a/Jint/Native/Function/FunctionPrototype.cs +++ b/Jint/Native/Function/FunctionPrototype.cs @@ -34,8 +34,8 @@ protected override void Initialize() ["apply"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "apply", Apply, 2, lengthFlags), propertyFlags), ["call"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "call", CallImpl, 1, lengthFlags), propertyFlags), ["bind"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "bind", Bind, 1, lengthFlags), propertyFlags), - ["arguments"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue), - ["caller"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue) + ["arguments"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable), + ["caller"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable) }; SetProperties(properties); diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index c284bdf750..d794635ada 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -49,7 +49,7 @@ internal ScriptFunctionInstance( && function.Function is not ArrowFunctionExpression && !function.Function.Generator) { - SetProperty(KnownKeys.Arguments, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue)); + SetProperty(KnownKeys.Arguments, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(engine, PropertyFlag.Configurable)); SetProperty(KnownKeys.Caller, new PropertyDescriptor(Undefined, PropertyFlag.Configurable)); } } diff --git a/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs b/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs index 8a3b51ea07..76a576e4e1 100644 --- a/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs @@ -10,6 +10,7 @@ public sealed class GetSetPropertyDescriptor : PropertyDescriptor public GetSetPropertyDescriptor(JsValue? get, JsValue? set, bool? enumerable = null, bool? configurable = null) : base(null, writable: null, enumerable: enumerable, configurable: configurable) { + _flags |= PropertyFlag.NonData; _get = get; _set = set; } @@ -17,12 +18,18 @@ public GetSetPropertyDescriptor(JsValue? get, JsValue? set, bool? enumerable = n internal GetSetPropertyDescriptor(JsValue? get, JsValue? set, PropertyFlag flags) : base(null, flags) { + _flags |= PropertyFlag.NonData; + _flags &= ~PropertyFlag.WritableSet; + _flags &= ~PropertyFlag.Writable; _get = get; _set = set; } public GetSetPropertyDescriptor(PropertyDescriptor descriptor) : base(descriptor) { + _flags |= PropertyFlag.NonData; + _flags &= ~PropertyFlag.WritableSet; + _flags &= ~PropertyFlag.Writable; _get = descriptor.Get; _set = descriptor.Set; } @@ -45,8 +52,10 @@ internal sealed class ThrowerPropertyDescriptor : PropertyDescriptor private readonly Engine _engine; private JsValue? _thrower; - public ThrowerPropertyDescriptor(Engine engine, PropertyFlag flags) : base(flags) + public ThrowerPropertyDescriptor(Engine engine, PropertyFlag flags) + : base(flags | PropertyFlag.CustomJsValue) { + _flags |= PropertyFlag.NonData; _engine = engine; } diff --git a/Jint/Runtime/Descriptors/PropertyDescriptor.cs b/Jint/Runtime/Descriptors/PropertyDescriptor.cs index f9dcf6fe71..57543f2fca 100644 --- a/Jint/Runtime/Descriptors/PropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/PropertyDescriptor.cs @@ -21,7 +21,7 @@ public PropertyDescriptor() : this(PropertyFlag.None) [MethodImpl(MethodImplOptions.AggressiveInlining)] protected PropertyDescriptor(PropertyFlag flags) { - _flags = flags; + _flags = flags & ~PropertyFlag.NonData; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -357,7 +357,7 @@ public static JsValue FromPropertyDescriptor(Engine engine, PropertyDescriptor d if (desc.IsDataDescriptor()) { - properties["value"] = new PropertyDescriptor(desc.Value ?? JsValue.Undefined, PropertyFlag.ConfigurableEnumerableWritable); + properties["value"] = new PropertyDescriptor(desc.Value ?? JsValue.Undefined, PropertyFlag.ConfigurableEnumerableWritable); if (desc._flags != PropertyFlag.None || desc.WritableSet) { properties["writable"] = new PropertyDescriptor(desc.Writable, PropertyFlag.ConfigurableEnumerableWritable); @@ -392,6 +392,10 @@ public bool IsAccessorDescriptor() [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsDataDescriptor() { + if (_flags.HasFlag(PropertyFlag.NonData)) + { + return false; + } return (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0 || (_flags & PropertyFlag.CustomJsValue) != 0 && !ReferenceEquals(CustomValue, null) || !ReferenceEquals(_value, null); diff --git a/Jint/Runtime/Descriptors/PropertyFlag.cs b/Jint/Runtime/Descriptors/PropertyFlag.cs index ac1a6231e3..926d787ec0 100644 --- a/Jint/Runtime/Descriptors/PropertyFlag.cs +++ b/Jint/Runtime/Descriptors/PropertyFlag.cs @@ -16,6 +16,9 @@ public enum PropertyFlag // we can check for mutable binding and do some fast assignments MutableBinding = 512, + // mark PropertyDescriptor as non data to accelerate IsDataDescriptor and avoid the side effect of CustomValue + NonData = 1024, + // common helpers AllForbidden = ConfigurableSet | EnumerableSet | WritableSet, ConfigurableEnumerableWritable = Configurable | Enumerable | Writable, diff --git a/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs index 3dee3f8192..85f41a0340 100644 --- a/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs @@ -19,6 +19,7 @@ public ClrAccessDescriptor( string name) : base(value: null, PropertyFlag.Configurable) { + _flags |= PropertyFlag.NonData; _env = env; _engine = engine; _name = new EnvironmentRecord.BindingName(name); diff --git a/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs index 2d13df852f..e427546bad 100644 --- a/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs @@ -12,6 +12,7 @@ internal sealed class LazyPropertyDescriptor : PropertyDescriptor internal LazyPropertyDescriptor(object? state, Func resolver, PropertyFlag flags) : base(null, flags | PropertyFlag.CustomJsValue) { + _flags &= ~PropertyFlag.NonData; _state = state; _resolver = resolver; } diff --git a/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs index 4885b4f52f..abfe7b4bb5 100644 --- a/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs @@ -1,5 +1,6 @@ using System.Reflection; using Jint.Native; +using Jint.Runtime.Interop; using Jint.Runtime.Interop.Reflection; namespace Jint.Runtime.Descriptors.Specialized @@ -17,31 +18,46 @@ public ReflectionDescriptor( bool enumerable) : base((enumerable ? PropertyFlag.Enumerable : PropertyFlag.None) | PropertyFlag.CustomJsValue) { + _flags |= PropertyFlag.NonData; _engine = engine; _reflectionAccessor = reflectionAccessor; _target = target; - Writable = reflectionAccessor.Writable && engine.Options.Interop.AllowWrite; + + if (reflectionAccessor.Writable && engine.Options.Interop.AllowWrite) + { + Set = new SetterFunctionInstance(_engine, DoSet); + } + if (reflectionAccessor.Readable) + { + Get = new GetterFunctionInstance(_engine, DoGet); + } } + public override JsValue? Get { get; } + public override JsValue? Set { get; } + protected internal override JsValue? CustomValue { - get + get => DoGet(null); + set => DoSet(null, value); + } + + JsValue DoGet(JsValue? thisObj) + { + var value = _reflectionAccessor.GetValue(_engine, _target); + var type = _reflectionAccessor.MemberType; + return JsValue.FromObjectWithType(_engine, value, type); + } + void DoSet(JsValue? thisObj, JsValue? v) + { + try { - var value = _reflectionAccessor.GetValue(_engine, _target); - var type = _reflectionAccessor.MemberType; - return JsValue.FromObjectWithType(_engine, value, type); + _reflectionAccessor.SetValue(_engine, _target, v!); } - set + catch (TargetInvocationException exception) { - try - { - _reflectionAccessor.SetValue(_engine, _target, value!); - } - catch (TargetInvocationException exception) - { - ExceptionHelper.ThrowMeaningfulException(_engine, exception); - } + ExceptionHelper.ThrowMeaningfulException(_engine, exception); } } } diff --git a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs index 162f496117..2ecb0892d0 100644 --- a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs @@ -119,6 +119,8 @@ internal static bool TryFindIndexer( return false; } + public override bool Readable => _indexer.CanRead; + public override bool Writable => _indexer.CanWrite; protected override object? DoGetValue(object target) diff --git a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs index 3a7a8b80df..18d400ca9c 100644 --- a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs @@ -18,14 +18,9 @@ public MethodAccessor(Type targetType, string name, MethodDescriptor[] methods) public override bool Writable => false; - protected override object? DoGetValue(object target) - { - return null; - } + protected override object? DoGetValue(object target) => null; - protected override void DoSetValue(object target, object? value) - { - } + protected override void DoSetValue(object target, object? value) { } public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true) { diff --git a/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs b/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs index c4d7a66e65..6d68e40083 100644 --- a/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs @@ -1,3 +1,5 @@ +using Jint.Runtime.Descriptors; + namespace Jint.Runtime.Interop.Reflection; internal sealed class NestedTypeAccessor : ReflectionAccessor @@ -11,12 +13,12 @@ public NestedTypeAccessor(TypeReference typeReference, string name) : base(typeo public override bool Writable => false; - protected override object? DoGetValue(object target) - { - return _typeReference; - } + protected override object? DoGetValue(object target) => null; + + protected override void DoSetValue(object target, object? value) { } - protected override void DoSetValue(object target, object? value) + public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true) { + return new(_typeReference, PropertyFlag.AllForbidden); } } diff --git a/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs b/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs index 0c220ea2be..9f33768947 100644 --- a/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs @@ -15,6 +15,8 @@ public PropertyAccessor( _propertyInfo = propertyInfo; } + public override bool Readable => _propertyInfo.CanRead; + public override bool Writable => _propertyInfo.CanWrite; protected override object? DoGetValue(object target) diff --git a/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs b/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs index 233f6f05ed..b768f55b3e 100644 --- a/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs @@ -28,6 +28,8 @@ protected ReflectionAccessor( _indexer = indexer; } + public virtual bool Readable => true; + public abstract bool Writable { get; } protected abstract object? DoGetValue(object target); From f4e0ce3f4ccea669388ec099d1e926c4b087ed60 Mon Sep 17 00:00:00 2001 From: Alexander Marek Date: Mon, 16 Oct 2023 18:44:48 +0200 Subject: [PATCH 14/44] Add convenience API methods for ShadowRealm (#1574) --- Jint.Benchmark/ShadowRealmBenchmark.cs | 61 +++++++++++++++++ .../ShadowRealmTests.cs | 65 +++++++++++++++++++ Jint/Native/ShadowRealm/ShadowRealm.cs | 65 +++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 Jint.Benchmark/ShadowRealmBenchmark.cs diff --git a/Jint.Benchmark/ShadowRealmBenchmark.cs b/Jint.Benchmark/ShadowRealmBenchmark.cs new file mode 100644 index 0000000000..754a4916a2 --- /dev/null +++ b/Jint.Benchmark/ShadowRealmBenchmark.cs @@ -0,0 +1,61 @@ +using BenchmarkDotNet.Attributes; +using Esprima.Ast; + +namespace Jint.Benchmark; + +[MemoryDiagnoser] +[BenchmarkCategory("ShadowRealm")] +public class ShadowRealmBenchmark +{ + private const string sourceCode = @" +(function (){return 'some string'})(); +"; + + private Engine engine; + private Script parsedScript; + + [GlobalSetup] + public void Setup() + { + engine = new Engine(); + parsedScript = Engine.PrepareScript(sourceCode); + } + + [Benchmark] + public void ReusingEngine() + { + engine.Evaluate(sourceCode); + } + + [Benchmark] + public void NewEngineInstance() + { + new Engine().Evaluate(sourceCode); + } + + [Benchmark] + public void ShadowRealm() + { + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.Evaluate(sourceCode); + } + + [Benchmark] + public void ReusingEngine_ParsedScript() + { + engine.Evaluate(parsedScript); + } + + [Benchmark] + public void NewEngineInstance_ParsedScript() + { + new Engine().Evaluate(parsedScript); + } + + [Benchmark] + public void ShadowRealm_ParsedScript() + { + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.Evaluate(parsedScript); + } +} diff --git a/Jint.Tests.PublicInterface/ShadowRealmTests.cs b/Jint.Tests.PublicInterface/ShadowRealmTests.cs index ca98ac163e..1740def1c5 100644 --- a/Jint.Tests.PublicInterface/ShadowRealmTests.cs +++ b/Jint.Tests.PublicInterface/ShadowRealmTests.cs @@ -1,3 +1,4 @@ +using Jint.Native; using Jint.Native.Object; namespace Jint.Tests.PublicInterface; @@ -28,6 +29,70 @@ public void CanUseViaEngineMethods() Assert.Equal("John Doe", result); } + [Fact] + public void MultipleShadowRealmsDoNotInterfere() + { + var engine = new Engine(options => options.EnableModules(GetBasePath())); + engine.SetValue("message", "world"); + engine.Evaluate("function hello() {return message}"); + + Assert.Equal("world",engine.Evaluate("hello();")); + + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.SetValue("message", "realm 1"); + shadowRealm.Evaluate("function hello() {return message}"); + + var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm2.SetValue("message", "realm 2"); + shadowRealm2.Evaluate("function hello() {return message}"); + + // Act & Assert + Assert.Equal("realm 1", shadowRealm.Evaluate("hello();")); + Assert.Equal("realm 2", shadowRealm2.Evaluate("hello();")); + } + + [Fact] + public void MultipleShadowRealm_SettingGlobalVariable_DoNotInterfere() + { + var engine = new Engine(options => options.EnableModules(GetBasePath())); + engine.SetValue("message", "hello "); + engine.Evaluate("(function hello() {message += \"engine\"})();"); + + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.SetValue("message", "hello "); + shadowRealm.Evaluate("(function hello() {message += \"realm 1\"})();"); + + var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm2.SetValue("message", "hello "); + shadowRealm2.Evaluate("(function hello() {message += \"realm 2\"})();"); + + // Act & Assert + Assert.Equal("hello engine", engine.Evaluate("message")); + Assert.Equal("hello realm 1", shadowRealm.Evaluate("message")); + Assert.Equal("hello realm 2", shadowRealm2.Evaluate("message")); + } + + [Fact] + public void CanReuseScriptWithShadowRealm() + { + var engine = new Engine(options => options.EnableModules(GetBasePath())); + engine.SetValue("message", "engine"); + + var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm.SetValue("message", "realm 1"); + + var shadowRealm2 = engine.Realm.Intrinsics.ShadowRealm.Construct(); + shadowRealm2.SetValue("message", "realm 2"); + + var parser = new Esprima.JavaScriptParser(); + var script = parser.ParseScript("(function hello() {return \"hello \" + message})();"); + + // Act & Assert + Assert.Equal("hello engine", engine.Evaluate(script)); + Assert.Equal("hello realm 1", shadowRealm.Evaluate(script)); + Assert.Equal("hello realm 2", shadowRealm2.Evaluate(script)); + } + private static string GetBasePath() { var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory); diff --git a/Jint/Native/ShadowRealm/ShadowRealm.cs b/Jint/Native/ShadowRealm/ShadowRealm.cs index 2ab25f2732..e9e9f20051 100644 --- a/Jint/Native/ShadowRealm/ShadowRealm.cs +++ b/Jint/Native/ShadowRealm/ShadowRealm.cs @@ -5,7 +5,9 @@ using Jint.Native.Object; using Jint.Native.Promise; using Jint.Runtime; +using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; +using Jint.Runtime.Interop; using Jint.Runtime.Interpreter; using Jint.Runtime.Interpreter.Statements; using Jint.Runtime.Modules; @@ -38,6 +40,12 @@ public JsValue Evaluate(string sourceText) return PerformShadowRealmEval(sourceText, callerRealm); } + public JsValue Evaluate(Script script) + { + var callerRealm = _engine.Realm; + return PerformShadowRealmEval(script, callerRealm); + } + public JsValue ImportValue(string specifier, string exportName) { var callerRealm = _engine.Realm; @@ -45,6 +53,47 @@ public JsValue ImportValue(string specifier, string exportName) _engine.RunAvailableContinuations(); return value; } + public ShadowRealm SetValue(string name, Delegate value) + { + _shadowRealm.GlobalObject.FastSetProperty(name, new PropertyDescriptor(new DelegateWrapper(_engine, value), true, false, true)); + return this; + } + + public ShadowRealm SetValue(string name, string value) + { + return SetValue(name, JsString.Create(value)); + } + + public ShadowRealm SetValue(string name, double value) + { + return SetValue(name, JsNumber.Create(value)); + } + + public ShadowRealm SetValue(string name, int value) + { + return SetValue(name, JsNumber.Create(value)); + } + + public ShadowRealm SetValue(string name, bool value) + { + return SetValue(name, value ? JsBoolean.True : JsBoolean.False); + } + + public ShadowRealm SetValue(string name, JsValue value) + { + _shadowRealm.GlobalObject.Set(name, value); + return this; + } + + public ShadowRealm SetValue(string name, object obj) + { + var value = obj is Type t + ? TypeReference.CreateTypeReference(_engine, t) + : JsValue.FromObject(_engine, obj); + + return SetValue(name, value); + } + /// /// https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval @@ -74,6 +123,22 @@ internal JsValue PerformShadowRealmEval(string sourceText, Realm callerRealm) return default; } + return PerformShadowRealmEvalInternal(script, callerRealm); + } + + internal JsValue PerformShadowRealmEval(Script script, Realm callerRealm) + { + var evalRealm = _shadowRealm; + + _engine._host.EnsureCanCompileStrings(callerRealm, evalRealm); + + return PerformShadowRealmEvalInternal(script, callerRealm); + } + + internal JsValue PerformShadowRealmEvalInternal(Script script, Realm callerRealm) + { + var evalRealm = _shadowRealm; + ref readonly var body = ref script.Body; if (body.Count == 0) { From 1a6aa38f1e1c6fc059d2980e378c670719c4e20c Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 21 Oct 2023 11:50:04 +0300 Subject: [PATCH 15/44] Implement Promise.withResolvers and update test suite (#1650) --- .../ShadowRealmTests.cs | 1 - .../Test262Harness.settings.json | 3 +- Jint/Native/Promise/PromiseConstructor.cs | 31 +++++++++++++------ Jint/Native/Proxy/JsProxy.cs | 1 - Jint/Native/RegExp/JsRegExp.cs | 2 ++ Jint/Native/RegExp/RegExpExtensions.cs | 25 ++------------- Jint/Native/RegExp/RegExpPrototype.cs | 27 +++++----------- README.md | 1 + 8 files changed, 36 insertions(+), 55 deletions(-) diff --git a/Jint.Tests.PublicInterface/ShadowRealmTests.cs b/Jint.Tests.PublicInterface/ShadowRealmTests.cs index 1740def1c5..f0d062d506 100644 --- a/Jint.Tests.PublicInterface/ShadowRealmTests.cs +++ b/Jint.Tests.PublicInterface/ShadowRealmTests.cs @@ -1,4 +1,3 @@ -using Jint.Native; using Jint.Native.Object; namespace Jint.Tests.PublicInterface; diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 88fa50bd24..f6ac59bf09 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -1,5 +1,5 @@ { - "SuiteGitSha": "9437cab774ab2f22c5cb971b11b8512eca705721", + "SuiteGitSha": "6396ebde0316639292530460d1ef961fd9bbe0d4", //"SuiteDirectory": "//mnt/c/work/test262", "TargetPath": "./Generated", "Namespace": "Jint.Tests.Test262", @@ -46,6 +46,7 @@ // RegExp handling problems "built-ins/RegExp/prototype/exec/S15.10.6.2_A1_T6.js", "language/literals/regexp/u-case-mapping.js", + "built-ins/RegExp/lookahead-quantifier-match-groups.js", // requires investigation how to process complex function name evaluation for property "built-ins/Function/prototype/toString/method-computed-property-name.js", diff --git a/Jint/Native/Promise/PromiseConstructor.cs b/Jint/Native/Promise/PromiseConstructor.cs index 63d7d3b568..56b227b4e6 100644 --- a/Jint/Native/Promise/PromiseConstructor.cs +++ b/Jint/Native/Promise/PromiseConstructor.cs @@ -21,8 +21,6 @@ internal sealed class PromiseConstructor : Constructor { private static readonly JsString _functionName = new JsString("Promise"); - internal PromisePrototype PrototypeObject { get; private set; } - internal PromiseConstructor( Engine engine, Realm realm, @@ -36,18 +34,21 @@ internal PromiseConstructor( _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } + internal PromisePrototype PrototypeObject { get; } + protected override void Initialize() { - const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; - const PropertyFlag lengthFlags = PropertyFlag.Configurable; + const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; + const PropertyFlag LengthFlags = PropertyFlag.Configurable; var properties = new PropertyDictionary(6, checkExistingKeys: false) { - ["resolve"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "resolve", Resolve, 1, lengthFlags), propertyFlags)), - ["reject"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "reject", Reject, 1, lengthFlags), propertyFlags)), - ["all"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "all", All, 1, lengthFlags), propertyFlags)), - ["allSettled"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "allSettled", AllSettled, 1, lengthFlags), propertyFlags)), - ["any"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "any", Any, 1, lengthFlags), propertyFlags)), - ["race"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "race", Race, 1, lengthFlags), propertyFlags)), + ["all"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "all", All, 1, LengthFlags), PropertyFlags)), + ["allSettled"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "allSettled", AllSettled, 1, LengthFlags), PropertyFlags)), + ["any"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "any", Any, 1, LengthFlags), PropertyFlags)), + ["race"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "race", Race, 1, LengthFlags), PropertyFlags)), + ["reject"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "reject", Reject, 1, LengthFlags), PropertyFlags)), + ["resolve"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "resolve", Resolve, 1, LengthFlags), PropertyFlags)), + ["withResolvers"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "withResolvers", WithResolvers , 0, LengthFlags), PropertyFlags)), }; SetProperties(properties); @@ -113,6 +114,16 @@ internal JsValue Resolve(JsValue thisObject, JsValue[] arguments) return PromiseResolve(thisObject, x); } + private JsValue WithResolvers(JsValue thisObject, JsValue[] arguments) + { + var promiseCapability = NewPromiseCapability(_engine, thisObject); + var obj = OrdinaryObjectCreate(_engine, _engine.Realm.Intrinsics.Object.PrototypeObject); + obj.CreateDataPropertyOrThrow("promise", promiseCapability.PromiseInstance); + obj.CreateDataPropertyOrThrow("resolve", promiseCapability.ResolveObj); + obj.CreateDataPropertyOrThrow("reject", promiseCapability.RejectObj); + return obj; + } + /// /// https://tc39.es/ecma262/#sec-promise-resolve /// diff --git a/Jint/Native/Proxy/JsProxy.cs b/Jint/Native/Proxy/JsProxy.cs index 1607632b32..386d70a695 100644 --- a/Jint/Native/Proxy/JsProxy.cs +++ b/Jint/Native/Proxy/JsProxy.cs @@ -2,7 +2,6 @@ using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; -using Jint.Runtime.Interop; namespace Jint.Native.Proxy { diff --git a/Jint/Native/RegExp/JsRegExp.cs b/Jint/Native/RegExp/JsRegExp.cs index 629fabae0f..62f5886cdd 100644 --- a/Jint/Native/RegExp/JsRegExp.cs +++ b/Jint/Native/RegExp/JsRegExp.cs @@ -74,6 +74,8 @@ public string Flags public bool FullUnicode { get; private set; } public bool UnicodeSets { get; private set; } + internal bool HasDefaultRegExpExec => Properties == null && Prototype is RegExpPrototype { HasDefaultExec: true }; + public override PropertyDescriptor GetOwnProperty(JsValue property) { if (property == PropertyLastIndex) diff --git a/Jint/Native/RegExp/RegExpExtensions.cs b/Jint/Native/RegExp/RegExpExtensions.cs index 927f8be297..aa27b6bb8e 100644 --- a/Jint/Native/RegExp/RegExpExtensions.cs +++ b/Jint/Native/RegExp/RegExpExtensions.cs @@ -1,26 +1,5 @@ -using System.Diagnostics.CodeAnalysis; -using Jint.Native.Object; +namespace Jint.Native.RegExp; -namespace Jint.Native.RegExp +internal static class RegExpExtensions { - internal static class RegExpExtensions - { - internal static bool TryGetDefaultRegExpExec(this ObjectInstance? o, [NotNullWhen(true)] out Func? exec) - { - if (o is RegExpPrototype prototype) - { - return prototype.TryGetDefaultExec(prototype, out exec); - } - - if (o is JsRegExp instance) - { - exec = default; - return instance.Properties == null - && TryGetDefaultRegExpExec(instance.Prototype, out exec); - } - - exec = default; - return false; - } - } } diff --git a/Jint/Native/RegExp/RegExpPrototype.cs b/Jint/Native/RegExp/RegExpPrototype.cs index 7912a64859..279f229a53 100644 --- a/Jint/Native/RegExp/RegExpPrototype.cs +++ b/Jint/Native/RegExp/RegExpPrototype.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Jint.Collections; using Jint.Native.Number; using Jint.Native.Object; @@ -158,7 +157,7 @@ private JsValue Replace(JsValue thisObject, JsValue[] arguments) if (!fullUnicode && !mayHaveNamedCaptures && !TypeConverter.ToBoolean(rx.Get(PropertySticky)) - && rx is JsRegExp rei && rei.TryGetDefaultRegExpExec(out _)) + && rx is JsRegExp rei && rei.HasDefaultRegExpExec) { var count = global ? int.MaxValue : 1; @@ -464,7 +463,7 @@ private JsValue Split(JsValue thisObject, JsValue[] arguments) return a; } - if (!unicodeMatching && rx is JsRegExp R && R.TryGetDefaultRegExpExec(out _)) + if (!unicodeMatching && rx is JsRegExp R && R.HasDefaultRegExpExec) { // we can take faster path @@ -690,7 +689,7 @@ private JsValue Match(JsValue thisObject, JsValue[] arguments) if (!fullUnicode && rx is JsRegExp rei - && rei.TryGetDefaultRegExpExec(out _)) + && rei.HasDefaultRegExpExec) { // fast path var a = _realm.Intrinsics.Array.ArrayCreate(0); @@ -812,8 +811,9 @@ private static ulong AdvanceStringIndex(string s, ulong index, bool unicode) internal static JsValue RegExpExec(ObjectInstance r, string s) { - var exec = r.Get(PropertyExec); - if (exec is ICallable callable) + var ri = r as JsRegExp; + + if ((ri is null || !ri.HasDefaultRegExpExec) && r.Get(PropertyExec) is ICallable callable) { var result = callable.Call(r, new JsValue[] { s }); if (!result.IsNull() && !result.IsObject()) @@ -824,7 +824,6 @@ internal static JsValue RegExpExec(ObjectInstance r, string s) return result; } - var ri = r as JsRegExp; if (ri is null) { ExceptionHelper.ThrowTypeError(r.Engine.Realm); @@ -833,17 +832,7 @@ internal static JsValue RegExpExec(ObjectInstance r, string s) return RegExpBuiltinExec(ri, s); } - internal bool TryGetDefaultExec(ObjectInstance o, [NotNullWhen((true))] out Func? exec) - { - if (o.Get(PropertyExec) is ClrFunctionInstance functionInstance && functionInstance._func == _defaultExec) - { - exec = _defaultExec; - return true; - } - - exec = default; - return false; - } + internal bool HasDefaultExec => Get(PropertyExec) is ClrFunctionInstance functionInstance && functionInstance._func == _defaultExec; /// /// https://tc39.es/ecma262/#sec-regexpbuiltinexec diff --git a/README.md b/README.md index edaaf5a8df..93113e6778 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ Following features are supported in version 3.x. #### ECMAScript Stage 3 (no version yet) - ✔ Array Grouping - `Object.groupBy` and `Map.groupBy` +- ✔ Promise.withResolvers - ✔ ShadowRealm #### Other From 8794aa03cc85ff449836b5ab4d2a6166edc1bf0f Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 21 Oct 2023 12:53:10 +0300 Subject: [PATCH 16/44] Add fast path without try-catch for CLR function call (#1651) --- Jint/Options.cs | 4 +- Jint/Runtime/Interop/ClrFunctionInstance.cs | 107 ++++++++++---------- 2 files changed, 58 insertions(+), 53 deletions(-) diff --git a/Jint/Options.cs b/Jint/Options.cs index 56f7b4d236..74a477434e 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -295,7 +295,7 @@ public class InteropOptions /// to the CLR host and interrupt the script execution. If handler returns true these exceptions are converted /// to JS errors that can be caught by the script. /// - public ExceptionHandlerDelegate ExceptionHandler { get; set; } = static exception => false; + public ExceptionHandlerDelegate ExceptionHandler { get; set; } = _defaultExceptionHandler; /// /// Assemblies to allow scripts to call CLR types directly like System.IO.File. @@ -328,6 +328,8 @@ public class InteropOptions /// public Func CreateTypeReferenceObject = (_, _, _) => null; + internal static readonly ExceptionHandlerDelegate _defaultExceptionHandler = static exception => false; + /// /// When not null, is used to serialize any CLR object in an /// passing through 'JSON.stringify'. diff --git a/Jint/Runtime/Interop/ClrFunctionInstance.cs b/Jint/Runtime/Interop/ClrFunctionInstance.cs index d777dc5a05..674baf3c4e 100644 --- a/Jint/Runtime/Interop/ClrFunctionInstance.cs +++ b/Jint/Runtime/Interop/ClrFunctionInstance.cs @@ -1,78 +1,81 @@ +using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using Jint.Native; using Jint.Native.Function; using Jint.Runtime.Descriptors; -namespace Jint.Runtime.Interop +namespace Jint.Runtime.Interop; + +/// +/// Wraps a Clr method into a FunctionInstance +/// +public sealed class ClrFunctionInstance : FunctionInstance, IEquatable { - /// - /// Wraps a Clr method into a FunctionInstance - /// - public sealed class ClrFunctionInstance : FunctionInstance, IEquatable + internal readonly Func _func; + private readonly bool _bubbleExceptions; + + public ClrFunctionInstance( + Engine engine, + string name, + Func func, + int length = 0, + PropertyFlag lengthFlags = PropertyFlag.AllForbidden) + : base(engine, engine.Realm, new JsString(name)) { - internal readonly Func _func; + _func = func; - public ClrFunctionInstance( - Engine engine, - string name, - Func func, - int length = 0, - PropertyFlag lengthFlags = PropertyFlag.AllForbidden) - : base(engine, engine.Realm, new JsString(name)) - { - _func = func; + _prototype = engine._originalIntrinsics.Function.PrototypeObject; - _prototype = engine._originalIntrinsics.Function.PrototypeObject; + _length = lengthFlags == PropertyFlag.AllForbidden + ? PropertyDescriptor.AllForbiddenDescriptor.ForNumber(length) + : new PropertyDescriptor(JsNumber.Create(length), lengthFlags); - _length = lengthFlags == PropertyFlag.AllForbidden - ? PropertyDescriptor.AllForbiddenDescriptor.ForNumber(length) - : new PropertyDescriptor(JsNumber.Create(length), lengthFlags); - } + _bubbleExceptions = _engine.Options.Interop.ExceptionHandler == InteropOptions._defaultExceptionHandler; + } + + protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) => _bubbleExceptions ? _func(thisObject, arguments) : CallSlow(thisObject, arguments); - protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) + [MethodImpl(MethodImplOptions.NoInlining)] + private JsValue CallSlow(JsValue thisObject, JsValue[] arguments) + { + try { - try + return _func(thisObject, arguments); + } + catch (Exception e) when (e is not JavaScriptException) + { + if (_engine.Options.Interop.ExceptionHandler(e)) { - return _func(thisObject, arguments); + ExceptionHelper.ThrowJavaScriptException(_realm.Intrinsics.Error, e.Message); } - catch (Exception e) when (e is not JavaScriptException) + else { - if (_engine.Options.Interop.ExceptionHandler(e)) - { - ExceptionHelper.ThrowJavaScriptException(_realm.Intrinsics.Error, e.Message); - } - else - { - ExceptionDispatchInfo.Capture(e).Throw(); - } - - return Undefined; + ExceptionDispatchInfo.Capture(e).Throw(); } + + return Undefined; } + } + + public override bool Equals(JsValue? obj) => Equals(obj as ClrFunctionInstance); - public override bool Equals(JsValue? obj) + public bool Equals(ClrFunctionInstance? other) + { + if (ReferenceEquals(null, other)) { - return Equals(obj as ClrFunctionInstance); + return false; } - public bool Equals(ClrFunctionInstance? other) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (_func == other._func) - { - return true; - } + return true; + } - return false; + if (_func == other._func) + { + return true; } + + return false; } } From f4667ed60e26739564748d3f173b0001763dd406 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 21 Oct 2023 20:00:14 +0300 Subject: [PATCH 17/44] Remove exposed CLR type equality requirement from ObjectWrapper.Equals (#1652) --- .../Runtime/InteropExplicitTypeTests.cs | 3 --- Jint.Tests/Runtime/InteropTests.cs | 20 +++++++++++++++++++ Jint/Native/JsValue.cs | 7 +------ Jint/Runtime/Interop/ObjectWrapper.cs | 8 ++------ 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs index eef485018a..f202249511 100644 --- a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs +++ b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs @@ -87,10 +87,7 @@ public InteropExplicitTypeTests() public void EqualTest() { Assert.Equal(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.i1")); - Assert.NotEqual(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.ci1")); - Assert.Equal(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.super")); - Assert.NotEqual(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.ci1")); } [Fact] diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index d8b0ac5368..2775f77721 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -3263,5 +3263,25 @@ public void CanPassDateTimeMinAndMaxViaInterop() engine.Execute("capture(maxDate);"); Assert.Equal(DateTime.MaxValue, dt); } + + private class Container + { + private readonly Child _child = new(); + public Child Child => _child; + public BaseClass Get() => _child; + } + + private class BaseClass { } + + private class Child : BaseClass { } + + [Fact] + public void AccessingBaseTypeShouldBeEqualToAccessingDerivedType() + { + var engine = new Engine().SetValue("container", new Container()); + var res = engine.Evaluate("container.Child === container.Get()"); // These two should be the same object. But this PR makes `container.Get()` return a different object + + Assert.True(res.AsBoolean()); + } } } diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 23cb199e8a..47e3f1ffcd 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -517,12 +517,7 @@ internal static bool SameValue(JsValue x, JsValue y) case Types.Symbol: return x == y; case Types.Object: - if (x is ObjectWrapper xo && y is ObjectWrapper yo) - { - return ReferenceEquals(xo.Target, yo.Target) - && xo.ClrType == yo.ClrType; - } - return false; + return x is ObjectWrapper xo && y is ObjectWrapper yo && ReferenceEquals(xo.Target, yo.Target); default: return false; } diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index 725feaf21a..245251fee7 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -301,10 +301,7 @@ internal override ulong GetSmallestIndex(ulong length) return Target is ICollection ? 0 : base.GetSmallestIndex(length); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as ObjectWrapper); - } + public override bool Equals(JsValue? obj) => Equals(obj as ObjectWrapper); public bool Equals(ObjectWrapper? other) { @@ -318,14 +315,13 @@ public bool Equals(ObjectWrapper? other) return true; } - return Equals(Target, other.Target) && Equals(ClrType, other.ClrType); + return Equals(Target, other.Target); } public override int GetHashCode() { var hashCode = -1468639730; hashCode = hashCode * -1521134295 + Target.GetHashCode(); - hashCode = hashCode * -1521134295 + ClrType.GetHashCode(); return hashCode; } From 0cf15010e0ad2a26ef9803f37aa124358e66ffdc Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 22 Oct 2023 10:22:49 +0300 Subject: [PATCH 18/44] Include interface's extended interfaces in interop property search (#1654) --- Jint.Tests/Runtime/InteropTests.cs | 48 +++++++++++++++++++++++++++ Jint/Runtime/Interop/TypeResolver.cs | 49 ++++++++++++++++++++-------- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 2775f77721..ac364f68b4 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -3283,5 +3283,53 @@ public void AccessingBaseTypeShouldBeEqualToAccessingDerivedType() Assert.True(res.AsBoolean()); } + + public interface IIndexer + { + T this[int index] { get; } + } + + public interface ICountable + { + int Count { get; } + } + + public interface IStringCollection : IIndexer, ICountable + { + string this[string name] { get; } + } + + public class Strings : IStringCollection + { + private readonly string[] _strings; + public Strings(string[] strings) + { + _strings = strings; + } + public string this[string name] + { + get + { + return int.TryParse(name, out var index) ? _strings[index] : _strings.FirstOrDefault(x => x.Contains(name)); + } + } + + public string this[int index] => _strings[index]; + public int Count => _strings.Length; + } + + public class Utils + { + public IStringCollection GetStrings() => new Strings(new [] { "a", "b", "c" }); + } + + [Fact] + public void AccessingInterfaceShouldContainExtendedInterfaces() + { + var engine = new Engine(); + engine.SetValue("Utils", new Utils()); + var result = engine.Evaluate("const strings = Utils.GetStrings(); strings.Count;").AsNumber(); + Assert.Equal(3, result); + } } } diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index 94de267184..16293f1633 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -212,29 +212,50 @@ internal bool TryFindMemberAccessor( PropertyInfo? property = null; var memberNameComparer = MemberNameComparer; var typeResolverMemberNameCreator = MemberNameCreator; - foreach (var p in type.GetProperties(bindingFlags)) + + PropertyInfo? GetProperty(Type t) { - if (!Filter(engine, p)) + foreach (var p in t.GetProperties(bindingFlags)) { - continue; - } + if (!Filter(engine, p)) + { + continue; + } - // only if it's not an indexer, we can do case-ignoring matches - var isStandardIndexer = p.GetIndexParameters().Length == 1 && p.Name == "Item"; - if (!isStandardIndexer) - { - foreach (var name in typeResolverMemberNameCreator(p)) + // only if it's not an indexer, we can do case-ignoring matches + var isStandardIndexer = p.GetIndexParameters().Length == 1 && p.Name == "Item"; + if (!isStandardIndexer) { - if (memberNameComparer.Equals(name, memberName)) + foreach (var name in typeResolverMemberNameCreator(p)) { - property = p; - break; + if (memberNameComparer.Equals(name, memberName)) + { + property = p; + break; + } } } } + + return property; + } + + property = GetProperty(type); + + if (property is null && type.IsInterface) + { + // check inherited interfaces + foreach (var iface in type.GetInterfaces()) + { + property = GetProperty(iface); + if (property is not null) + { + break; + } + } } - if (property != null) + if (property is not null) { accessor = new PropertyAccessor(memberName, property, indexerToTry); return true; @@ -259,7 +280,7 @@ internal bool TryFindMemberAccessor( } } - if (field != null) + if (field is not null) { accessor = new FieldAccessor(field, memberName, indexerToTry); return true; From f12cb7d4cd973065fb9fb6c4540255bffc34a13b Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 22 Oct 2023 11:08:35 +0300 Subject: [PATCH 19/44] Allow registering symbols to TypeReference (#1655) --- .../Runtime/InteropTests.TypeReference.cs | 16 ++++++++++++++ Jint/Runtime/Interop/TypeReference.cs | 22 +++++++++---------- .../Runtime/Interop/TypeReferencePrototype.cs | 14 +++++++++++- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.TypeReference.cs b/Jint.Tests/Runtime/InteropTests.TypeReference.cs index 01d2995bbb..ed4aa6a607 100644 --- a/Jint.Tests/Runtime/InteropTests.TypeReference.cs +++ b/Jint.Tests/Runtime/InteropTests.TypeReference.cs @@ -1,4 +1,6 @@ using Jint.Native; +using Jint.Native.Symbol; +using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; using Jint.Tests.Runtime.Domain; using Microsoft.Extensions.DependencyInjection; @@ -181,6 +183,20 @@ public void CanConfigureCustomInstanceCreator() Assert.Equal("Hello world", engine.Evaluate("new Injectable(123, 'abc').getInjectedValue();")); } + [Fact] + public void CanRegisterToStringTag() + { + var reference = TypeReference.CreateTypeReference(_engine); + reference.FastSetProperty(GlobalSymbolRegistry.ToStringTag, new PropertyDescriptor(nameof(Dependency), false, false, true)); + reference.FastSetDataProperty("abc", 123); + + _engine.SetValue("MyClass", reference); + _engine.Execute("var c = new MyClass();"); + + Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c);")); + Assert.Equal(123, _engine.Evaluate("c.abc")); + } + private class Injectable { private readonly Dependency _dependency; diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index c4c99b3f22..a5dd6de24f 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -261,20 +261,20 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) SetProperty(GlobalSymbolRegistry.HasInstance, hasInstanceProperty); return hasInstanceProperty; } - - return PropertyDescriptor.Undefined; } - - var key = jsString._value; - - if (_properties?.TryGetValue(key, out var descriptor) != true) + else { - descriptor = CreatePropertyDescriptor(key); - if (!ReferenceEquals(descriptor, PropertyDescriptor.Undefined)) + var key = jsString._value; + + if (_properties?.TryGetValue(key, out var descriptor) != true) { - _properties ??= new PropertyDictionary(); - _properties[key] = descriptor; - return descriptor; + descriptor = CreatePropertyDescriptor(key); + if (!ReferenceEquals(descriptor, PropertyDescriptor.Undefined)) + { + _properties ??= new PropertyDictionary(); + _properties[key] = descriptor; + return descriptor; + } } } diff --git a/Jint/Runtime/Interop/TypeReferencePrototype.cs b/Jint/Runtime/Interop/TypeReferencePrototype.cs index 562dc0fe52..51cd02fb3b 100644 --- a/Jint/Runtime/Interop/TypeReferencePrototype.cs +++ b/Jint/Runtime/Interop/TypeReferencePrototype.cs @@ -1,4 +1,6 @@ -using Jint.Native.Object; +using Jint.Native; +using Jint.Native.Object; +using Jint.Runtime.Descriptors; namespace Jint.Runtime.Interop; @@ -11,4 +13,14 @@ public TypeReferencePrototype(Engine engine, TypeReference typeReference) : base } public TypeReference TypeReference { get; } + + public override PropertyDescriptor GetOwnProperty(JsValue property) + { + var descriptor = TypeReference.GetOwnProperty(property); + if (descriptor != PropertyDescriptor.Undefined) + { + return descriptor; + } + return base.GetOwnProperty(property); + } } From adeb772d4984095f78a787dea8a3a459ed13a803 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Mon, 23 Oct 2023 20:23:30 +0300 Subject: [PATCH 20/44] Add better debugger view support via debugger attributes (#1656) --- Jint.Repl/Program.cs | 144 +++++++++--------- Jint.Tests/Runtime/InteropTests.Dynamic.cs | 33 ++++ Jint/Engine.cs | 21 ++- Jint/Native/Function/FunctionInstance.cs | 2 + Jint/Native/JsArray.cs | 28 ++++ Jint/Native/JsNumber.cs | 3 + Jint/Native/JsString.cs | 3 + Jint/Native/JsValue.cs | 55 +------ Jint/Native/Object/ObjectInstance.cs | 46 ++++++ .../Runtime/Environments/EnvironmentRecord.cs | 27 ++++ 10 files changed, 237 insertions(+), 125 deletions(-) diff --git a/Jint.Repl/Program.cs b/Jint.Repl/Program.cs index d52bdfbd41..fda7d8dc71 100644 --- a/Jint.Repl/Program.cs +++ b/Jint.Repl/Program.cs @@ -1,97 +1,89 @@ using System.Diagnostics; using System.Reflection; using Esprima; +using Jint; using Jint.Native; using Jint.Native.Json; using Jint.Runtime; -namespace Jint.Repl +var engine = new Engine(cfg => cfg + .AllowClr() +); + +engine + .SetValue("print", new Action(Console.WriteLine)) + .SetValue("load", new Func( + path => engine.Evaluate(File.ReadAllText(path))) + ); + +var filename = args.Length > 0 ? args[0] : ""; +if (!string.IsNullOrEmpty(filename)) { - internal static class Program + if (!File.Exists(filename)) { - private static void Main(string[] args) - { - var engine = new Engine(cfg => cfg - .AllowClr() - ); - - engine - .SetValue("print", new Action(Console.WriteLine)) - .SetValue("load", new Func( - path => engine.Evaluate(File.ReadAllText(path))) - ); + Console.WriteLine("Could not find file: {0}", filename); + } - var filename = args.Length > 0 ? args[0] : ""; - if (!string.IsNullOrEmpty(filename)) - { - if (!File.Exists(filename)) - { - Console.WriteLine("Could not find file: {0}", filename); - } + var script = File.ReadAllText(filename); + engine.Evaluate(script, "repl"); + return; +} - var script = File.ReadAllText(filename); - engine.Evaluate(script, "repl"); - return; - } +var assembly = Assembly.GetExecutingAssembly(); +var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); +var version = fvi.FileVersion; - var assembly = Assembly.GetExecutingAssembly(); - var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); - var version = fvi.FileVersion; +Console.WriteLine("Welcome to Jint ({0})", version); +Console.WriteLine("Type 'exit' to leave, " + + "'print()' to write on the console, " + + "'load()' to load scripts."); +Console.WriteLine(); - Console.WriteLine("Welcome to Jint ({0})", version); - Console.WriteLine("Type 'exit' to leave, " + - "'print()' to write on the console, " + - "'load()' to load scripts."); - Console.WriteLine(); +var defaultColor = Console.ForegroundColor; +var parserOptions = new ParserOptions +{ + Tolerant = true, + RegExpParseMode = RegExpParseMode.AdaptToInterpreted +}; - var defaultColor = Console.ForegroundColor; - var parserOptions = new ParserOptions - { - Tolerant = true, - RegExpParseMode = RegExpParseMode.AdaptToInterpreted - }; +var serializer = new JsonSerializer(engine); - var serializer = new JsonSerializer(engine); +while (true) +{ + Console.ForegroundColor = defaultColor; + Console.Write("jint> "); + var input = Console.ReadLine(); + if (input is "exit" or ".exit") + { + return; + } - while (true) + try + { + var result = engine.Evaluate(input, parserOptions); + JsValue str = result; + if (!result.IsPrimitive() && result is not IPrimitiveInstance) + { + str = serializer.Serialize(result, JsValue.Undefined, " "); + if (str == JsValue.Undefined) { - Console.ForegroundColor = defaultColor; - Console.Write("jint> "); - var input = Console.ReadLine(); - if (input is "exit" or ".exit") - { - return; - } - - try - { - var result = engine.Evaluate(input, parserOptions); - JsValue str = result; - if (!result.IsPrimitive() && result is not IPrimitiveInstance) - { - str = serializer.Serialize(result, JsValue.Undefined, " "); - if (str == JsValue.Undefined) - { - str = result; - } - } - else if (result.IsString()) - { - str = serializer.Serialize(result, JsValue.Undefined, JsValue.Undefined); - } - Console.WriteLine(str); - } - catch (JavaScriptException je) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(je.ToString()); - } - catch (Exception e) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(e.Message); - } + str = result; } } + else if (result.IsString()) + { + str = serializer.Serialize(result, JsValue.Undefined, JsValue.Undefined); + } + Console.WriteLine(str); + } + catch (JavaScriptException je) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(je.ToString()); + } + catch (Exception e) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(e.Message); } } diff --git a/Jint.Tests/Runtime/InteropTests.Dynamic.cs b/Jint.Tests/Runtime/InteropTests.Dynamic.cs index d8563d6502..d0de054323 100644 --- a/Jint.Tests/Runtime/InteropTests.Dynamic.cs +++ b/Jint.Tests/Runtime/InteropTests.Dynamic.cs @@ -1,4 +1,7 @@ using System.Dynamic; +using Jint.Native; +using Jint.Native.Symbol; +using Jint.Tests.Runtime.Domain; namespace Jint.Tests.Runtime { @@ -14,6 +17,36 @@ public void CanAccessExpandoObject() Assert.Equal("test", engine.Evaluate("expando.Name").ToString()); } + [Fact] + public void DebugView() + { + // allows displaying different local variables under debugger + + var engine = new Engine(); + var boolNet = true; + var boolJint = (JsBoolean) boolNet; + var doubleNet = 12.34; + var doubleJint = (JsNumber) doubleNet; + var integerNet = 42; + var integerJint = (JsNumber) integerNet; + var stringNet = "ABC"; + var stringJint = (JsString) stringNet; + var arrayNet = new[] { 1, 2, 3 }; + var arrayListNet = new List { 1, 2, 3 }; + var arrayJint = new JsArray(engine, arrayNet.Select(x => (JsNumber) x).ToArray()); + + var objectNet = new Person { Name = "name", Age = 12 }; + var objectJint = new JsObject(engine); + objectJint["name"] = "name"; + objectJint["age"] = 12; + objectJint[GlobalSymbolRegistry.ToStringTag] = "Object"; + + var dictionaryNet = new Dictionary(); + dictionaryNet["name"] = "name"; + dictionaryNet["age"] = 12; + dictionaryNet[GlobalSymbolRegistry.ToStringTag] = "Object"; + } + [Fact] public void CanAccessMemberNamedItemThroughExpando() { diff --git a/Jint/Engine.cs b/Jint/Engine.cs index be39814a89..cfa2399b20 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Diagnostics; +using System.Runtime.CompilerServices; using Esprima; using Esprima.Ast; using Jint.Native; @@ -24,6 +25,7 @@ namespace Jint /// /// Engine is the main API to JavaScript interpretation. Engine instances are not thread-safe. /// + [DebuggerTypeProxy(typeof(EngineDebugView))] public sealed partial class Engine : IDisposable { private static readonly Options _defaultEngineOptions = new(); @@ -1575,5 +1577,22 @@ public void Dispose() clearMethod?.Invoke(_objectWrapperCache, Array.Empty()); #endif } + + [DebuggerDisplay("Engine")] + private sealed class EngineDebugView + { + private readonly Engine _engine; + + public EngineDebugView(Engine engine) + { + _engine = engine; + } + + public ObjectInstance Globals => _engine.Realm.GlobalObject; + public Options Options => _engine.Options; + + public EnvironmentRecord VariableEnvironment => _engine.ExecutionContext.VariableEnvironment; + public EnvironmentRecord LexicalEnvironment => _engine.ExecutionContext.LexicalEnvironment; + } } } diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index 44ec6525ac..832536aff0 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native.Object; @@ -9,6 +10,7 @@ namespace Jint.Native.Function { + [DebuggerDisplay("{ToString(),nq}")] public abstract partial class FunctionInstance : ObjectInstance, ICallable { protected PropertyDescriptor? _prototypeDescriptor; diff --git a/Jint/Native/JsArray.cs b/Jint/Native/JsArray.cs index 26068a839b..7dc4473e78 100644 --- a/Jint/Native/JsArray.cs +++ b/Jint/Native/JsArray.cs @@ -1,7 +1,10 @@ +using System.Diagnostics; using Jint.Native.Array; namespace Jint.Native; +[DebuggerTypeProxy(typeof(JsArrayDebugView))] +[DebuggerDisplay("Count = {Length}")] public sealed class JsArray : ArrayInstance { /// @@ -21,4 +24,29 @@ public JsArray(Engine engine, uint capacity = 0, uint length = 0) : base(engine, public JsArray(Engine engine, JsValue[] items) : base(engine, items) { } + + private sealed class JsArrayDebugView + { + private readonly JsArray _array; + + public JsArrayDebugView(JsArray array) + { + _array = array; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public JsValue[] Values + { + get + { + var values = new JsValue[_array.Length]; + var i = 0; + foreach (var value in _array) + { + values[i++] = value; + } + return values; + } + } + } } diff --git a/Jint/Native/JsNumber.cs b/Jint/Native/JsNumber.cs index 5306bd2b4e..df6267717f 100644 --- a/Jint/Native/JsNumber.cs +++ b/Jint/Native/JsNumber.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using Jint.Native.Number; @@ -5,11 +6,13 @@ namespace Jint.Native; +[DebuggerDisplay("{_value}", Type = "string")] public sealed class JsNumber : JsValue, IEquatable { // .NET double epsilon and JS epsilon have different values internal const double JavaScriptEpsilon = 2.2204460492503130808472633361816E-16; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal readonly double _value; // how many decimals to check when determining if double is actually an int diff --git a/Jint/Native/JsString.cs b/Jint/Native/JsString.cs index b418ca5924..c9600b1997 100644 --- a/Jint/Native/JsString.cs +++ b/Jint/Native/JsString.cs @@ -1,8 +1,10 @@ +using System.Diagnostics; using System.Text; using Jint.Runtime; namespace Jint.Native; +[DebuggerDisplay("{ToString()}")] public class JsString : JsValue, IEquatable, IEquatable { private const int AsciiMax = 126; @@ -28,6 +30,7 @@ public class JsString : JsValue, IEquatable, IEquatable internal static readonly JsString LengthString = new JsString("length"); internal static readonly JsValue CommaString = new JsString(","); + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal string _value; static JsString() diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 47e3f1ffcd..10c594f052 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -13,11 +13,12 @@ namespace Jint.Native { - [DebuggerTypeProxy(typeof(JsValueDebugView))] public abstract class JsValue : IEquatable { public static readonly JsValue Undefined = new JsUndefined(); public static readonly JsValue Null = new JsNull(); + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal readonly InternalTypes _type; protected JsValue(Types type) @@ -33,8 +34,10 @@ internal JsValue(InternalTypes type) [Pure] public virtual bool IsArray() => false; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsIntegerIndexedArray => false; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsConstructor => false; [Pure] @@ -99,6 +102,7 @@ internal bool TryGetIterator(Realm realm, [NotNullWhen(true)] out IteratorInstan return true; } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] public Types Type { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -364,49 +368,6 @@ public override int GetHashCode() return _type.GetHashCode(); } - internal sealed class JsValueDebugView - { - public string Value; - - public JsValueDebugView(JsValue value) - { - switch (value.Type) - { - case Types.None: - Value = "None"; - break; - case Types.Undefined: - Value = "undefined"; - break; - case Types.Null: - Value = "null"; - break; - case Types.Boolean: - Value = ((JsBoolean) value)._value + " (bool)"; - break; - case Types.String: - Value = value.ToString() + " (string)"; - break; - case Types.Number: - Value = ((JsNumber) value)._value + " (number)"; - break; - case Types.BigInt: - Value = ((JsBigInt) value)._value + " (bigint)"; - break; - case Types.Object: - Value = value.AsObject().GetType().Name; - break; - case Types.Symbol: - var jsValue = ((JsSymbol) value)._value; - Value = (jsValue.IsUndefined() ? "" : jsValue.ToString()) + " (symbol)"; - break; - default: - Value = "Unknown"; - break; - } - } - } - /// /// Some values need to be cloned in order to be assigned, like ConcatenatedString. /// @@ -419,11 +380,9 @@ internal JsValue Clone() : DoClone(); } - internal virtual JsValue DoClone() - { - return this; - } + internal virtual JsValue DoClone() => this; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool IsCallable => this is ICallable; /// diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 4c7d7e8735..0942141081 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -6,6 +6,7 @@ using Jint.Native.BigInt; using Jint.Native.Boolean; using Jint.Native.Function; +using Jint.Native.Json; using Jint.Native.Number; using Jint.Native.RegExp; using Jint.Native.String; @@ -17,6 +18,7 @@ namespace Jint.Native.Object { + [DebuggerTypeProxy(typeof(ObjectInstanceDebugView))] public partial class ObjectInstance : JsValue, IEquatable { private bool _initialized; @@ -1200,6 +1202,7 @@ bool TryGetValue(ulong idx, out JsValue jsValue) internal ICallable GetCallable(JsValue source) => source.GetCallable(_engine.Realm); + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal bool IsConcatSpreadable { get @@ -1213,15 +1216,18 @@ internal bool IsConcatSpreadable } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] public virtual bool IsArrayLike => TryGetValue(CommonProperties.Length, out var lengthValue) && lengthValue.IsNumber() && ((JsNumber) lengthValue)._value >= 0; // safe default + [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal virtual bool HasOriginalIterator => false; internal override bool IsIntegerIndexedArray => false; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] public virtual uint Length => (uint) TypeConverter.ToLength(Get(CommonProperties.Length)); public virtual bool PreventExtensions() @@ -1649,5 +1655,45 @@ internal enum IntegrityLevel Sealed, Frozen } + + private sealed class ObjectInstanceDebugView + { + private readonly ObjectInstance _obj; + + public ObjectInstanceDebugView(ObjectInstance obj) + { + _obj = obj; + } + + public ObjectInstance? Prototype => _obj.Prototype; + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair[] Entries + { + get + { + var keys = new KeyValuePair[(_obj._properties?.Count ?? 0) + (_obj._symbols?.Count ?? 0)]; + + var i = 0; + if (_obj._properties is not null) + { + foreach(var key in _obj._properties) + { + keys[i++] = new KeyValuePair(key.Key.Name, UnwrapJsValue(key.Value, _obj)); + } + } + if (_obj._symbols is not null) + { + foreach(var key in _obj._symbols) + { + keys[i++] = new KeyValuePair(key.Key, UnwrapJsValue(key.Value, _obj)); + } + } + return keys; + } + } + + private string DebugToString() => new JsonSerializer(_obj._engine).Serialize(_obj, Undefined, " ").ToString(); + } } } diff --git a/Jint/Runtime/Environments/EnvironmentRecord.cs b/Jint/Runtime/Environments/EnvironmentRecord.cs index 143a5ebaf4..299c13d804 100644 --- a/Jint/Runtime/Environments/EnvironmentRecord.cs +++ b/Jint/Runtime/Environments/EnvironmentRecord.cs @@ -8,6 +8,7 @@ namespace Jint.Runtime.Environments /// Base implementation of an Environment Record /// https://tc39.es/ecma262/#sec-environment-records /// + [DebuggerTypeProxy(typeof(EnvironmentRecordDebugView))] public abstract class EnvironmentRecord : JsValue { protected internal readonly Engine _engine; @@ -132,6 +133,32 @@ public BindingName(string value) } } } + + private sealed class EnvironmentRecordDebugView + { + private readonly EnvironmentRecord _record; + + public EnvironmentRecordDebugView(EnvironmentRecord record) + { + _record = record; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair[] Entries + { + get + { + var bindingNames = _record.GetAllBindingNames(); + var bindings = new KeyValuePair[bindingNames.Length]; + var i = 0; + foreach (var key in bindingNames) + { + bindings[i++] = new KeyValuePair(key, _record.GetBindingValue(key, false)); + } + return bindings; + } + } + } } } From 5ee35f1e52c4077e0c354b3e277ca052852723cf Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 25 Oct 2023 17:33:04 +0300 Subject: [PATCH 21/44] Keep track of MethodInfoFunctionInstance's target object (#1658) --- Jint.Tests/Runtime/InteropTests.cs | 9 +++++++++ Jint/Native/Error/ErrorInstance.cs | 2 +- Jint/Native/JsNull.cs | 2 +- Jint/Native/JsUndefined.cs | 2 +- Jint/Native/JsValue.cs | 2 +- Jint/Options.cs | 10 ++++++++-- Jint/Runtime/Interop/MethodInfoFunctionInstance.cs | 13 ++++++++----- Jint/Runtime/Interop/Reflection/MethodAccessor.cs | 2 +- 8 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index ac364f68b4..3882d75cac 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -3331,5 +3331,14 @@ public void AccessingInterfaceShouldContainExtendedInterfaces() var result = engine.Evaluate("const strings = Utils.GetStrings(); strings.Count;").AsNumber(); Assert.Equal(3, result); } + + [Fact] + public void CanDestructureInteropTargetMethod() + { + var engine = new Engine(); + engine.SetValue("test", new Utils()); + var result = engine.Evaluate("const { getStrings } = test; getStrings().Count;"); + Assert.Equal(3, result); + } } } diff --git a/Jint/Native/Error/ErrorInstance.cs b/Jint/Native/Error/ErrorInstance.cs index 508348193b..20fcdf4108 100644 --- a/Jint/Native/Error/ErrorInstance.cs +++ b/Jint/Native/Error/ErrorInstance.cs @@ -31,6 +31,6 @@ internal void InstallErrorCause(JsValue options) public override string ToString() { - return Engine.Realm.Intrinsics.Error.PrototypeObject.ToString(this, Arguments.Empty).ToObject().ToString() ?? ""; + return Engine.Realm.Intrinsics.Error.PrototypeObject.ToString(this, Arguments.Empty).ToObject()?.ToString() ?? ""; } } diff --git a/Jint/Native/JsNull.cs b/Jint/Native/JsNull.cs index b08b12c09e..ee65963f12 100644 --- a/Jint/Native/JsNull.cs +++ b/Jint/Native/JsNull.cs @@ -8,7 +8,7 @@ internal JsNull() : base(Types.Null) { } - public override object ToObject() => null!; + public override object? ToObject() => null; public override string ToString() => "null"; diff --git a/Jint/Native/JsUndefined.cs b/Jint/Native/JsUndefined.cs index 9772944b93..745f99dd45 100644 --- a/Jint/Native/JsUndefined.cs +++ b/Jint/Native/JsUndefined.cs @@ -8,7 +8,7 @@ internal JsUndefined() : base(Types.Undefined) { } - public override object ToObject() => null!; + public override object? ToObject() => null; public override string ToString() => "undefined"; diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 10c594f052..ae5e6b1ac4 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -157,7 +157,7 @@ public static JsValue FromObjectWithType(Engine engine, object? value, Type? typ /// Converts a to its underlying CLR value. /// /// The underlying CLR value of the instance. - public abstract object ToObject(); + public abstract object? ToObject(); /// /// Coerces boolean value from instance. diff --git a/Jint/Options.cs b/Jint/Options.cs index 74a477434e..980524e4ea 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -169,10 +169,16 @@ private static void AttachExtensionMethodsToPrototype(Engine engine, ObjectInsta foreach (var overloads in methods.GroupBy(x => x.Name)) { - string name = overloads.Key; PropertyDescriptor CreateMethodInstancePropertyDescriptor(ClrFunctionInstance? function) { - var instance = new MethodInfoFunctionInstance(engine, objectType, name, MethodDescriptor.Build(overloads.ToList()), function); + var instance = new MethodInfoFunctionInstance( + engine, + objectType, + target: null, + overloads.Key, + methods: MethodDescriptor.Build(overloads.ToList()), + function); + return new PropertyDescriptor(instance, PropertyFlag.AllForbidden); } diff --git a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs index 8d7385b2a0..264af018ea 100644 --- a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs +++ b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs @@ -10,6 +10,7 @@ namespace Jint.Runtime.Interop internal sealed class MethodInfoFunctionInstance : FunctionInstance { private readonly Type _targetType; + private readonly object? _target; private readonly string _name; private readonly MethodDescriptor[] _methods; private readonly ClrFunctionInstance? _fallbackClrFunctionInstance; @@ -17,19 +18,21 @@ internal sealed class MethodInfoFunctionInstance : FunctionInstance public MethodInfoFunctionInstance( Engine engine, Type targetType, + object? target, string name, MethodDescriptor[] methods, ClrFunctionInstance? fallbackClrFunctionInstance = null) : base(engine, engine.Realm, new JsString(name)) { _targetType = targetType; + _target = target; _name = name; _methods = methods; _fallbackClrFunctionInstance = fallbackClrFunctionInstance; _prototype = engine.Realm.Intrinsics.Function.PrototypeObject; } - private static bool IsGenericParameter(object argObj, Type parameterType) + private static bool IsGenericParameter(object? argObj, Type parameterType) { if (argObj is null) { @@ -49,7 +52,7 @@ private static bool IsGenericParameter(object argObj, Type parameterType) return false; } - private static void HandleGenericParameter(object argObj, Type parameterType, Type[] genericArgTypes) + private static void HandleGenericParameter(object? argObj, Type parameterType, Type[] genericArgTypes) { if (argObj is null) { @@ -94,7 +97,7 @@ private static void HandleGenericParameter(object argObj, Type parameterType, Ty } } - private static MethodBase ResolveMethod(MethodBase method, ParameterInfo[] methodParameters, object thisObj, JsValue[] arguments) + private static MethodBase ResolveMethod(MethodBase method, ParameterInfo[] methodParameters, JsValue[] arguments) { if (!method.IsGenericMethod) { @@ -156,7 +159,7 @@ JsValue[] ArgumentProvider(MethodDescriptor method) } var converter = Engine.ClrTypeConverter; - var thisObj = thisObject.ToObject(); + var thisObj = thisObject.ToObject() ?? _target; object?[]? parameters = null; foreach (var (method, arguments, _) in TypeConverter.FindBestMatch(_engine, _methods, ArgumentProvider)) { @@ -167,7 +170,7 @@ JsValue[] ArgumentProvider(MethodDescriptor method) } var argumentsMatch = true; - var resolvedMethod = ResolveMethod(method.Method, methodParameters, thisObj, arguments); + var resolvedMethod = ResolveMethod(method.Method, methodParameters, arguments); // TPC: if we're concerned about cost of MethodInfo.GetParameters() - we could only invoke it if this ends up being a generic method (i.e. they will be different in that scenario) methodParameters = resolvedMethod.GetParameters(); for (var i = 0; i < parameters.Length; i++) diff --git a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs index 18d400ca9c..e8823d3843 100644 --- a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs @@ -24,7 +24,7 @@ protected override void DoSetValue(object target, object? value) { } public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true) { - return new(new MethodInfoFunctionInstance(engine, _targetType, _name, _methods), PropertyFlag.AllForbidden); + return new(new MethodInfoFunctionInstance(engine, _targetType, target, _name, _methods), PropertyFlag.AllForbidden); } } } From 0242ac5a810d556fac43ee94f852f9ead7141d67 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 28 Oct 2023 09:56:48 +0300 Subject: [PATCH 22/44] Track TypeReference registrations and use as prototype (#1661) --- Jint.Tests/Runtime/InteropTests.TypeReference.cs | 5 +++++ Jint/Engine.cs | 9 +++++++++ Jint/Runtime/Interop/DefaultObjectConverter.cs | 7 +++++++ Jint/Runtime/Interop/TypeReference.cs | 4 +++- 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Jint.Tests/Runtime/InteropTests.TypeReference.cs b/Jint.Tests/Runtime/InteropTests.TypeReference.cs index ed4aa6a607..dc1557f26f 100644 --- a/Jint.Tests/Runtime/InteropTests.TypeReference.cs +++ b/Jint.Tests/Runtime/InteropTests.TypeReference.cs @@ -195,6 +195,11 @@ public void CanRegisterToStringTag() Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c);")); Assert.Equal(123, _engine.Evaluate("c.abc")); + + // engine uses registered type reference + _engine.SetValue("c2", new Dependency()); + Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c2);")); + Assert.Equal(123, _engine.Evaluate("c2.abc")); } private class Injectable diff --git a/Jint/Engine.cs b/Jint/Engine.cs index cfa2399b20..45b4b0e3d4 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -61,6 +61,9 @@ public sealed partial class Engine : IDisposable // cache of types used when resolving CLR type names internal readonly Dictionary TypeCache = new(); + // we use registered type reference as prototype if it's known + internal Dictionary? _typeReferences; + // cache for already wrapped CLR objects to keep object identity internal ConditionalWeakTable? _objectWrapperCache; @@ -1562,6 +1565,12 @@ internal void SignalError(ErrorDispatchInfo error) _error = error; } + internal void RegisterTypeReference(TypeReference reference) + { + _typeReferences ??= new Dictionary(); + _typeReferences[reference.ReferenceType] = reference; + } + public void Dispose() { if (_objectWrapperCache is null) diff --git a/Jint/Runtime/Interop/DefaultObjectConverter.cs b/Jint/Runtime/Interop/DefaultObjectConverter.cs index f9739bbfb2..acf36264c9 100644 --- a/Jint/Runtime/Interop/DefaultObjectConverter.cs +++ b/Jint/Runtime/Interop/DefaultObjectConverter.cs @@ -110,6 +110,13 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW else { var wrapped = engine.Options.Interop.WrapObjectHandler.Invoke(engine, value, type); + + if (ReferenceEquals(wrapped?.GetPrototypeOf(), engine.Realm.Intrinsics.Object.PrototypeObject) + && engine._typeReferences?.TryGetValue(t, out var typeReference) == true) + { + wrapped.SetPrototypeOf(typeReference); + } + result = wrapped; if (engine.Options.Interop.TrackObjectWrapperIdentity && wrapped is not null) diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index a5dd6de24f..d340795568 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -40,7 +40,9 @@ public static TypeReference CreateTypeReference(Engine engine) public static TypeReference CreateTypeReference(Engine engine, Type type) { - return new TypeReference(engine, type); + var reference = new TypeReference(engine, type); + engine.RegisterTypeReference(reference); + return reference; } protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) From 2ae0dc2a353a339bac0a5cf294d1b5e5283ef4a5 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 28 Oct 2023 10:26:57 +0300 Subject: [PATCH 23/44] Account for integer indexers when searching for IndexerAccessor (#1662) --- Jint.Tests/Runtime/InteropTests.cs | 18 +++++---- .../Interop/Reflection/IndexerAccessor.cs | 40 +++++++++++++++---- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 3882d75cac..11a0953349 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -3297,6 +3297,7 @@ public interface ICountable public interface IStringCollection : IIndexer, ICountable { string this[string name] { get; } + string this[int index] { get; } } public class Strings : IStringCollection @@ -3306,14 +3307,8 @@ public Strings(string[] strings) { _strings = strings; } - public string this[string name] - { - get - { - return int.TryParse(name, out var index) ? _strings[index] : _strings.FirstOrDefault(x => x.Contains(name)); - } - } + public string this[string name] => null; public string this[int index] => _strings[index]; public int Count => _strings.Length; } @@ -3332,6 +3327,15 @@ public void AccessingInterfaceShouldContainExtendedInterfaces() Assert.Equal(3, result); } + [Fact] + public void IntegerIndexerIfPreferredOverStringIndexerWhenFound() + { + var engine = new Engine(); + engine.SetValue("Utils", new Utils()); + var result = engine.Evaluate("const strings = Utils.GetStrings(); strings[2];"); + Assert.Equal("c", result); + } + [Fact] public void CanDestructureInteropTargetMethod() { diff --git a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs index 2ecb0892d0..97fed3ebb9 100644 --- a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs @@ -37,18 +37,25 @@ internal static bool TryFindIndexer( [NotNullWhen(true)] out IndexerAccessor? indexerAccessor, [NotNullWhen(true)] out PropertyInfo? indexer) { + indexerAccessor = null; + indexer = null; var paramTypeArray = new Type[1]; + // integer keys can be ambiguous as we only know string keys + int? integerKey = null; + + if (int.TryParse(propertyName, out var intKeyTemp)) + { + integerKey = intKeyTemp; + } + IndexerAccessor? ComposeIndexerFactory(PropertyInfo candidate, Type paramType) { object? key = null; // int key is quite common case - if (paramType == typeof(int)) + if (paramType == typeof(int) && integerKey is not null) { - if (int.TryParse(propertyName, out var intValue)) - { - key = intValue; - } + key = integerKey; } else { @@ -89,6 +96,7 @@ internal static bool TryFindIndexer( } // try to find first indexer having either public getter or setter with matching argument type + PropertyInfo? fallbackIndexer = null; foreach (var candidate in targetType.GetProperties()) { if (!filter(candidate)) @@ -108,12 +116,30 @@ internal static bool TryFindIndexer( indexerAccessor = ComposeIndexerFactory(candidate, paramType); if (indexerAccessor != null) { - indexer = candidate; - return true; + if (paramType != typeof(string) || integerKey is null) + { + // exact match, we don't need to check for integer key + indexer = candidate; + return true; + } + + if (fallbackIndexer is null) + { + // our fallback + fallbackIndexer = candidate; + } } } } + if (fallbackIndexer is not null) + { + indexer = fallbackIndexer; + // just to keep compiler happy, we know we have a value + indexerAccessor = indexerAccessor ?? new IndexerAccessor(indexer, null, null!); + return true; + } + indexerAccessor = default; indexer = default; return false; From e125c62de0873fe8a6cd9d6648d5da915099d6e5 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 28 Oct 2023 12:11:32 +0300 Subject: [PATCH 24/44] Create a sample for System.Text.Json interop (#1663) --- Jint.Tests/Jint.Tests.csproj | 1 + .../Runtime/InteropTests.SystemTextJson.cs | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 Jint.Tests/Runtime/InteropTests.SystemTextJson.cs diff --git a/Jint.Tests/Jint.Tests.csproj b/Jint.Tests/Jint.Tests.csproj index 4403b74345..971c951484 100644 --- a/Jint.Tests/Jint.Tests.csproj +++ b/Jint.Tests/Jint.Tests.csproj @@ -30,6 +30,7 @@ + all diff --git a/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs b/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs new file mode 100644 index 0000000000..8b6d924962 --- /dev/null +++ b/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs @@ -0,0 +1,83 @@ +using System.Reflection; +using System.Text.Json.Nodes; +using Jint.Runtime.Interop; + +namespace Jint.Tests.Runtime; + +public partial class InteropTests +{ + [Fact] + public void AccessingJsonNodeShouldWork() + { + const string Json = """ + { + "employees": { + "type": "array", + "value": [ + { + "firstName": "John", + "lastName": "Doe" + }, + { + "firstName": "Jane", + "lastName": "Doe" + } + ] + } + } + """; + + var variables = JsonNode.Parse(Json); + + var engine = new Engine(options => + { + // JsonArray behave like JS array + options.Interop.WrapObjectHandler = static (e, target, type) => + { + var wrapped = new ObjectWrapper(e, target); + if (target is JsonArray) + { + wrapped.SetPrototypeOf(e.Realm.Intrinsics.Array.PrototypeObject); + } + return wrapped; + }; + + // we cannot access this[string] with anything else than JsonObject, otherwise itw will throw + options.Interop.TypeResolver = new TypeResolver + { + MemberFilter = static info => + { + if (info.DeclaringType != typeof(JsonObject) && info.Name == "Item" && info is PropertyInfo p) + { + var parameters = p.GetIndexParameters(); + return parameters.Length != 1 || parameters[0].ParameterType != typeof(string); + } + + return true; + } + }; + }); + + engine + .SetValue("variables", variables) + .Execute(""" + function populateFullName() { + return variables['employees'].value.map(item => { + var newItem = + { + "firstName": item.firstName, + "lastName": item.lastName, + "fullName": item.firstName + ' ' + item.lastName + }; + + return newItem; + }); + } + """); + + var result = engine.Evaluate("populateFullName()").AsArray(); + Assert.Equal((uint) 2, result.Length); + Assert.Equal("John Doe", result[0].AsObject()["fullName"]); + Assert.Equal("Jane Doe", result[1].AsObject()["fullName"]); + } +} From 176dc3083ab6a6c328e6c44f73ebfaf3a89ab961 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 28 Oct 2023 16:04:23 +0300 Subject: [PATCH 25/44] Convert to Central Package Management (#1664) --- Directory.Packages.props | 32 +++++++++++++++++++ Jint.Benchmark/Jint.Benchmark.csproj | 12 +++---- .../Jint.Tests.CommonScripts.csproj | 9 +++--- .../Jint.Tests.PublicInterface.csproj | 18 ++++------- Jint.Tests.Test262/Jint.Tests.Test262.csproj | 11 +++---- Jint.Tests/Jint.Tests.csproj | 20 +++++------- Jint.sln | 1 + Jint/Jint.csproj | 10 ++---- 8 files changed, 65 insertions(+), 48 deletions(-) create mode 100644 Directory.Packages.props diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000000..38a847299f --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,32 @@ + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Jint.Benchmark/Jint.Benchmark.csproj b/Jint.Benchmark/Jint.Benchmark.csproj index fa88bf720e..dc63a5db7b 100644 --- a/Jint.Benchmark/Jint.Benchmark.csproj +++ b/Jint.Benchmark/Jint.Benchmark.csproj @@ -1,4 +1,4 @@ - + net6.0 Exe @@ -24,10 +24,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj index 3032ed81dd..68d2fd1997 100644 --- a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj +++ b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -16,10 +16,9 @@ - - - - + + + diff --git a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj index f191524e97..e077c88377 100644 --- a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj +++ b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj @@ -21,17 +21,13 @@ - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + diff --git a/Jint.Tests.Test262/Jint.Tests.Test262.csproj b/Jint.Tests.Test262/Jint.Tests.Test262.csproj index 0e3f08d1e6..c149935d98 100644 --- a/Jint.Tests.Test262/Jint.Tests.Test262.csproj +++ b/Jint.Tests.Test262/Jint.Tests.Test262.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -16,11 +16,10 @@ - - - - - + + + + diff --git a/Jint.Tests/Jint.Tests.csproj b/Jint.Tests/Jint.Tests.csproj index 971c951484..baf45fd5a6 100644 --- a/Jint.Tests/Jint.Tests.csproj +++ b/Jint.Tests/Jint.Tests.csproj @@ -24,18 +24,14 @@ - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + + diff --git a/Jint.sln b/Jint.sln index c8f58b6724..7cc5e19cee 100644 --- a/Jint.sln +++ b/Jint.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject README.md = README.md .editorconfig = .editorconfig + Directory.Packages.props = Directory.Packages.props EndProjectSection EndProject Global diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index 2baa5a7fac..1a061147e2 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -1,4 +1,4 @@ - + en-US net462;netstandard2.0;netstandard2.1;net6.0 @@ -16,13 +16,7 @@ - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + From da7cb51e400132514de3a91fdcba0d8353d3677e Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Mon, 30 Oct 2023 20:06:55 +0200 Subject: [PATCH 26/44] Fix parsing of empty date string (#1666) --- Jint.Tests/Runtime/DateTests.cs | 6 ++++++ Jint/Runtime/DefaultTimeSystem.cs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/Jint.Tests/Runtime/DateTests.cs b/Jint.Tests/Runtime/DateTests.cs index 41d78646ab..6e94d7e144 100644 --- a/Jint.Tests/Runtime/DateTests.cs +++ b/Jint.Tests/Runtime/DateTests.cs @@ -107,4 +107,10 @@ public void CanUseMoment() var parsedDate = _engine.Evaluate("moment().format('yyyy')").ToString(); Assert.Equal(DateTime.Now.Year.ToString(),parsedDate); } + + [Fact] + public void CanParseEmptyDate() + { + Assert.True(double.IsNaN(_engine.Evaluate("Date.parse('')").AsNumber())); + } } diff --git a/Jint/Runtime/DefaultTimeSystem.cs b/Jint/Runtime/DefaultTimeSystem.cs index 9a287d34d3..5340c070ec 100644 --- a/Jint/Runtime/DefaultTimeSystem.cs +++ b/Jint/Runtime/DefaultTimeSystem.cs @@ -57,6 +57,11 @@ public virtual bool TryParse(string date, out long epochMilliseconds) { epochMilliseconds = long.MinValue; + if (string.IsNullOrEmpty(date)) + { + return false; + } + // special check for large years that always require + or - in front and have 6 digit year if ((date[0] == '+'|| date[0] == '-') && date.IndexOf('-', 1) == 7) { From 8297f0b5166f3e4c890fb71eceb229df544b72ed Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 1 Nov 2023 20:25:48 +0200 Subject: [PATCH 27/44] Update System.Text.Json sample to handle writes (#1668) --- .../Runtime/InteropTests.SystemTextJson.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs b/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs index 8b6d924962..4f0df7741c 100644 --- a/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs +++ b/Jint.Tests/Runtime/InteropTests.SystemTextJson.cs @@ -31,7 +31,7 @@ public void AccessingJsonNodeShouldWork() var engine = new Engine(options => { - // JsonArray behave like JS array + // make JsonArray behave like JS array options.Interop.WrapObjectHandler = static (e, target, type) => { var wrapped = new ObjectWrapper(e, target); @@ -47,7 +47,7 @@ public void AccessingJsonNodeShouldWork() { MemberFilter = static info => { - if (info.DeclaringType != typeof(JsonObject) && info.Name == "Item" && info is PropertyInfo p) + if (info.ReflectedType != typeof(JsonObject) && info.Name == "Item" && info is PropertyInfo p) { var parameters = p.GetIndexParameters(); return parameters.Length != 1 || parameters[0].ParameterType != typeof(string); @@ -75,9 +75,30 @@ function populateFullName() { } """); + // reading data var result = engine.Evaluate("populateFullName()").AsArray(); Assert.Equal((uint) 2, result.Length); Assert.Equal("John Doe", result[0].AsObject()["fullName"]); Assert.Equal("Jane Doe", result[1].AsObject()["fullName"]); + + // mutating data via JS + engine.Evaluate("variables.employees.type = 'array2'"); + engine.Evaluate("variables.employees.value[0].firstName = 'Jake'"); + + Assert.Equal("array2", engine.Evaluate("variables['employees']['type']").ToString()); + + result = engine.Evaluate("populateFullName()").AsArray(); + Assert.Equal((uint) 2, result.Length); + Assert.Equal("Jake Doe", result[0].AsObject()["fullName"]); + + // mutating original object that is wrapped inside the engine + variables["employees"]["type"] = "array"; + variables["employees"]["value"][0]["firstName"] = "John"; + + Assert.Equal("array", engine.Evaluate("variables['employees']['type']").ToString()); + + result = engine.Evaluate("populateFullName()").AsArray(); + Assert.Equal((uint) 2, result.Length); + Assert.Equal("John Doe", result[0].AsObject()["fullName"]); } } From 8015e77d6c8f6d89cb9fd73ca8588e6123c9900a Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 4 Nov 2023 10:22:54 +0200 Subject: [PATCH 28/44] Support prioritizing indexers (#1669) --- Jint.Tests/Runtime/InteropTests.cs | 17 +-- .../Interop/Reflection/IndexerAccessor.cs | 2 +- Jint/Runtime/Interop/TypeResolver.cs | 139 +++++++++++------- 3 files changed, 93 insertions(+), 65 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 11a0953349..e054e0bf42 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -1697,10 +1697,7 @@ public void ShouldConvertToEnum() public void ShouldUseExplicitPropertyGetter() { _engine.SetValue("c", new Company("ACME")); - - RunTest(@" - assert(c.Name === 'ACME'); - "); + Assert.Equal("ACME", _engine.Evaluate("c.Name")); } [Fact] @@ -1709,21 +1706,14 @@ public void ShouldUseExplicitIndexerPropertyGetter() var company = new Company("ACME"); ((ICompany) company)["Foo"] = "Bar"; _engine.SetValue("c", company); - - RunTest(@" - assert(c.Foo === 'Bar'); - "); + Assert.Equal("Bar", _engine.Evaluate("c.Foo")); } [Fact] public void ShouldUseExplicitPropertySetter() { _engine.SetValue("c", new Company("ACME")); - - RunTest(@" - c.Name = 'Foo'; - assert(c.Name === 'Foo'); - "); + Assert.Equal("Foo", _engine.Evaluate("c.Name = 'Foo'; c.Name;")); } [Fact] @@ -3297,7 +3287,6 @@ public interface ICountable public interface IStringCollection : IIndexer, ICountable { string this[string name] { get; } - string this[int index] { get; } } public class Strings : IStringCollection diff --git a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs index 97fed3ebb9..3d5aa0a8f7 100644 --- a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs @@ -12,7 +12,7 @@ internal sealed class IndexerAccessor : ReflectionAccessor { private readonly object _key; - private readonly PropertyInfo _indexer; + internal readonly PropertyInfo _indexer; private readonly MethodInfo? _getter; private readonly MethodInfo? _setter; private readonly MethodInfo? _containsKey; diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index 16293f1633..c3964ff754 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -75,7 +75,7 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( string memberName, bool forWrite) { - var isNumber = uint.TryParse(memberName, out _); + var isInteger = long.TryParse(memberName, out _); // we can always check indexer if there's one, and then fall back to properties if indexer returns null IndexerAccessor.TryFindIndexer(engine, type, memberName, out var indexerAccessor, out var indexer); @@ -83,7 +83,7 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( const BindingFlags BindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public; // properties and fields cannot be numbers - if (!isNumber + if (!isInteger && TryFindMemberAccessor(engine, type, memberName, BindingFlags, indexer, out var temp) && (!forWrite || temp.Writable)) { @@ -95,84 +95,106 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( return new DynamicObjectAccessor(type, memberName); } - // if no methods are found check if target implemented indexing - if (indexerAccessor != null) - { - return indexerAccessor; - } - - // try to find a single explicit property implementation - List? list = null; var typeResolverMemberNameComparer = MemberNameComparer; var typeResolverMemberNameCreator = MemberNameCreator; - foreach (var iface in type.GetInterfaces()) + + if (!isInteger) { - foreach (var iprop in iface.GetProperties()) + // try to find a single explicit property implementation + List? list = null; + foreach (var iface in type.GetInterfaces()) { - if (!Filter(engine, iprop)) + foreach (var iprop in iface.GetProperties()) { - continue; - } + if (!Filter(engine, iprop)) + { + continue; + } - if (iprop.Name == "Item" && iprop.GetIndexParameters().Length == 1) - { - // never take indexers, should use the actual indexer - continue; - } + if (iprop.Name == "Item" && iprop.GetIndexParameters().Length == 1) + { + // never take indexers, should use the actual indexer + continue; + } - foreach (var name in typeResolverMemberNameCreator(iprop)) - { - if (typeResolverMemberNameComparer.Equals(name, memberName)) + foreach (var name in typeResolverMemberNameCreator(iprop)) { - list ??= new List(); - list.Add(iprop); + if (typeResolverMemberNameComparer.Equals(name, memberName)) + { + list ??= new List(); + list.Add(iprop); + } } } } - } - if (list?.Count == 1) - { - return new PropertyAccessor(memberName, list[0]); - } + if (list?.Count == 1) + { + return new PropertyAccessor(memberName, list[0]); + } - // try to find explicit method implementations - List? explicitMethods = null; - foreach (var iface in type.GetInterfaces()) - { - foreach (var imethod in iface.GetMethods()) + // try to find explicit method implementations + List? explicitMethods = null; + foreach (var iface in type.GetInterfaces()) { - if (!Filter(engine, imethod)) + foreach (var imethod in iface.GetMethods()) { - continue; - } + if (!Filter(engine, imethod)) + { + continue; + } - foreach (var name in typeResolverMemberNameCreator(imethod)) - { - if (typeResolverMemberNameComparer.Equals(name, memberName)) + foreach (var name in typeResolverMemberNameCreator(imethod)) { - explicitMethods ??= new List(); - explicitMethods.Add(imethod); + if (typeResolverMemberNameComparer.Equals(name, memberName)) + { + explicitMethods ??= new List(); + explicitMethods.Add(imethod); + } } } } + + if (explicitMethods?.Count > 0) + { + return new MethodAccessor(type, memberName, MethodDescriptor.Build(explicitMethods)); + } } - if (explicitMethods?.Count > 0) + // if no methods are found check if target implemented indexing + var score = int.MaxValue; + if (indexerAccessor != null) { - return new MethodAccessor(type, memberName, MethodDescriptor.Build(explicitMethods)); + var parameter = indexerAccessor._indexer.GetIndexParameters()[0]; + score = CalculateIndexerScore(parameter, isInteger); } - // try to find explicit indexer implementations - foreach (var interfaceType in type.GetInterfaces()) + if (score != 0) { - if (IndexerAccessor.TryFindIndexer(engine, interfaceType, memberName, out var accessor, out _)) + // try to find explicit indexer implementations that has a better score than earlier + foreach (var interfaceType in type.GetInterfaces()) { - return accessor; + if (IndexerAccessor.TryFindIndexer(engine, interfaceType, memberName, out var accessor, out _)) + { + var parameter = accessor._indexer.GetIndexParameters()[0]; + var newScore = CalculateIndexerScore(parameter, isInteger); + if (newScore < score) + { + // found a better one + indexerAccessor = accessor; + score = newScore; + } + } } } - if (engine._extensionMethods.TryGetExtensionMethods(type, out var extensionMethods)) + // use the best indexer we were able to find + if (indexerAccessor != null) + { + return indexerAccessor; + } + + if (!isInteger && engine._extensionMethods.TryGetExtensionMethods(type, out var extensionMethods)) { var matches = new List(); foreach (var method in extensionMethods) @@ -200,6 +222,23 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( return ConstantValueAccessor.NullAccessor; } + private static int CalculateIndexerScore(ParameterInfo parameter, bool isInteger) + { + var paramType = parameter.ParameterType; + + if (paramType == typeof(int)) + { + return isInteger ? 0 : 10; + } + + if (paramType == typeof(string)) + { + return 1; + } + + return 5; + } + internal bool TryFindMemberAccessor( Engine engine, Type type, From aacc233dab1b7c15d518d491f4f0a91a8ae4bb59 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 4 Nov 2023 10:31:15 +0200 Subject: [PATCH 29/44] Upgrade NuGet packages (#1670) --- Directory.Packages.props | 10 ++-- Jint.Tests/Runtime/Domain/Dimensional.cs | 58 ++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 38a847299f..0255e39912 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,25 +4,25 @@ false - + - + - + - + - + diff --git a/Jint.Tests/Runtime/Domain/Dimensional.cs b/Jint.Tests/Runtime/Domain/Dimensional.cs index 9c338836f0..7e0069a253 100644 --- a/Jint.Tests/Runtime/Domain/Dimensional.cs +++ b/Jint.Tests/Runtime/Domain/Dimensional.cs @@ -1,6 +1,6 @@ namespace Jint.Tests.Runtime.Domain { - public class Dimensional : IComparable + public class Dimensional : IComparable, IEquatable { private readonly MeasureUnit[] PossibleMeasureUnits = new MeasureUnit[] { new MeasureUnit("Mass", "kg", 1.0), new MeasureUnit("Mass", "gr", 0.001), new MeasureUnit("Count", "piece", 1.0) }; @@ -47,19 +47,69 @@ public override string ToString() { return Value + " " + MeasureUnit.Name; } + + public bool Equals(Dimensional other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Value.Equals(other.Value) && Equals(MeasureUnit, other.MeasureUnit); + } + + public override bool Equals(object obj) + { + return Equals(obj as Dimensional); + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } } - public class MeasureUnit + public class MeasureUnit : IEquatable { public string MeasureType { get; set; } public string Name { get; set; } public double RelativeValue { get; set; } - public MeasureUnit(string measureType, string Name, double relativeValue) + public MeasureUnit(string measureType, string name, double relativeValue) { this.MeasureType = measureType; - this.Name = Name; + this.Name = name; this.RelativeValue = relativeValue; } + + public bool Equals(MeasureUnit other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return MeasureType == other.MeasureType && Name == other.Name && RelativeValue.Equals(other.RelativeValue); + } + + public override bool Equals(object obj) + { + return Equals(obj as MeasureUnit); + } + + public override int GetHashCode() + { + return MeasureType.GetHashCode() ^ Name.GetHashCode() ^ RelativeValue.GetHashCode(); + } } } From 7a2132f1b30a67822fba25a47617663a48bb928d Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 4 Nov 2023 19:08:31 +0200 Subject: [PATCH 30/44] Cache more script-global information when preparing AST (#1671) --- Jint/Collections/HybridDictionary.cs | 39 +++++++- Jint/Collections/ListDictionary.cs | 7 +- Jint/Collections/StringDictionarySlim.cs | 17 ++++ Jint/Engine.Ast.cs | 96 +++++++++++++++++- Jint/Engine.cs | 98 +++++++------------ Jint/HoistingScope.cs | 11 +-- .../Runtime/Environments/EnvironmentRecord.cs | 2 - .../Environments/GlobalEnvironmentRecord.cs | 45 +++++++-- Jint/Runtime/Environments/JintEnvironment.cs | 2 +- .../Expressions/JintIdentifierExpression.cs | 38 +++++-- .../Statements/JintExpressionStatement.cs | 10 +- 11 files changed, 271 insertions(+), 94 deletions(-) diff --git a/Jint/Collections/HybridDictionary.cs b/Jint/Collections/HybridDictionary.cs index 4b27fae70b..f629d0600e 100644 --- a/Jint/Collections/HybridDictionary.cs +++ b/Jint/Collections/HybridDictionary.cs @@ -51,7 +51,7 @@ public TValue this[Key key] { if (_list.Count >= CutoverPoint - 1) { - SwitchToDictionary(key, value); + SwitchToDictionary(key, value, tryAdd: false); } else { @@ -97,7 +97,7 @@ public void SetOrUpdateValue(Key key, Func updat } } - private void SwitchToDictionary(Key key, TValue value) + private bool SwitchToDictionary(Key key, TValue value, bool tryAdd) { var dictionary = new StringDictionarySlim(InitialDictionarySize); foreach (var pair in _list) @@ -105,9 +105,19 @@ private void SwitchToDictionary(Key key, TValue value) dictionary[pair.Key] = pair.Value; } - dictionary[key] = value; + bool result; + if (tryAdd) + { + result = dictionary.TryAdd(key, value); + } + else + { + dictionary[key] = value; + result = true; + } _dictionary = dictionary; _list = null; + return result; } public int Count @@ -116,6 +126,27 @@ public int Count get => _dictionary?.Count ?? _list?.Count ?? 0; } + public bool TryAdd(Key key, TValue value) + { + if (_dictionary != null) + { + return _dictionary.TryAdd(key, value); + } + else + { + _list ??= new ListDictionary(key, value, _checkExistingKeys); + + if (_list.Count + 1 >= CutoverPoint) + { + return SwitchToDictionary(key, value, tryAdd: true); + } + else + { + return _list.Add(key, value, tryAdd: true); + } + } + } + public void Add(Key key, TValue value) { if (_dictionary != null) @@ -132,7 +163,7 @@ public void Add(Key key, TValue value) { if (_list.Count + 1 >= CutoverPoint) { - SwitchToDictionary(key, value); + SwitchToDictionary(key, value, tryAdd: false); } else { diff --git a/Jint/Collections/ListDictionary.cs b/Jint/Collections/ListDictionary.cs index a1fdd1db1a..51e72c98ed 100644 --- a/Jint/Collections/ListDictionary.cs +++ b/Jint/Collections/ListDictionary.cs @@ -99,7 +99,7 @@ public int Count get => _count; } - public void Add(Key key, TValue value) + public bool Add(Key key, TValue value, bool tryAdd = false) { DictionaryNode last = null; DictionaryNode node; @@ -109,6 +109,10 @@ public void Add(Key key, TValue value) var oldKey = node.Key; if (checkExistingKeys && oldKey == key) { + if (tryAdd) + { + return false; + } ExceptionHelper.ThrowArgumentException(); } @@ -116,6 +120,7 @@ public void Add(Key key, TValue value) } AddNode(key, value, last); + return true; } private void AddNode(Key key, TValue value, DictionaryNode last) diff --git a/Jint/Collections/StringDictionarySlim.cs b/Jint/Collections/StringDictionarySlim.cs index 29b534e7d3..15b3a75e4f 100644 --- a/Jint/Collections/StringDictionarySlim.cs +++ b/Jint/Collections/StringDictionarySlim.cs @@ -171,6 +171,23 @@ public ref TValue GetOrAddValueRef(Key key) return ref AddKey(key, bucketIndex); } + public bool TryAdd(Key key, TValue value) + { + Entry[] entries = _entries; + int bucketIndex = key.HashCode & (_buckets.Length - 1); + for (int i = _buckets[bucketIndex] - 1; + (uint)i < (uint)entries.Length; i = entries[i].next) + { + if (key.Name == entries[i].key.Name) + { + return false; + } + } + + AddKey(key, bucketIndex) = value; + return true; + } + /// /// Adds a new item and expects key to not to exist. /// diff --git a/Jint/Engine.Ast.cs b/Jint/Engine.Ast.cs index 17df3b232a..8e76a0e680 100644 --- a/Jint/Engine.Ast.cs +++ b/Jint/Engine.Ast.cs @@ -8,7 +8,6 @@ namespace Jint; public partial class Engine { - /// /// Prepares a script for the engine that includes static analysis data to speed up execution during run-time. /// @@ -70,7 +69,102 @@ public void NodeVisitor(Node node) var function = (IFunction) node; node.AssociatedData = JintFunctionDefinition.BuildState(function); break; + case Nodes.Program: + node.AssociatedData = new CachedHoistingScope((Program) node); + break; + } + } + } +} + +internal sealed class CachedHoistingScope +{ + public CachedHoistingScope(Program program) + { + Scope = HoistingScope.GetProgramLevelDeclarations(program); + + VarNames = new List(); + GatherVarNames(Scope, VarNames); + + LexNames = new List(); + GatherLexNames(Scope, LexNames); + } + + internal static void GatherVarNames(HoistingScope scope, List boundNames) + { + var varDeclarations = scope._variablesDeclarations; + if (varDeclarations != null) + { + for (var i = 0; i < varDeclarations.Count; i++) + { + var d = varDeclarations[i]; + d.GetBoundNames(boundNames); + } + } + } + + internal static void GatherLexNames(HoistingScope scope, List boundNames) + { + var lexDeclarations = scope._lexicalDeclarations; + if (lexDeclarations != null) + { + var temp = new List(); + for (var i = 0; i < lexDeclarations.Count; i++) + { + var d = lexDeclarations[i]; + temp.Clear(); + d.GetBoundNames(temp); + foreach (var name in temp) + { + boundNames.Add(new CachedLexicalName(name, d.IsConstantDeclaration())); + } } } } + + internal readonly record struct CachedLexicalName(string Name, bool Constant); + + public HoistingScope Scope { get; } + public List VarNames { get; } + public List LexNames { get; } +} + +internal static class AstPreparationExtensions +{ + internal static HoistingScope GetHoistingScope(this Program program) + { + return program.AssociatedData is CachedHoistingScope cached ? cached.Scope : HoistingScope.GetProgramLevelDeclarations(program); + } + + internal static List GetVarNames(this Program program, HoistingScope hoistingScope) + { + List boundNames; + if (program.AssociatedData is CachedHoistingScope cached) + { + boundNames = cached.VarNames; + } + else + { + boundNames = new List(); + CachedHoistingScope.GatherVarNames(hoistingScope, boundNames); + } + + return boundNames; + } + + internal static List GetLexNames(this Program program, HoistingScope hoistingScope) + { + List boundNames; + if (program.AssociatedData is CachedHoistingScope cached) + { + boundNames = cached.LexNames; + } + else + { + boundNames = new List(); + CachedHoistingScope.GatherLexNames(hoistingScope, boundNames); + } + + return boundNames; + } } diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 45b4b0e3d4..240d4e8b18 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -917,14 +917,12 @@ private void GlobalDeclarationInstantiation( Script script, GlobalEnvironmentRecord env) { - var strict = _isStrict || StrictModeScope.IsStrictModeCode; - var hoistingScope = HoistingScope.GetProgramLevelDeclarations(strict, script); + var hoistingScope = script.GetHoistingScope(); var functionDeclarations = hoistingScope._functionDeclarations; - var varDeclarations = hoistingScope._variablesDeclarations; var lexDeclarations = hoistingScope._lexicalDeclarations; - var functionToInitialize = new LinkedList(); - var declaredFunctionNames = new HashSet(); + var functionToInitialize = new List(); + var declaredFunctionNames = new HashSet(StringComparer.Ordinal); var declaredVarNames = new List(); var realm = Realm; @@ -944,74 +942,56 @@ private void GlobalDeclarationInstantiation( } declaredFunctionNames.Add(fn); - functionToInitialize.AddFirst(new JintFunctionDefinition(d)); + functionToInitialize.Add(new JintFunctionDefinition(d)); } } } - var boundNames = new List(); - if (varDeclarations != null) + var varNames = script.GetVarNames(hoistingScope); + for (var j = 0; j < varNames.Count; j++) { - for (var i = 0; i < varDeclarations.Count; i++) + var vn = varNames[j]; + if (env.HasLexicalDeclaration(vn)) { - var d = varDeclarations[i]; - boundNames.Clear(); - d.GetBoundNames(boundNames); - for (var j = 0; j < boundNames.Count; j++) - { - var vn = boundNames[j]; - - if (env.HasLexicalDeclaration(vn)) - { - ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{vn}' has already been declared"); - } - - if (!declaredFunctionNames.Contains(vn)) - { - var vnDefinable = env.CanDeclareGlobalVar(vn); - if (!vnDefinable) - { - ExceptionHelper.ThrowTypeError(realm); - } + ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{vn}' has already been declared"); + } - declaredVarNames.Add(vn); - } + if (!declaredFunctionNames.Contains(vn)) + { + var vnDefinable = env.CanDeclareGlobalVar(vn); + if (!vnDefinable) + { + ExceptionHelper.ThrowTypeError(realm); } + + declaredVarNames.Add(vn); } } PrivateEnvironmentRecord? privateEnv = null; - if (lexDeclarations != null) + var lexNames = script.GetLexNames(hoistingScope); + for (var i = 0; i < lexNames.Count; i++) { - for (var i = 0; i < lexDeclarations.Count; i++) + var (dn, constant) = lexNames[i]; + if (env.HasVarDeclaration(dn) || env.HasLexicalDeclaration(dn) || env.HasRestrictedGlobalProperty(dn)) { - var d = lexDeclarations[i]; - boundNames.Clear(); - d.GetBoundNames(boundNames); - for (var j = 0; j < boundNames.Count; j++) - { - var dn = boundNames[j]; - if (env.HasVarDeclaration(dn) - || env.HasLexicalDeclaration(dn) - || env.HasRestrictedGlobalProperty(dn)) - { - ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared"); - } + ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared"); + } - if (d.IsConstantDeclaration()) - { - env.CreateImmutableBinding(dn, strict: true); - } - else - { - env.CreateMutableBinding(dn, canBeDeleted: false); - } - } + if (constant) + { + env.CreateImmutableBinding(dn, strict: true); + } + else + { + env.CreateMutableBinding(dn, canBeDeleted: false); } } - foreach (var f in functionToInitialize) + // we need to go trough in reverse order to handle the hoisting correctly + for (var i = functionToInitialize.Count - 1; i > -1; i--) { + var f = functionToInitialize[i]; var fn = f.Name!; if (env.HasLexicalDeclaration(fn)) @@ -1023,11 +1003,7 @@ private void GlobalDeclarationInstantiation( env.CreateGlobalFunctionBinding(fn, fo, canBeDeleted: false); } - for (var i = 0; i < declaredVarNames.Count; i++) - { - var vn = declaredVarNames[i]; - env.CreateGlobalVarBinding(vn, canBeDeleted: false); - } + env.CreateGlobalVarBindings(declaredVarNames, canBeDeleted: false); } /// @@ -1199,7 +1175,7 @@ internal void EvalDeclarationInstantiation( PrivateEnvironmentRecord? privateEnv, bool strict) { - var hoistingScope = HoistingScope.GetProgramLevelDeclarations(strict, script); + var hoistingScope = HoistingScope.GetProgramLevelDeclarations(script); var lexEnvRec = (DeclarativeEnvironmentRecord) lexEnv; var varEnvRec = varEnv; @@ -1261,7 +1237,7 @@ internal void EvalDeclarationInstantiation( var functionDeclarations = hoistingScope._functionDeclarations; var functionsToInitialize = new LinkedList(); - var declaredFunctionNames = new HashSet(); + var declaredFunctionNames = new HashSet(StringComparer.Ordinal); if (functionDeclarations != null) { diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index 7fde8159ae..68a28c6eff 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -28,12 +28,11 @@ private HoistingScope( } public static HoistingScope GetProgramLevelDeclarations( - bool strict, Program script, bool collectVarNames = false, bool collectLexicalNames = false) { - var treeWalker = new ScriptWalker(strict, collectVarNames, collectLexicalNames); + var treeWalker = new ScriptWalker(collectVarNames, collectLexicalNames); treeWalker.Visit(script, null); return new HoistingScope( @@ -46,7 +45,7 @@ public static HoistingScope GetProgramLevelDeclarations( public static HoistingScope GetFunctionLevelDeclarations(bool strict, IFunction node) { - var treeWalker = new ScriptWalker(strict, collectVarNames: true, collectLexicalNames: true); + var treeWalker = new ScriptWalker(collectVarNames: true, collectLexicalNames: true); treeWalker.Visit(node.Body, null); return new HoistingScope( @@ -63,7 +62,7 @@ public static HoistingScope GetModuleLevelDeclarations( bool collectLexicalNames = false) { // modules area always strict - var treeWalker = new ScriptWalker(strict: true, collectVarNames, collectLexicalNames); + var treeWalker = new ScriptWalker(collectVarNames, collectLexicalNames); treeWalker.Visit(module, null); return new HoistingScope( treeWalker._functions, @@ -204,7 +203,6 @@ private sealed class ScriptWalker { internal List? _functions; - private readonly bool _strict; private readonly bool _collectVarNames; internal List? _variableDeclarations; internal List? _varNames; @@ -213,9 +211,8 @@ private sealed class ScriptWalker internal List? _lexicalDeclarations; internal List? _lexicalNames; - public ScriptWalker(bool strict, bool collectVarNames, bool collectLexicalNames) + public ScriptWalker(bool collectVarNames, bool collectLexicalNames) { - _strict = strict; _collectVarNames = collectVarNames; _collectLexicalNames = collectLexicalNames; } diff --git a/Jint/Runtime/Environments/EnvironmentRecord.cs b/Jint/Runtime/Environments/EnvironmentRecord.cs index 299c13d804..b77ad4414d 100644 --- a/Jint/Runtime/Environments/EnvironmentRecord.cs +++ b/Jint/Runtime/Environments/EnvironmentRecord.cs @@ -118,7 +118,6 @@ internal sealed class BindingName { public readonly Key Key; public readonly JsString Value; - public readonly bool HasEvalOrArguments; public readonly JsValue? CalculatedValue; public BindingName(string value) @@ -126,7 +125,6 @@ public BindingName(string value) var key = (Key) value; Key = key; Value = JsString.Create(value); - HasEvalOrArguments = key == KnownKeys.Eval || key == KnownKeys.Arguments; if (key == KnownKeys.Undefined) { CalculatedValue = Undefined; diff --git a/Jint/Runtime/Environments/GlobalEnvironmentRecord.cs b/Jint/Runtime/Environments/GlobalEnvironmentRecord.cs index 1a0ae5f574..295811bb9a 100644 --- a/Jint/Runtime/Environments/GlobalEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/GlobalEnvironmentRecord.cs @@ -12,20 +12,30 @@ namespace Jint.Runtime.Environments /// public sealed class GlobalEnvironmentRecord : EnvironmentRecord { + /// + /// A sealed class for global usage. + /// + internal sealed class GlobalDeclarativeEnvironmentRecord : DeclarativeEnvironmentRecord + { + public GlobalDeclarativeEnvironmentRecord(Engine engine) : base(engine) + { + } + } + internal readonly ObjectInstance _global; // we expect it to be GlobalObject, but need to allow to something host-defined, like Window private readonly GlobalObject? _globalObject; // Environment records are needed by debugger - internal readonly DeclarativeEnvironmentRecord _declarativeRecord; - private readonly HashSet _varNames = new(); + internal readonly GlobalDeclarativeEnvironmentRecord _declarativeRecord; + private readonly HashSet _varNames = new(StringComparer.Ordinal); public GlobalEnvironmentRecord(Engine engine, ObjectInstance global) : base(engine) { _global = global; _globalObject = global as GlobalObject; - _declarativeRecord = new DeclarativeEnvironmentRecord(engine); + _declarativeRecord = new GlobalDeclarativeEnvironmentRecord(engine); } public ObjectInstance GlobalThisValue => _global; @@ -66,7 +76,7 @@ internal override bool TryGetBinding( out Binding binding, [NotNullWhen(true)] out JsValue? value) { - if (_declarativeRecord.TryGetBinding(name, strict, out binding, out value)) + if (_declarativeRecord._dictionary is not null && _declarativeRecord.TryGetBinding(name, strict, out binding, out value)) { return true; } @@ -343,14 +353,31 @@ public bool CanDeclareGlobalFunction(string name) public void CreateGlobalVarBinding(string name, bool canBeDeleted) { Key key = name; - if (!_global._properties!.ContainsKey(key) && _global.Extensible) - { - _global._properties[key] = new PropertyDescriptor(Undefined, canBeDeleted + if (_global.Extensible && _global._properties!.TryAdd(key, new PropertyDescriptor(Undefined, canBeDeleted ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding - : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding); + : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding))) + { + _varNames.Add(name); + } + } + + internal void CreateGlobalVarBindings(List names, bool canBeDeleted) + { + if (!_global.Extensible) + { + return; } - _varNames.Add(name); + for (var i = 0; i < names.Count; i++) + { + var name = names[i]; + + _global._properties!.TryAdd(name,new PropertyDescriptor(Undefined, canBeDeleted + ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding + : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding)); + + _varNames.Add(name); + } } /// diff --git a/Jint/Runtime/Environments/JintEnvironment.cs b/Jint/Runtime/Environments/JintEnvironment.cs index d007a85abc..b8cb54afe4 100644 --- a/Jint/Runtime/Environments/JintEnvironment.cs +++ b/Jint/Runtime/Environments/JintEnvironment.cs @@ -44,7 +44,7 @@ record = env; if (env._outerEnv is null) { - return env.TryGetBinding(name, strict, out _, out value); + return ((GlobalEnvironmentRecord) env).TryGetBinding(name, strict, out _, out value); } while (!ReferenceEquals(record, null)) diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs index 24620560af..d86d7e9784 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs @@ -9,28 +9,52 @@ namespace Jint.Runtime.Interpreter.Expressions; internal sealed class JintIdentifierExpression : JintExpression { + private EnvironmentRecord.BindingName _identifier = null!; + public JintIdentifierExpression(Identifier expression) : base(expression) { + _initialized = false; + } + + public EnvironmentRecord.BindingName Identifier + { + get + { + EnsureIdentifier(); + return _identifier; + } + } + + protected override void Initialize(EvaluationContext context) + { + EnsureIdentifier(); } - internal EnvironmentRecord.BindingName Identifier + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureIdentifier() { - get => (EnvironmentRecord.BindingName) (_expression.AssociatedData ??= new EnvironmentRecord.BindingName(((Identifier) _expression).Name)); + _identifier ??= _expression.AssociatedData as EnvironmentRecord.BindingName ?? new EnvironmentRecord.BindingName(((Identifier) _expression).Name); } - public bool HasEvalOrArguments => Identifier.HasEvalOrArguments; + public bool HasEvalOrArguments + { + get + { + var name = ((Identifier) _expression).Name; + return name is "eval" or "arguments"; + } + } protected override object EvaluateInternal(EvaluationContext context) { var engine = context.Engine; var env = engine.ExecutionContext.LexicalEnvironment; var strict = StrictModeScope.IsStrictModeCode; - var identifier = Identifier; - var identifierEnvironment = JintEnvironment.TryGetIdentifierEnvironmentWithBinding(env, identifier, out var temp) + var identifierEnvironment = JintEnvironment.TryGetIdentifierEnvironmentWithBinding(env, _identifier, out var temp) ? temp : JsValue.Undefined; - return engine._referencePool.Rent(identifierEnvironment, identifier.Value, strict, thisValue: null); + return engine._referencePool.Rent(identifierEnvironment, _identifier.Value, strict, thisValue: null); } public override JsValue GetValue(EvaluationContext context) @@ -79,6 +103,6 @@ public override JsValue GetValue(EvaluationContext context) [MethodImpl(MethodImplOptions.NoInlining)] private void ThrowNotInitialized(Engine engine) { - ExceptionHelper.ThrowReferenceError(engine.Realm, Identifier.Key.Name + " has not been initialized"); + ExceptionHelper.ThrowReferenceError(engine.Realm, _identifier.Key.Name + " has not been initialized"); } } diff --git a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs index 574045905a..e13a7a6329 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs @@ -7,6 +7,9 @@ internal sealed class JintExpressionStatement : JintStatement Date: Sat, 4 Nov 2023 19:16:52 +0200 Subject: [PATCH 31/44] Upgrade to Esprima 3.0.2 (#1672) --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0255e39912..906cd876e4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + From bf728992e6f48e95557e1ba0432295293eaf6712 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 5 Nov 2023 18:35:53 +0200 Subject: [PATCH 32/44] Update benchmark results (#1673) --- Directory.Packages.props | 2 +- Jint.Benchmark/README.md | 154 +++++++++++++++++++-------------------- 2 files changed, 76 insertions(+), 80 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 906cd876e4..270b05b60d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,7 +22,7 @@ - + diff --git a/Jint.Benchmark/README.md b/Jint.Benchmark/README.md index 9bacc72f8a..2f1a8eaf8b 100644 --- a/Jint.Benchmark/README.md +++ b/Jint.Benchmark/README.md @@ -9,91 +9,87 @@ dotnet run -c Release --allCategories EngineComparison * tests are run in global engine strict mode, as YantraJS always uses strict mode which improves performance * `Jint` and `Jint_ParsedScript` shows the difference between always parsing the script source file and reusing parsed `Script` instance. -Last updated 2023-08-29 +Last updated 2023-11-05 * Jint main -* Jurassic 3.2.6 -* NiL.JS 2.5.1665 -* YantraJS.Core 1.2.179 +* Jurassic 3.2.7 +* NiL.JS 2.5.1674 +* YantraJS.Core 1.2.203 ``` -BenchmarkDotNet v0.13.7, Windows 11 (10.0.23531.1001) +BenchmarkDotNet v0.13.10, Windows 11 (10.0.23580.1000) AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores -.NET SDK 8.0.100-preview.7.23376.3 - [Host] : .NET 6.0.21 (6.0.2123.36311), X64 RyuJIT AVX2 - DefaultJob : .NET 6.0.21 (6.0.2123.36311), X64 RyuJIT AVX2 +.NET SDK 8.0.100-rc.2.23502.2 + [Host] : .NET 6.0.24 (6.0.2423.51814), X64 RyuJIT AVX2 + DefaultJob : .NET 6.0.24 (6.0.2423.51814), X64 RyuJIT AVX2 ``` -| Method | FileName | Mean | StdDev | Median | Rank | Allocated | -|------------------ |--------------------- |-----------------:|---------------:|-----------------:|-----:|---------------:| -| YantraJS | array-stress | 6,246.687 μs | 28.4141 μs | 6,240.110 μs | 1 | 6533.07 KB | -| NilJS | array-stress | 7,629.064 μs | 21.4722 μs | 7,626.207 μs | 2 | 4533.76 KB | -| Jint | array-stress | 11,304.127 μs | 24.2301 μs | 11,301.966 μs | 3 | 7111.4 KB | -| Jint_ParsedScript | array-stress | 11,928.758 μs | 78.8122 μs | 11,974.534 μs | 4 | 7085.34 KB | -| Jurassic | array-stress | 13,568.895 μs | 49.1134 μs | 13,548.192 μs | 5 | 11646.96 KB | -| | | | | | | | -| YantraJS | dromaeo-3d-cube | NA | NA | NA | ? | NA | -| NilJS | dromaeo-3d-cube | 11,262.315 μs | 39.4986 μs | 11,253.403 μs | 1 | 4694.63 KB | -| Jint_ParsedScript | dromaeo-3d-cube | 26,401.931 μs | 35.0936 μs | 26,404.000 μs | 2 | 5894.78 KB | -| Jint | dromaeo-3d-cube | 27,799.875 μs | 24.2606 μs | 27,802.781 μs | 3 | 6191.02 KB | -| Jurassic | dromaeo-3d-cube | 49,539.274 μs | 137.7463 μs | 49,615.655 μs | 4 | 10671.74 KB | -| | | | | | | | -| NilJS | dromaeo-core-eval | 2,638.637 μs | 8.3816 μs | 2,642.709 μs | 1 | 1598.78 KB | -| Jint | dromaeo-core-eval | 5,986.834 μs | 23.0157 μs | 5,978.144 μs | 2 | 350.31 KB | -| Jint_ParsedScript | dromaeo-core-eval | 6,049.918 μs | 47.3595 μs | 6,052.202 μs | 2 | 331.04 KB | -| YantraJS | dromaeo-core-eval | 15,121.048 μs | 33.6780 μs | 15,127.921 μs | 3 | 36547.42 KB | -| Jurassic | dromaeo-core-eval | 16,809.782 μs | 6.8618 μs | 16,811.078 μs | 4 | 2901.46 KB | -| | | | | | | | -| Jurassic | dromaeo-object-array | 50,652.934 μs | 193.8856 μs | 50,633.500 μs | 1 | 25814.2 KB | -| YantraJS | dromaeo-object-array | 59,426.130 μs | 492.9887 μs | 59,443.689 μs | 2 | 24745.94 KB | -| Jint_ParsedScript | dromaeo-object-array | 66,402.409 μs | 254.3555 μs | 66,323.750 μs | 3 | 100747.98 KB | -| Jint | dromaeo-object-array | 67,909.973 μs | 374.0128 μs | 67,856.262 μs | 4 | 100792.72 KB | -| NilJS | dromaeo-object-array | 76,716.933 μs | 335.0924 μs | 76,717.700 μs | 5 | 17698.13 KB | -| | | | | | | | -| Jint_ParsedScript | droma(...)egexp [21] | 298,212.421 μs | 4,821.1841 μs | 297,469.850 μs | 1 | 165381.18 KB | -| Jint | droma(...)egexp [21] | 308,929.771 μs | 7,106.6296 μs | 307,314.500 μs | 2 | 168713.91 KB | -| NilJS | droma(...)egexp [21] | 603,665.040 μs | 4,970.4874 μs | 605,048.300 μs | 3 | 768274.13 KB | -| Jurassic | droma(...)egexp [21] | 841,737.444 μs | 30,514.3096 μs | 839,741.400 μs | 4 | 825832.59 KB | -| YantraJS | droma(...)egexp [21] | 1,202,044.984 μs | 25,781.0535 μs | 1,198,305.600 μs | 5 | 941580.41 KB | -| | | | | | | | -| NilJS | droma(...)tring [21] | 417,303.358 μs | 13,929.0807 μs | 418,108.100 μs | 1 | 1377743.66 KB | -| Jint_ParsedScript | droma(...)tring [21] | 563,559.551 μs | 33,924.5891 μs | 551,658.750 μs | 2 | 1322031.27 KB | -| Jint | droma(...)tring [21] | 572,808.661 μs | 30,017.2335 μs | 570,151.650 μs | 2 | 1322176.1 KB | -| Jurassic | droma(...)tring [21] | 692,137.075 μs | 28,047.3722 μs | 698,963.900 μs | 3 | 1457949.11 KB | -| YantraJS | droma(...)tring [21] | 4,060,814.093 μs | 60,908.6909 μs | 4,079,384.300 μs | 4 | 15718148.67 KB | -| | | | | | | | -| NilJS | droma(...)ase64 [21] | 47,816.450 μs | 138.7136 μs | 47,770.455 μs | 1 | 19605.27 KB | -| Jint | droma(...)ase64 [21] | 65,790.989 μs | 272.8843 μs | 65,817.512 μs | 2 | 6772.48 KB | -| Jint_ParsedScript | droma(...)ase64 [21] | 66,114.687 μs | 146.3463 μs | 66,118.562 μs | 2 | 6680.29 KB | -| Jurassic | droma(...)ase64 [21] | 84,478.585 μs | 323.0873 μs | 84,454.725 μs | 3 | 74321.7 KB | -| YantraJS | droma(...)ase64 [21] | 235,350.207 μs | 955.7123 μs | 235,605.333 μs | 4 | 760629.53 KB | -| | | | | | | | -| Jint_ParsedScript | evaluation | 13.655 μs | 0.0306 μs | 13.663 μs | 1 | 26.46 KB | -| Jint | evaluation | 34.425 μs | 0.1069 μs | 34.444 μs | 2 | 35.89 KB | -| NilJS | evaluation | 56.276 μs | 0.1443 μs | 56.288 μs | 3 | 23.47 KB | -| YantraJS | evaluation | 129.051 μs | 0.5500 μs | 129.043 μs | 4 | 462.57 KB | -| Jurassic | evaluation | 1,611.643 μs | 2.3082 μs | 1,611.278 μs | 5 | 420.41 KB | -| | | | | | | | -| YantraJS | linq-js | NA | NA | NA | ? | NA | -| Jint_ParsedScript | linq-js | 116.014 μs | 0.6197 μs | 115.770 μs | 1 | 215.13 KB | -| Jint | linq-js | 2,248.864 μs | 6.9181 μs | 2,247.281 μs | 2 | 1274.64 KB | -| NilJS | linq-js | 9,450.914 μs | 26.1239 μs | 9,452.424 μs | 3 | 4127.79 KB | -| Jurassic | linq-js | 46,341.837 μs | 310.1326 μs | 46,370.700 μs | 4 | 9305.52 KB | -| | | | | | | | -| Jint_ParsedScript | minimal | 3.203 μs | 0.0178 μs | 3.196 μs | 1 | 12.9 KB | -| Jint | minimal | 5.230 μs | 0.0169 μs | 5.229 μs | 2 | 14.34 KB | -| NilJS | minimal | 6.055 μs | 0.0276 μs | 6.041 μs | 3 | 4.81 KB | -| YantraJS | minimal | 123.197 μs | 2.7249 μs | 122.828 μs | 4 | 458.56 KB | -| Jurassic | minimal | 297.292 μs | 0.6073 μs | 297.435 μs | 5 | 386.24 KB | -| | | | | | | | -| YantraJS | stopwatch | 112,530.838 μs | 449.4615 μs | 112,460.320 μs | 1 | 258622.36 KB | -| Jurassic | stopwatch | 247,085.684 μs | 1,162.2628 μs | 246,727.867 μs | 2 | 156937.08 KB | -| NilJS | stopwatch | 295,878.240 μs | 2,000.4676 μs | 295,209.200 μs | 3 | 97361.17 KB | -| Jint_ParsedScript | stopwatch | 471,369.071 μs | 1,578.5815 μs | 471,148.200 μs | 4 | 53023.28 KB | -| Jint | stopwatch | 472,028.947 μs | 3,209.3311 μs | 471,611.400 μs | 4 | 53044.52 KB | - -Benchmarks with issues: -EngineComparisonBenchmark.YantraJS: DefaultJob [FileName=dromaeo-3d-cube] -EngineComparisonBenchmark.YantraJS: DefaultJob [FileName=linq-js] +| Method | FileName | Mean | StdDev | Rank | Allocated | +|------------------ |--------------------- |-----------------:|----------------:|-----:|---------------:| +| NilJS | array-stress | 7,618.410 μs | 9.6261 μs | 1 | 4533.76 KB | +| YantraJS | array-stress | 7,634.348 μs | 154.1204 μs | 1 | 8080.23 KB | +| Jint | array-stress | 10,189.119 μs | 100.0517 μs | 2 | 7112.02 KB | +| Jint_ParsedScript | array-stress | 10,829.737 μs | 29.7168 μs | 3 | 7090.84 KB | +| Jurassic | array-stress | 13,478.434 μs | 134.6721 μs | 4 | 11646.94 KB | +| | | | | | | +| YantraJS | dromaeo-3d-cube | 6,903.873 μs | 29.0353 μs | 1 | 11426.85 KB | +| NilJS | dromaeo-3d-cube | 11,520.118 μs | 12.9132 μs | 2 | 4694.63 KB | +| Jint_ParsedScript | dromaeo-3d-cube | 25,403.787 μs | 76.2460 μs | 3 | 5934.53 KB | +| Jint | dromaeo-3d-cube | 26,214.383 μs | 19.9661 μs | 4 | 6191.84 KB | +| Jurassic | dromaeo-3d-cube | 49,227.408 μs | 112.1937 μs | 5 | 10670.73 KB | +| | | | | | | +| NilJS | dromaeo-core-eval | 2,688.083 μs | 7.5997 μs | 1 | 1598.78 KB | +| Jint_ParsedScript | dromaeo-core-eval | 5,404.308 μs | 11.9911 μs | 2 | 333.82 KB | +| Jint | dromaeo-core-eval | 5,745.384 μs | 10.9482 μs | 3 | 350.86 KB | +| YantraJS | dromaeo-core-eval | 10,070.410 μs | 26.0609 μs | 4 | 70662.5 KB | +| Jurassic | dromaeo-core-eval | 12,145.734 μs | 44.3013 μs | 5 | 2884.85 KB | +| | | | | | | +| Jurassic | dromaeo-object-array | 50,713.787 μs | 105.2239 μs | 1 | 25814.89 KB | +| YantraJS | dromaeo-object-array | 63,932.521 μs | 830.2901 μs | 2 | 29485.54 KB | +| Jint | dromaeo-object-array | 65,413.838 μs | 508.2830 μs | 3 | 100793.32 KB | +| Jint_ParsedScript | dromaeo-object-array | 66,591.951 μs | 140.1417 μs | 4 | 100754.12 KB | +| NilJS | dromaeo-object-array | 75,914.511 μs | 126.7804 μs | 5 | 17698.17 KB | +| | | | | | | +| Jint | droma(...)egexp [21] | 285,685.732 μs | 7,488.3067 μs | 1 | 170207.95 KB | +| Jint_ParsedScript | droma(...)egexp [21] | 289,626.408 μs | 1,896.8981 μs | 1 | 167049.52 KB | +| NilJS | droma(...)egexp [21] | 614,280.120 μs | 7,612.4833 μs | 2 | 766193.08 KB | +| Jurassic | droma(...)egexp [21] | 807,573.300 μs | 14,825.6620 μs | 3 | 825805.82 KB | +| YantraJS | droma(...)egexp [21] | 1,229,769.600 μs | 16,340.3960 μs | 4 | 1254750.68 KB | +| | | | | | | +| NilJS | droma(...)tring [21] | 426,125.000 μs | 7,684.4889 μs | 1 | 1377812.93 KB | +| Jint_ParsedScript | droma(...)tring [21] | 608,204.631 μs | 58,027.2350 μs | 2 | 1322143.68 KB | +| Jint | droma(...)tring [21] | 617,734.935 μs | 39,573.9870 μs | 2 | 1322326.65 KB | +| Jurassic | droma(...)tring [21] | 619,546.013 μs | 9,041.0940 μs | 2 | 1458038.6 KB | +| YantraJS | droma(...)tring [21] | 4,197,768.766 μs | 343,801.2590 μs | 3 | 29822200.57 KB | +| | | | | | | +| NilJS | droma(...)ase64 [21] | 49,243.377 μs | 108.4628 μs | 1 | 19604.8 KB | +| Jint_ParsedScript | droma(...)ase64 [21] | 59,677.164 μs | 177.0683 μs | 2 | 6722.79 KB | +| Jint | droma(...)ase64 [21] | 63,301.808 μs | 318.1747 μs | 3 | 6806.58 KB | +| YantraJS | droma(...)ase64 [21] | 78,283.727 μs | 991.5219 μs | 4 | 1492785.6 KB | +| Jurassic | droma(...)ase64 [21] | 83,485.030 μs | 198.9519 μs | 5 | 74319.52 KB | +| | | | | | | +| Jint_ParsedScript | evaluation | 13.500 μs | 0.0247 μs | 1 | 27.48 KB | +| Jint | evaluation | 33.419 μs | 0.0412 μs | 2 | 36.05 KB | +| NilJS | evaluation | 59.750 μs | 0.1541 μs | 3 | 23.47 KB | +| YantraJS | evaluation | 159.699 μs | 0.6218 μs | 4 | 931 KB | +| Jurassic | evaluation | 1,582.755 μs | 2.5897 μs | 5 | 420.41 KB | +| | | | | | | +| Jint_ParsedScript | linq-js | 111.636 μs | 0.2424 μs | 1 | 226.52 KB | +| YantraJS | linq-js | 461.542 μs | 0.4869 μs | 2 | 1453.9 KB | +| Jint | linq-js | 2,208.746 μs | 12.8004 μs | 3 | 1276.1 KB | +| NilJS | linq-js | 10,450.782 μs | 73.1973 μs | 4 | 4127.79 KB | +| Jurassic | linq-js | 44,781.543 μs | 394.7200 μs | 5 | 9302.34 KB | +| | | | | | | +| Jint_ParsedScript | minimal | 3.382 μs | 0.0078 μs | 1 | 12.99 KB | +| Jint | minimal | 5.334 μs | 0.0174 μs | 2 | 14.38 KB | +| NilJS | minimal | 5.891 μs | 0.0096 μs | 3 | 4.81 KB | +| YantraJS | minimal | 153.890 μs | 0.8023 μs | 4 | 925.48 KB | +| Jurassic | minimal | 294.178 μs | 0.3525 μs | 5 | 386.24 KB | +| | | | | | | +| YantraJS | stopwatch | 112,865.841 μs | 223.3235 μs | 1 | 224277.02 KB | +| Jurassic | stopwatch | 254,529.440 μs | 839.9853 μs | 2 | 156937.17 KB | +| NilJS | stopwatch | 297,948.830 μs | 1,345.9144 μs | 3 | 97363.1 KB | +| Jint_ParsedScript | stopwatch | 394,136.840 μs | 3,882.0490 μs | 4 | 53015.98 KB | +| Jint | stopwatch | 457,053.953 μs | 1,630.9870 μs | 5 | 53045.23 KB | From cbe3b6f6c826189a08de929ed63b5203cb73d450 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Fri, 10 Nov 2023 18:24:48 +0200 Subject: [PATCH 33/44] JsProxy should return proxied target's ToObject via ToObject (#1675) --- Jint.Tests/Runtime/ProxyTests.cs | 39 ++++++++++++++++++++++++++++++++ Jint/Native/Proxy/JsProxy.cs | 2 ++ 2 files changed, 41 insertions(+) diff --git a/Jint.Tests/Runtime/ProxyTests.cs b/Jint.Tests/Runtime/ProxyTests.cs index e569e8ae4f..f86e61cd55 100644 --- a/Jint.Tests/Runtime/ProxyTests.cs +++ b/Jint.Tests/Runtime/ProxyTests.cs @@ -236,6 +236,17 @@ class TestClass private int x = 1; public int PropertySideEffect => x++; + + public string Name => "My Name is Test"; + + public void SayHello() + { + } + + public int Add(int a, int b) + { + return a + b; + } } [Fact] @@ -443,4 +454,32 @@ public void ClrPropertySideEffect() Assert.Equal(2, _engine.Evaluate("p.PropertySideEffect").AsInteger()); // no call to PropertySideEffect Assert.Equal(2, TestClass.Instance.PropertySideEffect); // second call to PropertySideEffect } + + [Fact] + public void ToObjectReturnsProxiedToObject() + { + _engine + .SetValue("T", new TestClass()) + .Execute(""" + const handler = { + get(target, property, receiver) { + + if (!target[property]) { + return (...args) => "Not available"; + } + + // return Reflect.get(target, property, receiver); + return Reflect.get(...arguments); + } + }; + + const p = new Proxy(T, handler); + const name = p.Name; // works + const s = p.GetX(); // works because method does NOT exist on clr object + + p.SayHello(); // throws System.Reflection.TargetException: 'Object does not match target type.' + const t = p.Add(5,3); // throws System.Reflection.TargetException: 'Object does not match target type.' + """); + + } } diff --git a/Jint/Native/Proxy/JsProxy.cs b/Jint/Native/Proxy/JsProxy.cs index 386d70a695..cdd0364dfa 100644 --- a/Jint/Native/Proxy/JsProxy.cs +++ b/Jint/Native/Proxy/JsProxy.cs @@ -100,6 +100,8 @@ public override bool IsArray() return _target.IsArray(); } + public override object ToObject() => _target.ToObject(); + internal override bool IsConstructor { get From bd171d02ac4725f1cb2d9091a7036bb15bf281ab Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Fri, 10 Nov 2023 19:25:01 +0200 Subject: [PATCH 34/44] Improve ParseArrayIndex when property is length (#1676) --- Jint/Native/Array/ArrayInstance.cs | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index 1a75e47145..7f1ca99035 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -597,24 +597,10 @@ internal static bool IsArrayIndex(JsValue p, out uint index) // return TypeConverter.ToString(index) == TypeConverter.ToString(p) && index != uint.MaxValue; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ParseArrayIndex(string p) { - if (p.Length == 0) - { - return uint.MaxValue; - } - - if (p.Length > 1 && p[0] == '0') - { - // If p is a number that start with '0' and is not '0' then - // its ToString representation can't be the same a p. This is - // not a valid array index. '01' !== ToString(ToUInt32('01')) - // http://www.ecma-international.org/ecma-262/5.1/#sec-15.4 - - return uint.MaxValue; - } - - if (!uint.TryParse(p, out var d)) + if (p.Length == 0 || p.Length > 1 && !IsInRange(p[0], '1', '9') || !uint.TryParse(p, out var d)) { return uint.MaxValue; } @@ -622,6 +608,9 @@ internal static uint ParseArrayIndex(string p) return d; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsInRange(char c, char min, char max) => c - (uint) min <= max - (uint) min; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetIndexValue(uint index, JsValue? value, bool updateLength) { From 791384f34d18ed794e9702337ce8a7d442a5176e Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Fri, 10 Nov 2023 19:40:18 +0200 Subject: [PATCH 35/44] Move expression initialize to expression implementation (#1677) --- .../BindingPatternAssignmentExpression.cs | 13 +++--- .../Expressions/JintArrayExpression.cs | 10 ++++- .../Expressions/JintAssignmentExpression.cs | 10 ++++- .../Expressions/JintAwaitExpression.cs | 12 +++--- .../Expressions/JintBinaryExpression.cs | 41 ++++++++++++++++++- .../Expressions/JintCallExpression.cs | 10 ++++- .../Interpreter/Expressions/JintExpression.cs | 23 ----------- .../Expressions/JintIdentifierExpression.cs | 10 ++++- .../Expressions/JintImportExpression.cs | 10 ++++- .../Expressions/JintLogicalAndExpression.cs | 10 ++++- .../Expressions/JintMemberExpression.cs | 10 ++++- .../Expressions/JintNewExpression.cs | 10 ++++- .../Expressions/JintObjectExpression.cs | 10 ++++- .../Expressions/JintSequenceExpression.cs | 10 ++++- .../JintTaggedTemplateExpression.cs | 10 ++++- .../JintTemplateLiteralExpression.cs | 17 ++++---- .../Expressions/JintUpdateExpression.cs | 10 ++++- 17 files changed, 158 insertions(+), 68 deletions(-) diff --git a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs index b6e2964f3d..9a808961bf 100644 --- a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs @@ -12,20 +12,21 @@ internal sealed class BindingPatternAssignmentExpression : JintExpression { private readonly BindingPattern _pattern; private JintExpression _right = null!; + private bool _initialized; public BindingPatternAssignmentExpression(AssignmentExpression expression) : base(expression) { _pattern = (BindingPattern) expression.Left; - _initialized = false; - } - - protected override void Initialize(EvaluationContext context) - { - _right = Build(((AssignmentExpression) _expression).Right); } protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + _right = Build(((AssignmentExpression) _expression).Right); + _initialized = true; + } + var rightValue = _right.GetValue(context); if (context.IsAbrupt()) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs index 0a67535406..230b9ff4b8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs @@ -8,10 +8,10 @@ internal sealed class JintArrayExpression : JintExpression { private JintExpression?[] _expressions = Array.Empty(); private bool _hasSpreads; + private bool _initialized; private JintArrayExpression(ArrayExpression expression) : base(expression) { - _initialized = false; } public static JintExpression Build(ArrayExpression expression) @@ -21,7 +21,7 @@ public static JintExpression Build(ArrayExpression expression) : new JintArrayExpression(expression); } - protected override void Initialize(EvaluationContext context) + private void Initialize() { ref readonly var elements = ref ((ArrayExpression) _expression).Elements; var expressions = _expressions = new JintExpression[((ArrayExpression) _expression).Elements.Count]; @@ -42,6 +42,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var engine = context.Engine; var a = engine.Realm.Intrinsics.Array.ArrayCreate(_hasSpreads ? 0 : (uint) _expressions.Length); diff --git a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs index 74ca100e36..1bac40f5af 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs @@ -343,13 +343,13 @@ internal sealed class SimpleAssignmentExpression : JintExpression private JintIdentifierExpression? _leftIdentifier; private bool _evalOrArguments; + private bool _initialized; public SimpleAssignmentExpression(AssignmentExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var assignmentExpression = (AssignmentExpression) _expression; _left = Build((Expression) assignmentExpression.Left); @@ -361,6 +361,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + object? completion = null; if (_leftIdentifier != null) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs index baaaa6b6e7..600b7cbb55 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs @@ -6,19 +6,21 @@ namespace Jint.Runtime.Interpreter.Expressions; internal sealed class JintAwaitExpression : JintExpression { private JintExpression _awaitExpression = null!; + private bool _initialized; public JintAwaitExpression(AwaitExpression expression) : base(expression) { _initialized = false; } - protected override void Initialize(EvaluationContext context) - { - _awaitExpression = Build(((AwaitExpression) _expression).Argument); - } - protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + _awaitExpression = Build(((AwaitExpression) _expression).Argument); + _initialized = true; + } + var engine = context.Engine; var asyncContext = engine.ExecutionContext; diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs index 9828dd6446..b17a6dd703 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs @@ -20,18 +20,25 @@ internal abstract class JintBinaryExpression : JintExpression private JintExpression _left = null!; private JintExpression _right = null!; + private bool _initialized; private JintBinaryExpression(BinaryExpression expression) : base(expression) { // TODO check https://tc39.es/ecma262/#sec-applystringornumericbinaryoperator - _initialized = false; } - protected override void Initialize(EvaluationContext context) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() { + if (_initialized) + { + return; + } + var expression = (BinaryExpression) _expression; _left = Build(expression.Left); _right = Build(expression.Right); + _initialized = true; } internal static bool TryOperatorOverloading( @@ -196,6 +203,8 @@ public StrictlyEqualBinaryExpression(BinaryExpression expression) : base(express protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); var equal = left == right; @@ -211,6 +220,8 @@ public StrictlyNotEqualBinaryExpression(BinaryExpression expression) : base(expr protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); return left == right ? JsBoolean.False : JsBoolean.True; @@ -225,6 +236,8 @@ public LessBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -248,6 +261,8 @@ public GreaterBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -271,6 +286,8 @@ public PlusBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -314,6 +331,8 @@ public MinusBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -352,6 +371,8 @@ public TimesBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -393,6 +414,8 @@ public DivideBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -419,6 +442,8 @@ public EqualBinaryExpression(BinaryExpression expression, bool invert = false) : protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -448,6 +473,8 @@ public CompareBinaryExpression(BinaryExpression expression, bool leftFirst) : ba protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var leftValue = _left.GetValue(context); var rightValue = _right.GetValue(context); @@ -473,6 +500,8 @@ public InstanceOfBinaryExpression(BinaryExpression expression) : base(expression protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var leftValue = _left.GetValue(context); var rightValue = _right.GetValue(context); return leftValue.InstanceofOperator(rightValue) ? JsBoolean.True : JsBoolean.False; @@ -487,6 +516,8 @@ public ExponentiationBinaryExpression(BinaryExpression expression) : base(expres protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var leftReference = _left.GetValue(context); var rightReference = _right.GetValue(context); @@ -612,6 +643,8 @@ public InBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -640,6 +673,8 @@ public ModuloBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var left = _left.GetValue(context); var right = _right.GetValue(context); @@ -754,6 +789,8 @@ public BitwiseBinaryExpression(BinaryExpression expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { + EnsureInitialized(); + var lval = _left.GetValue(context); var rval = _right.GetValue(context); diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs index c1f1e0abfc..a44bf7163c 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs @@ -16,13 +16,13 @@ internal sealed class JintCallExpression : JintExpression private JintExpression _calleeExpression = null!; private bool _hasSpreads; + private bool _initialized; public JintCallExpression(CallExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize(EvaluationContext context) { var expression = (CallExpression) _expression; ref readonly var expressionArguments = ref expression.Arguments; @@ -78,6 +78,12 @@ static bool CanSpread(Node? e) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(context); + _initialized = true; + } + if (!context.Engine._stackGuard.TryEnterOnCurrentStack()) { return context.Engine._stackGuard.RunOnEmptyStack(EvaluateInternal, context); diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index 51e42f766a..fe78d83ef8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -10,9 +10,6 @@ namespace Jint.Runtime.Interpreter.Expressions { internal abstract class JintExpression { - // require sub-classes to set to false explicitly to skip virtual call - protected bool _initialized = true; - protected internal readonly Expression _expression; protected JintExpression(Expression expression) @@ -43,12 +40,6 @@ public object Evaluate(EvaluationContext context) var oldSyntaxElement = context.LastSyntaxElement; context.PrepareFor(_expression); - if (!_initialized) - { - Initialize(context); - _initialized = true; - } - var result = EvaluateInternal(context); context.LastSyntaxElement = oldSyntaxElement; @@ -59,23 +50,9 @@ public object Evaluate(EvaluationContext context) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal object EvaluateWithoutNodeTracking(EvaluationContext context) { - if (!_initialized) - { - Initialize(context); - _initialized = true; - } - return EvaluateInternal(context); } - /// - /// Opportunity to build one-time structures and caching based on lexical context. - /// - /// - protected virtual void Initialize(EvaluationContext context) - { - } - protected abstract object EvaluateInternal(EvaluationContext context); /// diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs index d86d7e9784..5120b40378 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs @@ -10,10 +10,10 @@ namespace Jint.Runtime.Interpreter.Expressions; internal sealed class JintIdentifierExpression : JintExpression { private EnvironmentRecord.BindingName _identifier = null!; + private bool _initialized; public JintIdentifierExpression(Identifier expression) : base(expression) { - _initialized = false; } public EnvironmentRecord.BindingName Identifier @@ -25,7 +25,7 @@ public EnvironmentRecord.BindingName Identifier } } - protected override void Initialize(EvaluationContext context) + private void Initialize() { EnsureIdentifier(); } @@ -47,6 +47,12 @@ public bool HasEvalOrArguments protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var engine = context.Engine; var env = engine.ExecutionContext.LexicalEnvironment; var strict = StrictModeScope.IsStrictModeCode; diff --git a/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs index 5f03d8667e..c90affcf9b 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs @@ -7,14 +7,14 @@ namespace Jint.Runtime.Interpreter.Expressions; internal sealed class JintImportExpression : JintExpression { private JintExpression _importExpression; + private bool _initialized; public JintImportExpression(ImportExpression expression) : base(expression) { - _initialized = false; _importExpression = null!; } - protected override void Initialize(EvaluationContext context) + private void Initialize(EvaluationContext context) { var expression = ((ImportExpression) _expression).Source; _importExpression = Build(expression); @@ -25,6 +25,12 @@ protected override void Initialize(EvaluationContext context) /// protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(context); + _initialized = true; + } + var referencingScriptOrModule = context.Engine.GetActiveScriptOrModule(); var argRef = _importExpression.Evaluate(context); var specifier = context.Engine.GetValue(argRef); //.UnwrapIfPromise(); diff --git a/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs index a7531617b4..8a091bdb40 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs @@ -7,13 +7,13 @@ internal sealed class JintLogicalAndExpression : JintExpression { private JintExpression _left = null!; private JintExpression _right = null!; + private bool _initialized; public JintLogicalAndExpression(BinaryExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var expression = (BinaryExpression) _expression; _left = Build(expression.Left); @@ -22,6 +22,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var left = _left.GetValue(context); if (left is JsBoolean b && !b._value) diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs index f2480d2774..7ba1557516 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs @@ -14,14 +14,14 @@ internal sealed class JintMemberExpression : JintExpression private JintExpression _objectExpression = null!; private JintExpression? _propertyExpression; private JsValue? _determinedProperty; + private bool _initialized; public JintMemberExpression(MemberExpression expression) : base(expression) { - _initialized = false; _memberExpression = (MemberExpression) _expression; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { _objectExpression = Build(_memberExpression.Object); @@ -45,6 +45,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + JsValue? actualThis = null; string? baseReferenceName = null; JsValue? baseValue = null; diff --git a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs index 3b9a91e571..20d6c51aa8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs @@ -8,13 +8,13 @@ internal sealed class JintNewExpression : JintExpression private JintExpression _calleeExpression = null!; private JintExpression[] _jintArguments = Array.Empty(); private bool _hasSpreads; + private bool _initialized; public JintNewExpression(NewExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var expression = (NewExpression) _expression; _calleeExpression = Build(expression.Callee); @@ -35,6 +35,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var engine = context.Engine; // todo: optimize by defining a common abstract class or interface diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs index b02d0af123..437c7899a1 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs @@ -18,6 +18,7 @@ internal sealed class JintObjectExpression : JintExpression // check if we can do a shortcut when all are object properties // and don't require duplicate checking private bool _canBuildFast; + private bool _initialized; private sealed class ObjectProperty { @@ -54,7 +55,6 @@ public JintFunctionDefinition GetFunctionDefinition(Engine engine) private JintObjectExpression(ObjectExpression expression) : base(expression) { - _initialized = false; } public static JintExpression Build(ObjectExpression expression) @@ -64,7 +64,7 @@ public static JintExpression Build(ObjectExpression expression) : new JintObjectExpression(expression); } - protected override void Initialize(EvaluationContext context) + private void Initialize() { _canBuildFast = true; var expression = (ObjectExpression) _expression; @@ -120,6 +120,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + return _canBuildFast ? BuildObjectFast(context) : BuildObjectNormal(context); diff --git a/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs index 415960764a..b4a2148d59 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs @@ -6,13 +6,13 @@ namespace Jint.Runtime.Interpreter.Expressions internal sealed class JintSequenceExpression : JintExpression { private JintExpression[] _expressions = Array.Empty(); + private bool _initialized; public JintSequenceExpression(SequenceExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var expression = (SequenceExpression) _expression; ref readonly var expressions = ref expression.Expressions; @@ -27,6 +27,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var result = JsValue.Undefined; foreach (var expression in _expressions) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs index 88ee87c6e4..95e34407b4 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs @@ -12,13 +12,13 @@ internal sealed class JintTaggedTemplateExpression : JintExpression private JintExpression _tagIdentifier = null!; private JintTemplateLiteralExpression _quasi = null!; + private bool _initialized; public JintTaggedTemplateExpression(TaggedTemplateExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var taggedTemplateExpression = (TaggedTemplateExpression) _expression; _tagIdentifier = Build(taggedTemplateExpression.Tag); @@ -28,6 +28,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var engine = context.Engine; var identifier = _tagIdentifier.Evaluate(context); diff --git a/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs index 105f9d81af..e3b7688ac4 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs @@ -8,14 +8,14 @@ internal sealed class JintTemplateLiteralExpression : JintExpression { internal readonly TemplateLiteral _templateLiteralExpression; internal JintExpression[] _expressions = Array.Empty(); + private bool _initialized; public JintTemplateLiteralExpression(TemplateLiteral expression) : base(expression) { _templateLiteralExpression = expression; - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { DoInitialize(); } @@ -32,8 +32,14 @@ internal void DoInitialize() _initialized = true; } - private JsString BuildString(EvaluationContext context) + protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + using var sb = StringBuilderPool.Rent(); ref readonly var elements = ref _templateLiteralExpression.Quasis; for (var i = 0; i < elements.Count; i++) @@ -49,9 +55,4 @@ private JsString BuildString(EvaluationContext context) return JsString.Create(sb.ToString()); } - - protected override object EvaluateInternal(EvaluationContext context) - { - return BuildString(context); - } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs index b71da841a1..ddcec5bcb3 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs @@ -13,13 +13,13 @@ internal sealed class JintUpdateExpression : JintExpression private JintIdentifierExpression? _leftIdentifier; private bool _evalOrArguments; + private bool _initialized; public JintUpdateExpression(UpdateExpression expression) : base(expression) { - _initialized = false; } - protected override void Initialize(EvaluationContext context) + private void Initialize() { var expression = (UpdateExpression) _expression; _prefix = expression.Prefix; @@ -43,6 +43,12 @@ protected override void Initialize(EvaluationContext context) protected override object EvaluateInternal(EvaluationContext context) { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + var fastResult = _leftIdentifier != null ? UpdateIdentifier(context) : null; From 675ad495ce16806aee17751fa84b00c374c627eb Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 11 Nov 2023 11:27:37 +0200 Subject: [PATCH 36/44] Support caching of string instances in script preparation (#1678) --- Jint.Benchmark/EngineComparisonBenchmark.cs | 5 +- Jint/Engine.Ast.cs | 6 +- Jint/Engine.cs | 2 +- Jint/Native/Array/ArrayInstance.cs | 30 ++++----- Jint/Native/Function/FunctionInstance.cs | 24 +++---- Jint/Native/Iterator/IteratorResult.cs | 4 +- Jint/Native/JsString.cs | 67 ++++++++++++++----- Jint/Native/Map/JsMap.cs | 4 +- Jint/Native/Proxy/JsProxy.cs | 2 +- Jint/Native/RegExp/JsRegExp.cs | 4 +- Jint/Native/Set/JsSet.cs | 4 +- Jint/Native/String/StringInstance.cs | 8 +-- Jint/Runtime/CommonProperties.cs | 42 ++++++------ .../Runtime/Environments/EnvironmentRecord.cs | 11 +++ .../Expressions/JintCallExpression.cs | 2 +- .../Expressions/JintIdentifierExpression.cs | 2 +- .../Expressions/JintMemberExpression.cs | 33 +++++---- Jint/Runtime/References/Reference.cs | 2 +- 18 files changed, 155 insertions(+), 97 deletions(-) diff --git a/Jint.Benchmark/EngineComparisonBenchmark.cs b/Jint.Benchmark/EngineComparisonBenchmark.cs index 2277284711..eab0dfebe4 100644 --- a/Jint.Benchmark/EngineComparisonBenchmark.cs +++ b/Jint.Benchmark/EngineComparisonBenchmark.cs @@ -93,6 +93,9 @@ public void NilJS() public void YantraJS() { var engine = new YantraJS.Core.JSContext(); - engine.Eval(_files[FileName]); + // By default YantraJS is strict mode only, in strict mode + // we need to pass `this` explicitly in global context + // if script is expecting global context as `this` + engine.Eval(_files[FileName], null, engine); } } diff --git a/Jint/Engine.Ast.cs b/Jint/Engine.Ast.cs index 8e76a0e680..94e61e375c 100644 --- a/Jint/Engine.Ast.cs +++ b/Jint/Engine.Ast.cs @@ -1,5 +1,6 @@ using Esprima; using Esprima.Ast; +using Jint.Native; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter; using Jint.Runtime.Interpreter.Expressions; @@ -54,7 +55,7 @@ public void NodeVisitor(Node node) if (!_bindingNames.TryGetValue(name, out var bindingName)) { - _bindingNames[name] = bindingName = new EnvironmentRecord.BindingName(name); + _bindingNames[name] = bindingName = new EnvironmentRecord.BindingName(JsString.CachedCreate(name)); } node.AssociatedData = bindingName; @@ -63,6 +64,9 @@ public void NodeVisitor(Node node) case Nodes.Literal: node.AssociatedData = JintLiteralExpression.ConvertToJsValue((Literal) node); break; + case Nodes.MemberExpression: + node.AssociatedData = JintMemberExpression.InitializeDeterminedProperty((MemberExpression) node, cache: true); + break; case Nodes.ArrowFunctionExpression: case Nodes.FunctionDeclaration: case Nodes.FunctionExpression: diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 240d4e8b18..98f130b982 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -641,7 +641,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) private bool TryHandleStringValue(JsValue property, JsString s, ref ObjectInstance? o, out JsValue jsValue) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { jsValue = JsNumber.Create((uint) s.Length); return true; diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index 7f1ca99035..df34ed8f3e 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -107,7 +107,7 @@ internal bool CanUseFastAccess public sealed override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { return DefineLength(desc); } @@ -296,7 +296,7 @@ internal uint GetLength() protected sealed override void AddProperty(JsValue property, PropertyDescriptor descriptor) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property )) { _length = descriptor; return; @@ -307,7 +307,7 @@ protected sealed override void AddProperty(JsValue property, PropertyDescriptor protected sealed override bool TryGetProperty(JsValue property, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { descriptor = _length; return _length != null; @@ -416,7 +416,7 @@ public sealed override IEnumerable> Ge public sealed override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { return _length ?? PropertyDescriptor.Undefined; } @@ -451,7 +451,7 @@ public sealed override JsValue Get(JsValue property, JsValue receiver) return value; } - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { var length = _length?._value; if (length is not null) @@ -468,13 +468,13 @@ public sealed override bool Set(JsValue property, JsValue value, JsValue receive var isSafeSelfTarget = IsSafeSelfTarget(receiver); if (isSafeSelfTarget && CanUseFastAccess) { - if (IsArrayIndex(property, out var index)) + if (!ReferenceEquals(property, CommonProperties.Length) && IsArrayIndex(property, out var index)) { SetIndexValue(index, value, updateLength: true); return true; } - if (property == CommonProperties.Length + if (CommonProperties.Length.Equals(property) && _length is { Writable: true } && value is JsNumber jsNumber && jsNumber.IsInteger() @@ -532,7 +532,7 @@ protected internal sealed override void SetOwnProperty(JsValue property, Propert { WriteArrayValue(index, desc); } - else if (property == CommonProperties.Length) + else if (CommonProperties.Length.Equals(property)) { _length = desc; } @@ -564,33 +564,33 @@ private void TrackChanges(JsValue property, PropertyDescriptor desc, bool isArra } } - public sealed override void RemoveOwnProperty(JsValue p) + public sealed override void RemoveOwnProperty(JsValue property) { - if (IsArrayIndex(p, out var index)) + if (IsArrayIndex(property, out var index)) { Delete(index); } - if (p == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { _length = null; } - base.RemoveOwnProperty(p); + base.RemoveOwnProperty(property); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsArrayIndex(JsValue p, out uint index) { - if (p is JsNumber number) + if (p.IsNumber()) { - var value = number._value; + var value = ((JsNumber) p)._value; var intValue = (uint) value; index = intValue; return value == intValue && intValue != uint.MaxValue; } - index = ParseArrayIndex(p.ToString()); + index = !p.IsSymbol() ? ParseArrayIndex(p.ToString()) : uint.MaxValue; return index != uint.MaxValue; // 15.4 - Use an optimized version of the specification diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index 832536aff0..3e6b897288 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -126,15 +126,15 @@ internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Prototype) + if (CommonProperties.Prototype.Equals(property)) { return _prototypeDescriptor ?? PropertyDescriptor.Undefined; } - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { return _length ?? PropertyDescriptor.Undefined; } - if (property == CommonProperties.Name) + if (CommonProperties.Name.Equals(property)) { return _nameDescriptor ?? PropertyDescriptor.Undefined; } @@ -144,15 +144,15 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == CommonProperties.Prototype) + if (CommonProperties.Prototype.Equals(property)) { _prototypeDescriptor = desc; } - else if (property == CommonProperties.Length) + else if (CommonProperties.Length.Equals(property)) { _length = desc; } - else if (property == CommonProperties.Name) + else if (CommonProperties.Name.Equals(property)) { _nameDescriptor = desc; } @@ -164,15 +164,15 @@ protected internal override void SetOwnProperty(JsValue property, PropertyDescri public override void RemoveOwnProperty(JsValue property) { - if (property == CommonProperties.Prototype) + if (CommonProperties.Prototype.Equals(property)) { _prototypeDescriptor = null; } - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { _length = null; } - if (property == CommonProperties.Name) + if (CommonProperties.Name.Equals(property)) { _nameDescriptor = null; } @@ -401,7 +401,7 @@ public override IEnumerable> GetOwnPro public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Constructor) + if (CommonProperties.Constructor.Equals(property)) { return _constructor ?? PropertyDescriptor.Undefined; } @@ -411,7 +411,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == CommonProperties.Constructor) + if (CommonProperties.Constructor.Equals(property)) { _constructor = desc; } @@ -423,7 +423,7 @@ protected internal override void SetOwnProperty(JsValue property, PropertyDescri public override void RemoveOwnProperty(JsValue property) { - if (property == CommonProperties.Constructor) + if (CommonProperties.Constructor.Equals(property)) { _constructor = null; } diff --git a/Jint/Native/Iterator/IteratorResult.cs b/Jint/Native/Iterator/IteratorResult.cs index ca802fe0d0..471108e378 100644 --- a/Jint/Native/Iterator/IteratorResult.cs +++ b/Jint/Native/Iterator/IteratorResult.cs @@ -32,12 +32,12 @@ public static IteratorResult CreateKeyValueIteratorPosition(Engine engine, JsVal public override JsValue Get(JsValue property, JsValue receiver) { - if (property == CommonProperties.Value) + if (CommonProperties.Value.Equals(property)) { return _value; } - if (property == CommonProperties.Done) + if (CommonProperties.Done.Equals(property)) { return _done; } diff --git a/Jint/Native/JsString.cs b/Jint/Native/JsString.cs index c9600b1997..307281db32 100644 --- a/Jint/Native/JsString.cs +++ b/Jint/Native/JsString.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Diagnostics; using System.Text; using Jint.Runtime; @@ -12,27 +13,29 @@ public class JsString : JsValue, IEquatable, IEquatable private static readonly JsString[] _charToStringJsValue; private static readonly JsString[] _intToStringJsValue; - public static readonly JsString Empty = new JsString(""); - internal static readonly JsString NullString = new JsString("null"); - internal static readonly JsString UndefinedString = new JsString("undefined"); - internal static readonly JsString ObjectString = new JsString("object"); - internal static readonly JsString FunctionString = new JsString("function"); - internal static readonly JsString BooleanString = new JsString("boolean"); - internal static readonly JsString StringString = new JsString("string"); - internal static readonly JsString NumberString = new JsString("number"); - internal static readonly JsString BigIntString = new JsString("bigint"); - internal static readonly JsString SymbolString = new JsString("symbol"); - internal static readonly JsString DefaultString = new JsString("default"); - internal static readonly JsString NumberZeroString = new JsString("0"); - internal static readonly JsString NumberOneString = new JsString("1"); - internal static readonly JsString TrueString = new JsString("true"); - internal static readonly JsString FalseString = new JsString("false"); - internal static readonly JsString LengthString = new JsString("length"); - internal static readonly JsValue CommaString = new JsString(","); + public static readonly JsString Empty; + internal static readonly JsString NullString; + internal static readonly JsString UndefinedString; + internal static readonly JsString ObjectString; + internal static readonly JsString FunctionString; + internal static readonly JsString BooleanString; + internal static readonly JsString StringString; + internal static readonly JsString NumberString; + internal static readonly JsString BigIntString; + internal static readonly JsString SymbolString; + internal static readonly JsString DefaultString; + internal static readonly JsString NumberZeroString; + internal static readonly JsString NumberOneString; + internal static readonly JsString TrueString; + internal static readonly JsString FalseString; + internal static readonly JsString LengthString; + internal static readonly JsValue CommaString; [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal string _value; + private static ConcurrentDictionary _stringCache; + static JsString() { _charToJsValue = new JsString[AsciiMax + 1]; @@ -49,6 +52,26 @@ static JsString() { _intToStringJsValue[i] = new JsString(TypeConverter.ToString(i)); } + + + _stringCache = new ConcurrentDictionary(); + Empty = new JsString("", InternalTypes.String); + NullString = CachedCreate("null"); + UndefinedString = CachedCreate("undefined"); + ObjectString = CachedCreate("object"); + FunctionString = CachedCreate("function"); + BooleanString = CachedCreate("boolean"); + StringString = CachedCreate("string"); + NumberString = CachedCreate("number"); + BigIntString = CachedCreate("bigint"); + SymbolString = CachedCreate("symbol"); + DefaultString = CachedCreate("default"); + NumberZeroString = CachedCreate("0"); + NumberOneString = CachedCreate("1"); + TrueString = CachedCreate("true"); + FalseString = CachedCreate("false"); + LengthString = CachedCreate("length"); + CommaString = CachedCreate(","); } public JsString(string value) : this(value, InternalTypes.String) @@ -146,6 +169,16 @@ internal static JsString Create(string value) return new JsString(value); } + internal static JsString CachedCreate(string value) + { + if (value.Length < 2) + { + return Create(value); + } + + return _stringCache.GetOrAdd(value, static x => new JsString(x)); + } + internal static JsString Create(char value) { var temp = _charToJsValue; diff --git a/Jint/Native/Map/JsMap.cs b/Jint/Native/Map/JsMap.cs index 546244c98a..00bb401539 100644 --- a/Jint/Native/Map/JsMap.cs +++ b/Jint/Native/Map/JsMap.cs @@ -18,7 +18,7 @@ public JsMap(Engine engine, Realm realm) : base(engine) public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Size) + if (CommonProperties.Size.Equals(property)) { return new PropertyDescriptor(_map.Count, PropertyFlag.AllForbidden); } @@ -28,7 +28,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected override bool TryGetProperty(JsValue property, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { - if (property == CommonProperties.Size) + if (CommonProperties.Size.Equals(property)) { descriptor = new PropertyDescriptor(_map.Count, PropertyFlag.AllForbidden); return true; diff --git a/Jint/Native/Proxy/JsProxy.cs b/Jint/Native/Proxy/JsProxy.cs index cdd0364dfa..a5877e9803 100644 --- a/Jint/Native/Proxy/JsProxy.cs +++ b/Jint/Native/Proxy/JsProxy.cs @@ -128,7 +128,7 @@ public override JsValue Get(JsValue property, JsValue receiver) AssertTargetNotRevoked(property); var target = _target; - if (property == KeyFunctionRevoke || !TryCallHandler(TrapGet, new[] { target, TypeConverter.ToPropertyKey(property), receiver }, out var result)) + if (KeyFunctionRevoke.Equals(property) || !TryCallHandler(TrapGet, new[] { target, TypeConverter.ToPropertyKey(property), receiver }, out var result)) { return target.Get(property, receiver); } diff --git a/Jint/Native/RegExp/JsRegExp.cs b/Jint/Native/RegExp/JsRegExp.cs index 62f5886cdd..26782c0504 100644 --- a/Jint/Native/RegExp/JsRegExp.cs +++ b/Jint/Native/RegExp/JsRegExp.cs @@ -78,7 +78,7 @@ public string Flags public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == PropertyLastIndex) + if (PropertyLastIndex.Equals(property)) { return _prototypeDescriptor ?? PropertyDescriptor.Undefined; } @@ -88,7 +88,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == PropertyLastIndex) + if (PropertyLastIndex.Equals(property)) { _prototypeDescriptor = desc; return; diff --git a/Jint/Native/Set/JsSet.cs b/Jint/Native/Set/JsSet.cs index d13d5ef417..b5fb4d4553 100644 --- a/Jint/Native/Set/JsSet.cs +++ b/Jint/Native/Set/JsSet.cs @@ -16,7 +16,7 @@ public JsSet(Engine engine) : base(engine) public override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Size) + if (CommonProperties.Size.Equals(property)) { return new PropertyDescriptor(_set.Count, PropertyFlag.AllForbidden); } @@ -26,7 +26,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) protected override bool TryGetProperty(JsValue property, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { - if (property == CommonProperties.Size) + if (CommonProperties.Size.Equals(property)) { descriptor = new PropertyDescriptor(_set.Count, PropertyFlag.AllForbidden); return true; diff --git a/Jint/Native/String/StringInstance.cs b/Jint/Native/String/StringInstance.cs index 73b80d8556..a91d03ef09 100644 --- a/Jint/Native/String/StringInstance.cs +++ b/Jint/Native/String/StringInstance.cs @@ -35,12 +35,12 @@ private static bool IsInt32(double d, out int intValue) public sealed override PropertyDescriptor GetOwnProperty(JsValue property) { - if (property == CommonProperties.Infinity) + if (CommonProperties.Infinity.Equals(property)) { return PropertyDescriptor.Undefined; } - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { return _length ?? PropertyDescriptor.Undefined; } @@ -107,7 +107,7 @@ public sealed override List GetOwnPropertyKeys(Types types) protected internal sealed override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { _length = desc; } @@ -119,7 +119,7 @@ protected internal sealed override void SetOwnProperty(JsValue property, Propert public sealed override void RemoveOwnProperty(JsValue property) { - if (property == CommonProperties.Length) + if (CommonProperties.Length.Equals(property)) { _length = null; } diff --git a/Jint/Runtime/CommonProperties.cs b/Jint/Runtime/CommonProperties.cs index 6605e206cb..ee2e6bcd08 100644 --- a/Jint/Runtime/CommonProperties.cs +++ b/Jint/Runtime/CommonProperties.cs @@ -4,26 +4,26 @@ namespace Jint.Runtime { internal static class CommonProperties { - internal static readonly JsString Arguments = new JsString("arguments"); - internal static readonly JsString Caller = new JsString("caller"); - internal static readonly JsString Callee = new JsString("callee"); - internal static readonly JsString Constructor = new JsString("constructor"); - internal static readonly JsString Eval = new JsString("eval"); - internal static readonly JsString Infinity = new JsString("Infinity"); - internal static readonly JsString Length = new JsString("length"); - internal static readonly JsString Name = new JsString("name"); - internal static readonly JsString Prototype = new JsString("prototype"); - internal static readonly JsString Size = new JsString("size"); - internal static readonly JsString Next = new JsString("next"); - internal static readonly JsString Done = new JsString("done"); - internal static readonly JsString Value = new JsString("value"); - internal static readonly JsString Return = new JsString("return"); - internal static readonly JsString Set = new JsString("set"); - internal static readonly JsString Get = new JsString("get"); - internal static readonly JsString Writable = new JsString("writable"); - internal static readonly JsString Enumerable = new JsString("enumerable"); - internal static readonly JsString Configurable = new JsString("configurable"); - internal static readonly JsString Stack = new JsString("stack"); - internal static readonly JsString Message = new JsString("message"); + internal static readonly JsString Arguments = JsString.CachedCreate("arguments"); + internal static readonly JsString Caller = JsString.CachedCreate("caller"); + internal static readonly JsString Callee = JsString.CachedCreate("callee"); + internal static readonly JsString Constructor = JsString.CachedCreate("constructor"); + internal static readonly JsString Eval = JsString.CachedCreate("eval"); + internal static readonly JsString Infinity = JsString.CachedCreate("Infinity"); + internal static readonly JsString Length = JsString.CachedCreate("length"); + internal static readonly JsString Name = JsString.CachedCreate("name"); + internal static readonly JsString Prototype = JsString.CachedCreate("prototype"); + internal static readonly JsString Size = JsString.CachedCreate("size"); + internal static readonly JsString Next = JsString.CachedCreate("next"); + internal static readonly JsString Done = JsString.CachedCreate("done"); + internal static readonly JsString Value = JsString.CachedCreate("value"); + internal static readonly JsString Return = JsString.CachedCreate("return"); + internal static readonly JsString Set = JsString.CachedCreate("set"); + internal static readonly JsString Get = JsString.CachedCreate("get"); + internal static readonly JsString Writable = JsString.CachedCreate("writable"); + internal static readonly JsString Enumerable = JsString.CachedCreate("enumerable"); + internal static readonly JsString Configurable = JsString.CachedCreate("configurable"); + internal static readonly JsString Stack = JsString.CachedCreate("stack"); + internal static readonly JsString Message = JsString.CachedCreate("message"); } } diff --git a/Jint/Runtime/Environments/EnvironmentRecord.cs b/Jint/Runtime/Environments/EnvironmentRecord.cs index b77ad4414d..d4db2b8f93 100644 --- a/Jint/Runtime/Environments/EnvironmentRecord.cs +++ b/Jint/Runtime/Environments/EnvironmentRecord.cs @@ -130,6 +130,17 @@ public BindingName(string value) CalculatedValue = Undefined; } } + + public BindingName(JsString value) + { + var key = (Key) value.ToString(); + Key = key; + Value = value; + if (key == KnownKeys.Undefined) + { + CalculatedValue = Undefined; + } + } } private sealed class EnvironmentRecordDebugView diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs index a44bf7163c..fa0421a36f 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs @@ -115,7 +115,7 @@ protected override object EvaluateInternal(EvaluationContext context) if (ReferenceEquals(func, engine.Realm.Intrinsics.Eval) && referenceRecord != null && !referenceRecord.IsPropertyReference - && referenceRecord.ReferencedName == CommonProperties.Eval) + && CommonProperties.Eval.Equals(referenceRecord.ReferencedName)) { return HandleEval(context, func, engine, referenceRecord); } diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs index 5120b40378..864a17584f 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs @@ -109,6 +109,6 @@ public override JsValue GetValue(EvaluationContext context) [MethodImpl(MethodImplOptions.NoInlining)] private void ThrowNotInitialized(Engine engine) { - ExceptionHelper.ThrowReferenceError(engine.Realm, _identifier.Key.Name + " has not been initialized"); + ExceptionHelper.ThrowReferenceError(engine.Realm, $"{_identifier.Key.Name} has not been initialized"); } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs index 7ba1557516..d5a9f32036 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs @@ -16,38 +16,45 @@ internal sealed class JintMemberExpression : JintExpression private JsValue? _determinedProperty; private bool _initialized; + private static readonly JsValue _nullMarker = new JsString("NULL MARKER"); + public JintMemberExpression(MemberExpression expression) : base(expression) { _memberExpression = (MemberExpression) _expression; } - private void Initialize() + internal static JsValue InitializeDeterminedProperty(MemberExpression expression, bool cache) { - _objectExpression = Build(_memberExpression.Object); - - if (!_memberExpression.Computed) + JsValue? property = null; + if (!expression.Computed) { - if (_memberExpression.Property is Identifier identifier) + if (expression.Property is Identifier identifier) { - _determinedProperty = identifier.Name; + property = cache ? JsString.CachedCreate(identifier.Name) : JsString.Create(identifier.Name); } } - else if (_memberExpression.Property.Type == Nodes.Literal) + else if (expression.Property.Type == Nodes.Literal) { - _determinedProperty = JintLiteralExpression.ConvertToJsValue((Literal) _memberExpression.Property); + property = JintLiteralExpression.ConvertToJsValue((Literal) expression.Property); } - if (_determinedProperty is null) - { - _propertyExpression = Build(_memberExpression.Property); - } + return property ?? _nullMarker; } protected override object EvaluateInternal(EvaluationContext context) { if (!_initialized) { - Initialize(); + _objectExpression = Build(_memberExpression.Object); + + _determinedProperty ??= _expression.AssociatedData as JsValue ?? InitializeDeterminedProperty(_memberExpression, cache: false); + + if (ReferenceEquals(_determinedProperty, _nullMarker)) + { + _propertyExpression = Build(_memberExpression.Property); + _determinedProperty = null; + } + _initialized = true; } diff --git a/Jint/Runtime/References/Reference.cs b/Jint/Runtime/References/Reference.cs index eef5c5ba27..f1a19fb14d 100644 --- a/Jint/Runtime/References/Reference.cs +++ b/Jint/Runtime/References/Reference.cs @@ -97,7 +97,7 @@ internal void AssertValid(Realm realm) { if (_strict && (_base._type & InternalTypes.ObjectEnvironmentRecord) != 0 - && (_referencedName == CommonProperties.Eval || _referencedName == CommonProperties.Arguments)) + && (CommonProperties.Eval.Equals(_referencedName) || CommonProperties.Arguments.Equals(_referencedName))) { ExceptionHelper.ThrowSyntaxError(realm); } From 4bff351660c1cacd911a7ccac1f3976c80364e89 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 12 Nov 2023 10:46:27 +0200 Subject: [PATCH 37/44] Optimize Array.pop (#1680) --- Jint/Engine.cs | 2 +- Jint/Native/Array/ArrayInstance.cs | 98 +++++++++++++++++++---------- Jint/Native/Array/ArrayPrototype.cs | 7 ++- 3 files changed, 73 insertions(+), 34 deletions(-) diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 98f130b982..cfb9c4d207 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -618,7 +618,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) return JsValue.Undefined; } - var callable = (ICallable) getter.AsObject(); + var callable = (ICallable) getter; return callable.Call(baseValue, Arguments.Empty); } } diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index df34ed8f3e..800a960b27 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -23,6 +23,8 @@ public class ArrayInstance : ObjectInstance, IEnumerable private ObjectChangeFlags _objectChangeFlags; + private ArrayConstructor? _constructor; + private protected ArrayInstance(Engine engine, InternalTypes type) : base(engine, type: type) { _dense = System.Array.Empty(); @@ -54,7 +56,8 @@ private protected ArrayInstance(Engine engine, JsValue[] items) : base(engine, t private void InitializePrototypeAndValidateCapacity(Engine engine, uint capacity) { - _prototype = engine.Realm.Intrinsics.Array.PrototypeObject; + _constructor = engine.Realm.Intrinsics.Array; + _prototype = _constructor.PrototypeObject; if (capacity > 0 && capacity > engine.Options.Constraints.MaxArraySize) { @@ -67,7 +70,7 @@ private void InitializePrototypeAndValidateCapacity(Engine engine, uint capacity public sealed override bool IsArray() => true; internal sealed override bool HasOriginalIterator - => ReferenceEquals(Get(GlobalSymbolRegistry.Iterator), _engine.Realm.Intrinsics.Array.PrototypeObject._originalIteratorFunction); + => ReferenceEquals(Get(GlobalSymbolRegistry.Iterator), _constructor?.PrototypeObject._originalIteratorFunction); /// /// Checks whether there have been changes to object prototype chain which could render fast access patterns impossible. @@ -83,7 +86,7 @@ internal bool CanUseFastAccess } if (_prototype is not ArrayPrototype arrayPrototype - || !ReferenceEquals(_prototype, _engine.Realm.Intrinsics.Array.PrototypeObject)) + || !ReferenceEquals(_prototype, _constructor?.PrototypeObject)) { // somebody has switched prototype return false; @@ -96,7 +99,7 @@ internal bool CanUseFastAccess } if (arrayPrototype.Prototype is not ObjectPrototype arrayPrototypePrototype - || !ReferenceEquals(arrayPrototypePrototype, _engine.Realm.Intrinsics.Array.PrototypeObject.Prototype)) + || !ReferenceEquals(arrayPrototypePrototype, _constructor.PrototypeObject.Prototype)) { return false; } @@ -177,7 +180,7 @@ private bool DefineLength(PropertyDescriptor desc) { for (uint keyIndex = 0; keyIndex < _dense.Length; ++keyIndex) { - if (_dense[keyIndex] == null) + if (_dense[keyIndex] is null) { continue; } @@ -284,15 +287,10 @@ private bool DefineOwnProperty(uint index, PropertyDescriptor desc) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal uint GetLength() - { - if (_length is null) - { - return 0; - } + internal uint GetLength() => (uint) GetJsNumberLength()._value; - return (uint) ((JsNumber) _length._value!)._value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private JsNumber GetJsNumberLength() => _length is null ? JsNumber.PositiveZero : (JsNumber) _length._value!; protected sealed override void AddProperty(JsValue property, PropertyDescriptor descriptor) { @@ -330,7 +328,7 @@ public sealed override List GetOwnPropertyKeys(Types types = Types.None var length = System.Math.Min(temp.Length, GetLength()); for (var i = 0; i < length; i++) { - if (temp[i] != null) + if (temp[i] is not null) { properties.Add(JsString.Create(i)); } @@ -380,7 +378,7 @@ public sealed override IEnumerable> Ge for (uint i = 0; i < length; i++) { var value = temp[i]; - if (value != null) + if (value is not null) { if (_sparse is null || !_sparse.TryGetValue(i, out var descriptor) || descriptor is null) { @@ -633,17 +631,19 @@ private void EnsureCorrectLength(uint index) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetLength(ulong length) + internal void SetLength(ulong length) => SetLength(JsNumber.Create(length)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetLength(JsNumber length) { - var number = JsNumber.Create(length); if (Extensible && _length!._flags == PropertyFlag.OnlyWritable) { - _length!.Value = number; + _length!.Value = length; } else { // slow path - Set(CommonProperties.Length, number, true); + Set(CommonProperties.Length, length, true); } } @@ -677,33 +677,44 @@ internal bool DeletePropertyOrThrow(uint index) return true; } - internal bool Delete(uint index) + private bool Delete(uint index) => Delete(index, unwrapFromNonDataDescriptor: false, out _); + + private bool Delete(uint index, bool unwrapFromNonDataDescriptor, out JsValue? deletedValue) { + TryGetDescriptor(index, createIfMissing: false, out var desc); + // check fast path var temp = _dense; if (temp != null) { if (index < (uint) temp.Length) { - if (!TryGetDescriptor(index, createIfMissing: false, out var descriptor) || descriptor.Configurable) + if (desc is null || desc.Configurable) { + deletedValue = temp[index]; temp[index] = null; return true; } } } - if (!TryGetDescriptor(index, createIfMissing: false, out var desc)) + if (desc is null) { + deletedValue = null; return true; } if (desc.Configurable) { - DeleteAt(index); + _sparse!.Remove(index); + deletedValue = desc.IsDataDescriptor() || unwrapFromNonDataDescriptor + ? UnwrapJsValue(desc) + : null; + return true; } + deletedValue = null; return false; } @@ -728,6 +739,12 @@ internal bool DeleteAt(uint index) private bool TryGetDescriptor(uint index, bool createIfMissing, [NotNullWhen(true)] out PropertyDescriptor? descriptor) { + if (!createIfMissing && _sparse is null) + { + descriptor = null; + return false; + } + descriptor = null; var temp = _dense; if (temp != null) @@ -735,14 +752,10 @@ private bool TryGetDescriptor(uint index, bool createIfMissing, [NotNullWhen(tru if (index < (uint) temp.Length) { var value = temp[index]; - if (value != null) + if (value is not null) { if (_sparse is null || !_sparse.TryGetValue(index, out descriptor) || descriptor is null) { - if (!createIfMissing) - { - return false; - } _sparse ??= new Dictionary(); _sparse[index] = descriptor = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); } @@ -913,7 +926,7 @@ private void ConvertToSparse() for (uint i = 0; i < (uint) temp.Length; ++i) { var value = temp[i]; - if (value != null) + if (value is not null) { _sparse.TryGetValue(i, out var descriptor); descriptor ??= new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); @@ -1004,7 +1017,7 @@ private IEnumerable Enumerate() for (var i = 0; i < length; i++) { var value = temp[i]; - if (value != null) + if (value is not null) { yield return new IndexedEntry(i, value); } @@ -1107,6 +1120,27 @@ public uint Push(JsValue[] values) return (uint) n; } + public JsValue Pop() + { + var len = GetJsNumberLength(); + if (JsNumber.PositiveZero.Equals(len)) + { + SetLength(len); + return Undefined; + } + + var newLength = (uint) len._value - 1; + + if (!Delete(newLength, unwrapFromNonDataDescriptor: true, out var element)) + { + ExceptionHelper.ThrowTypeError(_engine.Realm); + } + + SetLength(newLength); + + return element ?? Undefined; + } + private bool CanSetLength() { if (!_length!.IsAccessorDescriptor()) @@ -1138,7 +1172,7 @@ internal JsArray Map(JsValue[] arguments) var len = GetLength(); var callable = GetCallable(callbackfn); - var a = Engine.Realm.Intrinsics.Array.ArrayCreate(len); + var a = _engine.Realm.Intrinsics.Array.ArrayCreate(len); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = this; for (uint k = 0; k < len; k++) @@ -1306,7 +1340,7 @@ internal void CopyValues(JsArray source, uint sourceStartIndex, uint targetStart for (var i = sourceStartIndex; i < sourceStartIndex + length; ++i, j++) { JsValue? sourceValue; - if (i < (uint) sourceDense.Length && sourceDense[i] != null) + if (i < (uint) sourceDense.Length && sourceDense[i] is not null) { sourceValue = sourceDense[i]; } diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index bdc1d4d341..7e6728435c 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -1633,6 +1633,11 @@ public JsValue Push(JsValue thisObject, JsValue[] arguments) public JsValue Pop(JsValue thisObject, JsValue[] arguments) { + if (thisObject is JsArray { CanUseFastAccess: true } array) + { + return array.Pop(); + } + var o = ArrayOperations.For(_realm, thisObject); ulong len = o.GetLongLength(); if (len == 0) @@ -1641,7 +1646,7 @@ public JsValue Pop(JsValue thisObject, JsValue[] arguments) return Undefined; } - len = len - 1; + len -= 1; JsValue element = o.Get(len); o.DeletePropertyOrThrow(len); o.SetLength(len); From 088f08f8e1f178f4b55a067d156f8c40e2511b41 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 12 Nov 2023 14:21:00 +0200 Subject: [PATCH 38/44] Enable code analysis with latest-Recommended (#1681) --- .editorconfig | 15 ++++++ Jint.Tests/Runtime/EngineTests.cs | 7 +-- Jint/Constraints/TimeConstraint.cs | 2 + Jint/Jint.csproj | 1 + Jint/Native/ArrayBuffer/JsArrayBuffer.cs | 2 + Jint/Native/BigInt/BigIntPrototype.cs | 5 +- Jint/Native/Date/DatePrototype.cs | 19 +++---- Jint/Native/Date/MimeKit.cs | 8 ++- .../FinalizationRegistryInstance.cs | 2 +- .../FinalizationRegistryPrototype.cs | 2 +- Jint/Native/Function/ClassDefinition.cs | 2 +- .../Native/Function/ScriptFunctionInstance.cs | 4 +- Jint/Native/Global/GlobalObject.cs | 30 +++++------ Jint/Native/Intl/DateTimeFormatConstructor.cs | 2 +- Jint/Native/Intl/NumberFormatConstructor.cs | 6 +-- Jint/Native/Intl/PluralRulesConstructor.cs | 2 +- .../Intl/RelativeTimeFormatConstructor.cs | 2 +- Jint/Native/JsBigInt.cs | 15 ++---- Jint/Native/JsBoolean.cs | 7 ++- Jint/Native/JsNull.cs | 15 +++--- Jint/Native/JsNumber.cs | 21 ++++---- Jint/Native/JsString.cs | 27 +++------- Jint/Native/JsSymbol.cs | 17 ++----- Jint/Native/JsUndefined.cs | 15 +++--- Jint/Native/JsValue.cs | 15 ++---- Jint/Native/Json/JsonParser.cs | 4 +- Jint/Native/Json/JsonSerializer.cs | 3 +- Jint/Native/Number/Dtoa/Bignum.cs | 18 +++---- Jint/Native/Number/NumberPrototype.cs | 50 +++++++++---------- Jint/Native/Object/ObjectInstance.cs | 11 ++-- Jint/Native/Promise/PromiseOperations.cs | 2 +- Jint/Native/Proxy/JsProxy.cs | 2 +- Jint/Native/RegExp/RegExpPrototype.cs | 6 +++ Jint/Native/String/StringPrototype.cs | 12 +++-- Jint/Native/TypedArray/JsTypedArray.cs | 3 +- .../TypedArray/TypedArrayConstructor.cs | 3 +- Jint/Options.cs | 4 +- Jint/Pooling/ConcurrentObjectPool.cs | 2 + Jint/Pooling/ObjectPool.cs | 2 + Jint/Runtime/CallStack/JintCallStack.cs | 12 +++-- Jint/Runtime/CallStack/StackGuard.cs | 4 +- Jint/Runtime/Debugger/BreakPointCollection.cs | 2 - Jint/Runtime/DefaultTimeSystem.cs | 2 + Jint/Runtime/Interop/ClrFunctionInstance.cs | 6 ++- Jint/Runtime/Interop/ClrHelper.cs | 8 ++- .../Runtime/Interop/DefaultObjectConverter.cs | 13 ++--- Jint/Runtime/Interop/DelegateWrapper.cs | 8 +-- Jint/Runtime/Interop/MethodDescriptor.cs | 2 +- Jint/Runtime/Interop/NamespaceReference.cs | 2 +- Jint/Runtime/Interop/ObjectWrapper.cs | 11 ++-- Jint/Runtime/Interop/TypeReference.cs | 6 +-- .../BindingPatternAssignmentExpression.cs | 3 +- .../Expressions/JintCallExpression.cs | 3 +- .../Expressions/JintMemberExpression.cs | 2 +- .../Interpreter/JintFunctionDefinition.cs | 2 +- .../JintExportDefaultDeclaration.cs | 2 +- .../Statements/JintForInForOfStatement.cs | 2 + Jint/Runtime/Modules/DefaultModuleLoader.cs | 2 +- Jint/Runtime/Modules/FailFastModuleLoader.cs | 2 + .../Runtime/Modules/SourceTextModuleRecord.cs | 4 +- Jint/Runtime/OrderedDictionary.cs | 2 +- Jint/Runtime/TypeConverter.cs | 12 ++--- 62 files changed, 248 insertions(+), 229 deletions(-) diff --git a/.editorconfig b/.editorconfig index 758216668c..570d06a3c5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -36,6 +36,21 @@ indent_size = 2 # IDE0055: Fix formatting dotnet_diagnostic.IDE0055.severity = warning +# Error CA1051 : Do not declare visible instance fields +dotnet_diagnostic.CA1051.severity = none + +# Error CA1720 : Identifiers should not contain type names +dotnet_diagnostic.CA1720.severity = none + +# Error CA1711: Identifiers should not have incorrect suffix +dotnet_diagnostic.CA1711.severity = none + +# Error CA1710: Identifiers should have correct suffix +dotnet_diagnostic.CA1710.severity = none + +# Error CA1716: Identifiers should have correct suffix +dotnet_diagnostic.CA1716.severity = none + # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = false diff --git a/Jint.Tests/Runtime/EngineTests.cs b/Jint.Tests/Runtime/EngineTests.cs index d898f99a8e..ef6f84c3ba 100644 --- a/Jint.Tests/Runtime/EngineTests.cs +++ b/Jint.Tests/Runtime/EngineTests.cs @@ -4,6 +4,7 @@ using Esprima.Ast; using Jint.Native; using Jint.Native.Array; +using Jint.Native.Number; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Debugger; @@ -823,8 +824,8 @@ public void ShouldHandleEscapedSlashesInRegex() [Fact] public void ShouldComputeFractionInBase() { - Assert.Equal("011", _engine.Realm.Intrinsics.Number.PrototypeObject.ToFractionBase(0.375, 2)); - Assert.Equal("14141414141414141414141414141414141414141414141414", _engine.Realm.Intrinsics.Number.PrototypeObject.ToFractionBase(0.375, 5)); + Assert.Equal("011", NumberPrototype.ToFractionBase(0.375, 2)); + Assert.Equal("14141414141414141414141414141414141414141414141414", NumberPrototype.ToFractionBase(0.375, 5)); } [Fact] @@ -910,7 +911,7 @@ public void ShouldNotAllowModifyingSharedUndefinedDescriptor() [InlineData("2qgpckvng1s", 10000000000000000L, 36)] public void ShouldConvertNumbersToDifferentBase(string expected, long number, int radix) { - var result = _engine.Realm.Intrinsics.Number.PrototypeObject.ToBase(number, radix); + var result = NumberPrototype.ToBase(number, radix); Assert.Equal(expected, result); } diff --git a/Jint/Constraints/TimeConstraint.cs b/Jint/Constraints/TimeConstraint.cs index a6b052b48d..b7f295c671 100644 --- a/Jint/Constraints/TimeConstraint.cs +++ b/Jint/Constraints/TimeConstraint.cs @@ -3,7 +3,9 @@ namespace Jint.Constraints; +#pragma warning disable CA1001 internal sealed class TimeConstraint : Constraint +#pragma warning restore CA1001 { private readonly TimeSpan _timeout; private CancellationTokenSource? _cts; diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index 1a061147e2..a83d5581a0 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -9,6 +9,7 @@ enable enable true + latest-Recommended diff --git a/Jint/Native/ArrayBuffer/JsArrayBuffer.cs b/Jint/Native/ArrayBuffer/JsArrayBuffer.cs index c745a66ce6..1c84164be5 100644 --- a/Jint/Native/ArrayBuffer/JsArrayBuffer.cs +++ b/Jint/Native/ArrayBuffer/JsArrayBuffer.cs @@ -37,7 +37,9 @@ private byte[] CreateByteDataBlock(ulong byteLength) internal byte[]? ArrayBufferData => _arrayBufferData; internal bool IsDetachedBuffer => _arrayBufferData is null; +#pragma warning disable CA1822 internal bool IsSharedArrayBuffer => false; // TODO SharedArrayBuffer +#pragma warning restore CA1822 /// /// https://tc39.es/ecma262/#sec-detacharraybuffer diff --git a/Jint/Native/BigInt/BigIntPrototype.cs b/Jint/Native/BigInt/BigIntPrototype.cs index 8d801fce9a..86875afaed 100644 --- a/Jint/Native/BigInt/BigIntPrototype.cs +++ b/Jint/Native/BigInt/BigIntPrototype.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Numerics; using Jint.Collections; using Jint.Native.Object; @@ -55,7 +56,7 @@ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments) var x = ThisBigIntValue(thisObject); //var numberFormat = (NumberFormat) Construct(_realm.Intrinsics.NumberFormat, new[] { locales, options }); // numberFormat.FormatNumeric(x); - return x._value.ToString("R"); + return x._value.ToString("R", CultureInfo.InvariantCulture); } /// @@ -103,7 +104,7 @@ private JsValue ToBigIntString(JsValue thisObject, JsValue[] arguments) if (radixMV == 10) { - return value.ToString("R"); + return value.ToString("R", CultureInfo.InvariantCulture); } var negative = value < 0; diff --git a/Jint/Native/Date/DatePrototype.cs b/Jint/Native/Date/DatePrototype.cs index 16796707d9..19abde6aa5 100644 --- a/Jint/Native/Date/DatePrototype.cs +++ b/Jint/Native/Date/DatePrototype.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Runtime.CompilerServices; using Jint.Collections; using Jint.Native.Object; @@ -775,9 +776,9 @@ private JsValue ToUtcString(JsValue thisObject, JsValue[] arguments) var weekday = _dayNames[WeekDay(tv)]; var month = _monthNames[MonthFromTime(tv)]; - var day = DateFromTime(tv).ToString("00"); + var day = DateFromTime(tv).ToString("00", CultureInfo.InvariantCulture); var yv = YearFromTime(tv); - var paddedYear = yv.ToString("0000"); + var paddedYear = yv.ToString("0000", CultureInfo.InvariantCulture); return $"{weekday}, {day} {month} {paddedYear} {TimeString(tv)}"; } @@ -1355,11 +1356,11 @@ private static string DateString(DatePresentation tv) var month = _monthNames[MonthFromTime(tv)]; var dateFromTime = DateFromTime(tv); - var day = System.Math.Max(1, dateFromTime).ToString("00"); + var day = System.Math.Max(1, dateFromTime).ToString("00", CultureInfo.InvariantCulture); var yv = YearFromTime(tv); var yearSign = yv < 0 ? "-" : ""; var year = System.Math.Abs(yv); - var paddedYear = year.ToString("0000"); + var paddedYear = year.ToString("0000", CultureInfo.InvariantCulture); return weekday + " " + month + " " + day + " " + yearSign + paddedYear; } @@ -1369,9 +1370,9 @@ private static string DateString(DatePresentation tv) /// private static string TimeString(DatePresentation t) { - var hour = HourFromTime(t).ToString("00"); - var minute = MinFromTime(t).ToString("00"); - var second = SecFromTime(t).ToString("00"); + var hour = HourFromTime(t).ToString("00", CultureInfo.InvariantCulture); + var minute = MinFromTime(t).ToString("00", CultureInfo.InvariantCulture); + var second = SecFromTime(t).ToString("00", CultureInfo.InvariantCulture); return hour + ":" + minute + ":" + second + " GMT"; } @@ -1396,8 +1397,8 @@ private string TimeZoneString(DatePresentation tv) absOffset = -1 * offset; } - var offsetMin = MinFromTime(absOffset).ToString("00"); - var offsetHour = HourFromTime(absOffset).ToString("00"); + var offsetMin = MinFromTime(absOffset).ToString("00", CultureInfo.InvariantCulture); + var offsetHour = HourFromTime(absOffset).ToString("00", CultureInfo.InvariantCulture); var tzName = " (" + _timeSystem.DefaultTimeZone.StandardName + ")"; diff --git a/Jint/Native/Date/MimeKit.cs b/Jint/Native/Date/MimeKit.cs index 3672ac480d..61ba981024 100644 --- a/Jint/Native/Date/MimeKit.cs +++ b/Jint/Native/Date/MimeKit.cs @@ -180,6 +180,7 @@ static DateUtils() any[1] = (char) c; } +#pragma warning disable CA2249 if (NumericZoneCharacters.IndexOf((char) c) == -1) datetok[c] |= DateTokenFlags.NonNumericZone; if (AlphaZoneCharacters.IndexOf((char) c) == -1) @@ -192,6 +193,7 @@ static DateUtils() datetok[c] |= DateTokenFlags.NonMonth; if (TimeCharacters.IndexOf((char) c) == -1) datetok[c] |= DateTokenFlags.NonTime; +#pragma warning restore CA2249 } datetok[':'] |= DateTokenFlags.HasColon; @@ -518,7 +520,9 @@ private static bool TryParseUnknownDateFormat(IList tokens, byte[] te int endIndex = tokens[i].Start + tokens[i].Length; int index = tokens[i].Start; +#pragma warning disable CA1806 ParseUtils.TryParseInt32(text, ref index, endIndex, out value); +#pragma warning restore CA1806 if (month == null && value > 0 && value <= 12) { @@ -727,7 +731,9 @@ public static bool SkipCommentsAndWhiteSpace(byte[] text, ref int index, int end if (!SkipComment(text, ref index, endIndex)) { if (throwOnError) - throw new Exception($"Incomplete comment token at offset {startIndex}"); + { + throw new ArgumentException($"Incomplete comment token at offset {startIndex}"); + } return false; } diff --git a/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs b/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs index f6b1529b32..1dd7e6dfc9 100644 --- a/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs +++ b/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs @@ -19,7 +19,7 @@ public FinalizationRegistryInstance(Engine engine, Realm realm, ICallable cleanu _callable = engine._host.MakeJobCallBack(cleanupCallback); } - public void CleanupFinalizationRegistry(ICallable? callback) + public static void CleanupFinalizationRegistry(ICallable? callback) { } diff --git a/Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs b/Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs index 0f39c5e6dd..22df9c2c35 100644 --- a/Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs +++ b/Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs @@ -98,7 +98,7 @@ private JsValue CleanupSome(JsValue thisObject, JsValue[] arguments) ExceptionHelper.ThrowTypeError(_realm, callback + " must be callable"); } - finalizationRegistry.CleanupFinalizationRegistry(callback as ICallable); + FinalizationRegistryInstance.CleanupFinalizationRegistry(callback as ICallable); return Undefined; } diff --git a/Jint/Native/Function/ClassDefinition.cs b/Jint/Native/Function/ClassDefinition.cs index 24a8fa7f4a..f82d7fba2e 100644 --- a/Jint/Native/Function/ClassDefinition.cs +++ b/Jint/Native/Function/ClassDefinition.cs @@ -276,7 +276,7 @@ private static ClassFieldDefinition ClassFieldDefinitionEvaluation(Engine engine private sealed class ClassFieldFunction : Node, IFunction { - private readonly NodeList _nodeList = new(); + private readonly NodeList _nodeList; private readonly BlockStatement _statement; public ClassFieldFunction(Expression expression) : base(Nodes.ExpressionStatement) diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index d794635ada..b01c08f7ee 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -57,7 +57,7 @@ internal ScriptFunctionInstance( /// /// https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist /// - protected internal override JsValue Call(JsValue thisArgument, JsValue[] arguments) + protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) { var strict = _functionDefinition.Strict || _thisMode == FunctionThisMode.Strict; using (new StrictModeScope(strict, true)) @@ -71,7 +71,7 @@ protected internal override JsValue Call(JsValue thisArgument, JsValue[] argumen ExceptionHelper.ThrowTypeError(calleeContext.Realm, $"Class constructor {_functionDefinition.Name} cannot be invoked without 'new'"); } - OrdinaryCallBindThis(calleeContext, thisArgument); + OrdinaryCallBindThis(calleeContext, thisObject); // actual call var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine); diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index c3d053d956..86ee63c231 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -138,7 +138,7 @@ public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments) if (trimmedString[0] == '-') { i++; - if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("-Infinity")) + if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("-Infinity", StringComparison.Ordinal)) { return JsNumber.DoubleNegativeInfinity; } @@ -147,18 +147,18 @@ public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments) if (trimmedString[0] == '+') { i++; - if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("+Infinity")) + if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("+Infinity", StringComparison.Ordinal)) { return JsNumber.DoublePositiveInfinity; } } - if (trimmedString.StartsWith("Infinity")) + if (trimmedString.StartsWith("Infinity", StringComparison.Ordinal)) { return JsNumber.DoublePositiveInfinity; } - if (trimmedString.StartsWith("NaN")) + if (trimmedString.StartsWith("NaN", StringComparison.Ordinal)) { return JsNumber.DoubleNaN; } @@ -462,7 +462,9 @@ private JsValue Decode(string uriString, string? reservedSet) if ((B & 0x80) == 0) { C = (char)B; +#pragma warning disable CA2249 if (reservedSet == null || reservedSet.IndexOf(C) == -1) +#pragma warning restore CA2249 { _stringBuilder.Append(C); } @@ -590,7 +592,7 @@ private static bool IsDigit(char c, int radix, out int result) /// public JsValue Escape(JsValue thisObject, JsValue[] arguments) { - const string whiteList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./"; + const string WhiteList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./"; var uriString = TypeConverter.ToString(arguments.At(0)); var strLen = uriString.Length; @@ -601,17 +603,17 @@ public JsValue Escape(JsValue thisObject, JsValue[] arguments) for (var k = 0; k < strLen; k++) { var c = uriString[k]; - if (whiteList.IndexOf(c) != -1) + if (WhiteList.IndexOf(c) != -1) { _stringBuilder.Append(c); } else if (c < 256) { - _stringBuilder.Append($"%{((int) c):X2}"); + _stringBuilder.Append('%').AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", (int) c); } else { - _stringBuilder.Append($"%u{((int) c):X4}"); + _stringBuilder.Append("%u").AppendFormat(CultureInfo.InvariantCulture, "{0:X4}", (int) c); } } @@ -639,18 +641,16 @@ public JsValue Unescape(JsValue thisObject, JsValue[] arguments) && uriString[k + 1] == 'u' && uriString.Skip(k + 2).Take(4).All(IsValidHexaChar)) { - c = (char)int.Parse( - string.Join(string.Empty, uriString.Skip(k + 2).Take(4)), - NumberStyles.AllowHexSpecifier); + var joined = string.Join(string.Empty, uriString.Skip(k + 2).Take(4)); + c = (char) int.Parse(joined, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); k += 5; } else if (k <= strLen - 3 - && uriString.Skip(k + 1).Take(2).All(IsValidHexaChar)) + && uriString.Skip(k + 1).Take(2).All(IsValidHexaChar)) { - c = (char)int.Parse( - string.Join(string.Empty, uriString.Skip(k + 1).Take(2)), - NumberStyles.AllowHexSpecifier); + var joined = string.Join(string.Empty, uriString.Skip(k + 1).Take(2)); + c = (char) int.Parse(joined, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); k += 2; } diff --git a/Jint/Native/Intl/DateTimeFormatConstructor.cs b/Jint/Native/Intl/DateTimeFormatConstructor.cs index bffd0a861a..11c1d4d5ee 100644 --- a/Jint/Native/Intl/DateTimeFormatConstructor.cs +++ b/Jint/Native/Intl/DateTimeFormatConstructor.cs @@ -48,7 +48,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) /// /// https://tc39.es/ecma402/#sec-initializedatetimeformat /// - private void InitializeDateTimeFormat(JsObject dateTimeFormat, JsValue locales, JsValue options) + private static void InitializeDateTimeFormat(JsObject dateTimeFormat, JsValue locales, JsValue options) { // TODO } diff --git a/Jint/Native/Intl/NumberFormatConstructor.cs b/Jint/Native/Intl/NumberFormatConstructor.cs index 1589b1b889..a180225ac4 100644 --- a/Jint/Native/Intl/NumberFormatConstructor.cs +++ b/Jint/Native/Intl/NumberFormatConstructor.cs @@ -201,7 +201,7 @@ private void SetNumberFormatUnitOptions(JsObject intlObj, JsValue options) /// /// https://tc39.es/ecma402/#sec-iswellformedunitidentifier /// - private bool IsWellFormedUnitIdentifier(JsValue unitIdentifier) + private static bool IsWellFormedUnitIdentifier(JsValue unitIdentifier) { var value = unitIdentifier.ToString(); if (IsSanctionedSingleUnitIdentifier(value)) @@ -209,8 +209,8 @@ private bool IsWellFormedUnitIdentifier(JsValue unitIdentifier) return true; } - var i = value.IndexOf("-per-"); - if (i == -1 || value.IndexOf("-per-", i + 1) != -1) + var i = value.IndexOf("-per-", StringComparison.Ordinal); + if (i == -1 || value.IndexOf("-per-", i + 1, StringComparison.Ordinal) != -1) { return false; } diff --git a/Jint/Native/Intl/PluralRulesConstructor.cs b/Jint/Native/Intl/PluralRulesConstructor.cs index 66b0052304..877ebe7488 100644 --- a/Jint/Native/Intl/PluralRulesConstructor.cs +++ b/Jint/Native/Intl/PluralRulesConstructor.cs @@ -48,7 +48,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) /// /// https://tc39.es/ecma402/#sec-initializepluralrules /// - private void InitializePluralRules(JsObject pluralRules, JsValue locales, JsValue options) + private static void InitializePluralRules(JsObject pluralRules, JsValue locales, JsValue options) { // TODO } diff --git a/Jint/Native/Intl/RelativeTimeFormatConstructor.cs b/Jint/Native/Intl/RelativeTimeFormatConstructor.cs index 0afb13dca2..0679add371 100644 --- a/Jint/Native/Intl/RelativeTimeFormatConstructor.cs +++ b/Jint/Native/Intl/RelativeTimeFormatConstructor.cs @@ -48,7 +48,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) /// /// https://tc39.es/ecma402/#sec-InitializeRelativeTimeFormat /// - private void InitializeRelativeTimeFormat(JsObject relativeTimeFormat, JsValue locales, JsValue options) + private static void InitializeRelativeTimeFormat(JsObject relativeTimeFormat, JsValue locales, JsValue options) { // TODO } diff --git a/Jint/Native/JsBigInt.cs b/Jint/Native/JsBigInt.cs index a9d2867b2f..18cbe8e0fd 100644 --- a/Jint/Native/JsBigInt.cs +++ b/Jint/Native/JsBigInt.cs @@ -95,15 +95,9 @@ public override bool IsLooselyEqual(JsValue value) return false; } - public override bool Equals(object? other) - { - return Equals(other as JsBigInt); - } + public override bool Equals(object? obj) => Equals(obj as JsBigInt); - public override bool Equals(JsValue? other) - { - return Equals(other as JsBigInt); - } + public override bool Equals(JsValue? other) => Equals(other as JsBigInt); public bool Equals(JsBigInt? other) { @@ -115,8 +109,5 @@ public bool Equals(JsBigInt? other) return ReferenceEquals(this, other) || _value == other._value; } - public override int GetHashCode() - { - return _value.GetHashCode(); - } + public override int GetHashCode() => _value.GetHashCode(); } diff --git a/Jint/Native/JsBoolean.cs b/Jint/Native/JsBoolean.cs index e735c5d94a..c74866a672 100644 --- a/Jint/Native/JsBoolean.cs +++ b/Jint/Native/JsBoolean.cs @@ -38,11 +38,16 @@ public override bool IsLooselyEqual(JsValue value) return !value.IsNullOrUndefined() && base.IsLooselyEqual(value); } - public override bool Equals(JsValue? obj) + public override bool Equals(object? obj) { return Equals(obj as JsBoolean); } + public override bool Equals(JsValue? other) + { + return Equals(other as JsBoolean); + } + public bool Equals(JsBoolean? other) { if (ReferenceEquals(this, other)) diff --git a/Jint/Native/JsNull.cs b/Jint/Native/JsNull.cs index ee65963f12..96179300f4 100644 --- a/Jint/Native/JsNull.cs +++ b/Jint/Native/JsNull.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Jint.Runtime; namespace Jint.Native; @@ -17,13 +18,11 @@ public override bool IsLooselyEqual(JsValue value) return ReferenceEquals(Null, value) || ReferenceEquals(Undefined, value); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as JsNull); - } + public override bool Equals(object? obj) => Equals(obj as JsNull); - public bool Equals(JsNull? other) - { - return other is not null; - } + public override bool Equals(JsValue? other) => Equals(other as JsNull); + + public bool Equals(JsNull? other) => other is not null; + + public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); } diff --git a/Jint/Native/JsNumber.cs b/Jint/Native/JsNumber.cs index df6267717f..e4cb66833d 100644 --- a/Jint/Native/JsNumber.cs +++ b/Jint/Native/JsNumber.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using Jint.Native.Number; @@ -90,10 +91,10 @@ internal static JsNumber Create(object value) var underlyingType = System.Type.GetTypeCode(Enum.GetUnderlyingType(value.GetType())); return underlyingType switch { - TypeCode.Int64 => Create(Convert.ToInt64(value)), - TypeCode.UInt32 => Create(Convert.ToUInt64(value)), - TypeCode.UInt64 => Create(Convert.ToUInt64(value)), - _ => Create(Convert.ToInt32(value)) + TypeCode.Int64 => Create(Convert.ToInt64(value, CultureInfo.InvariantCulture)), + TypeCode.UInt32 => Create(Convert.ToUInt64(value, CultureInfo.InvariantCulture)), + TypeCode.UInt64 => Create(Convert.ToUInt64(value, CultureInfo.InvariantCulture)), + _ => Create(Convert.ToInt32(value, CultureInfo.InvariantCulture)) }; } @@ -265,10 +266,9 @@ public override bool IsLooselyEqual(JsValue value) return base.IsLooselyEqual(value); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as JsNumber); - } + public override bool Equals(object? obj) => Equals(obj as JsNumber); + + public override bool Equals(JsValue? other) => Equals(other as JsNumber); public bool Equals(JsNumber? other) { @@ -290,8 +290,5 @@ public bool Equals(JsNumber? other) return _value == other._value; } - public override int GetHashCode() - { - return _value.GetHashCode(); - } + public override int GetHashCode() => _value.GetHashCode(); } diff --git a/Jint/Native/JsString.cs b/Jint/Native/JsString.cs index 307281db32..819c232f77 100644 --- a/Jint/Native/JsString.cs +++ b/Jint/Native/JsString.cs @@ -286,15 +286,11 @@ internal string Substring(int startIndex) return ToString().Substring(startIndex); } - public sealed override bool Equals(JsValue? obj) - { - return Equals(obj as JsString); - } + public sealed override bool Equals(object? obj) => Equals(obj as JsString); - public virtual bool Equals(string? s) - { - return s != null && ToString() == s; - } + public sealed override bool Equals(JsValue? other) => Equals(other as JsString); + + public virtual bool Equals(string? other) => other != null && ToString() == other; public virtual bool Equals(JsString? other) { @@ -326,15 +322,7 @@ public override bool IsLooselyEqual(JsValue value) return base.IsLooselyEqual(value); } - public sealed override bool Equals(object? obj) - { - return Equals(obj as JsString); - } - - public override int GetHashCode() - { - return _value.GetHashCode(); - } + public override int GetHashCode() => _value.GetHashCode(); internal sealed class ConcatenatedString : JsString { @@ -445,10 +433,7 @@ public override bool Equals(JsString? other) return ToString() == other.ToString(); } - public override int GetHashCode() - { - return _stringBuilder?.GetHashCode() ?? _value.GetHashCode(); - } + public override int GetHashCode() => _stringBuilder?.GetHashCode() ?? _value.GetHashCode(); internal override JsValue DoClone() { diff --git a/Jint/Native/JsSymbol.cs b/Jint/Native/JsSymbol.cs index d2f3262891..664d1a3383 100644 --- a/Jint/Native/JsSymbol.cs +++ b/Jint/Native/JsSymbol.cs @@ -27,18 +27,11 @@ public override string ToString() return "Symbol(" + value + ")"; } - public override bool Equals(JsValue? obj) - { - return Equals(obj as JsSymbol); - } + public override bool Equals(object? obj) => Equals(obj as JsSymbol); - public bool Equals(JsSymbol? other) - { - return ReferenceEquals(this, other); - } + public override bool Equals(JsValue? other) => Equals(other as JsSymbol); - public override int GetHashCode() - { - return RuntimeHelpers.GetHashCode(this); - } + public bool Equals(JsSymbol? other) => ReferenceEquals(this, other); + + public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); } diff --git a/Jint/Native/JsUndefined.cs b/Jint/Native/JsUndefined.cs index 745f99dd45..ea7ab6a75d 100644 --- a/Jint/Native/JsUndefined.cs +++ b/Jint/Native/JsUndefined.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Jint.Runtime; namespace Jint.Native; @@ -17,13 +18,11 @@ public override bool IsLooselyEqual(JsValue value) return ReferenceEquals(Undefined, value) || ReferenceEquals(Null, value); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as JsUndefined); - } + public override bool Equals(object? obj) => Equals(obj as JsUndefined); - public bool Equals(JsUndefined? other) - { - return !ReferenceEquals(null, other); - } + public override bool Equals(JsValue? other) => Equals(other as JsUndefined); + + public bool Equals(JsUndefined? other) => !ReferenceEquals(null, other); + + public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); } diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index ae5e6b1ac4..1a6e413efb 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -350,23 +350,14 @@ public virtual bool IsLooselyEqual(JsValue value) /// /// Strict equality. /// - public override bool Equals(object? obj) - { - return Equals(obj as JsValue); - } + public override bool Equals(object? obj) => Equals(obj as JsValue); /// /// Strict equality. /// - public virtual bool Equals(JsValue? other) - { - return ReferenceEquals(this, other); - } + public virtual bool Equals(JsValue? other) => ReferenceEquals(this, other); - public override int GetHashCode() - { - return _type.GetHashCode(); - } + public override int GetHashCode() => _type.GetHashCode(); /// /// Some values need to be cloned in order to be assigned, like ConcatenatedString. diff --git a/Jint/Native/Json/JsonParser.cs b/Jint/Native/Json/JsonParser.cs index f23e9aae49..449371e4b5 100644 --- a/Jint/Native/Json/JsonParser.cs +++ b/Jint/Native/Json/JsonParser.cs @@ -460,7 +460,7 @@ private void ThrowError(Token token, string messageFormat, params object[] argum [DoesNotReturn] private void ThrowError(int position, string messageFormat, params object[] arguments) { - string msg = System.String.Format(messageFormat, arguments); + var msg = string.Format(CultureInfo.InvariantCulture, messageFormat, arguments); ExceptionHelper.ThrowSyntaxError(_engine.Realm, $"{msg} at position {position}"); } @@ -756,7 +756,7 @@ private sealed class Token public char FirstCharacter; public JsValue Value = JsValue.Undefined; public string Text = null!; - public TextRange Range = default; + public TextRange Range; } private readonly struct TextRange diff --git a/Jint/Native/Json/JsonSerializer.cs b/Jint/Native/Json/JsonSerializer.cs index fe17770993..c13da3cb97 100644 --- a/Jint/Native/Json/JsonSerializer.cs +++ b/Jint/Native/Json/JsonSerializer.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Runtime.CompilerServices; using System.Text; using Jint.Collections; @@ -398,7 +399,7 @@ private static void AppendJsonStringCharacter(string value, ref int index, Strin else if (c < 0x20 || char.IsSurrogate(c)) { target.Append("\\u"); - target.Append(((int) c).ToString("x4")); + target.Append(((int) c).ToString("x4", CultureInfo.InvariantCulture)); } else { diff --git a/Jint/Native/Number/Dtoa/Bignum.cs b/Jint/Native/Number/Dtoa/Bignum.cs index ac874ccbe3..8c8196e469 100644 --- a/Jint/Native/Number/Dtoa/Bignum.cs +++ b/Jint/Native/Number/Dtoa/Bignum.cs @@ -108,7 +108,7 @@ void Align(Bignum other) // We replace some of the hidden digits (X) of a with 0 digits. // a: aaaaaa000X or a: aaaaa0XX int zero_digits = exponent_ - other.exponent_; - EnsureCapacity(used_digits_ + zero_digits); + ValidateCapacity(used_digits_ + zero_digits); for (int i = used_digits_ - 1; i >= 0; --i) { bigits_[i + zero_digits] = bigits_[i]; @@ -126,7 +126,7 @@ void Align(Bignum other) } } - void EnsureCapacity(int size) + private static void ValidateCapacity(int size) { if (size > kBigitCapacity) { @@ -161,7 +161,7 @@ internal void AssignUInt16(uint value) Zero(); if (value == 0) return; - EnsureCapacity(1); + ValidateCapacity(1); bigits_[0] = value; used_digits_ = 1; } @@ -174,7 +174,7 @@ internal void AssignUInt64(ulong value) if (value == 0) return; int needed_bigits = kUInt64Size / kBigitSize + 1; - EnsureCapacity(needed_bigits); + ValidateCapacity(needed_bigits); for (int i = 0; i < needed_bigits; ++i) { bigits_[i] = (uint) (value & kBigitMask); @@ -419,7 +419,7 @@ internal void MultiplyByUInt32(uint factor) while (carry != 0) { - EnsureCapacity(used_digits_ + 1); + ValidateCapacity(used_digits_ + 1); bigits_[used_digits_] = (uint) (carry & kBigitMask); used_digits_++; carry >>= kBigitSize; @@ -449,7 +449,7 @@ internal void MultiplyByUInt64(ulong factor) } while (carry != 0) { - EnsureCapacity(used_digits_ + 1); + ValidateCapacity(used_digits_ + 1); bigits_[used_digits_] = (uint) (carry & kBigitMask); used_digits_++; carry >>= kBigitSize; @@ -461,7 +461,7 @@ internal void ShiftLeft(int shift_amount) if (used_digits_ == 0) return; exponent_ += shift_amount / kBigitSize; int local_shift = shift_amount % kBigitSize; - EnsureCapacity(used_digits_ + 1); + ValidateCapacity(used_digits_ + 1); BigitsShiftLeft(local_shift); } @@ -516,7 +516,7 @@ internal void AssignPowerUInt16(uint baseValue, int power_exponent) int final_size = bit_size * power_exponent; // 1 extra bigit for the shifting, and one for rounded final_size. - EnsureCapacity(final_size / kBigitSize + 2); + ValidateCapacity(final_size / kBigitSize + 2); // Left to Right exponentiation. int mask = 1; @@ -578,7 +578,7 @@ void Square() { Debug.Assert(IsClamped()); int product_length = 2 * used_digits_; - EnsureCapacity(product_length); + ValidateCapacity(product_length); // Comba multiplication: compute each column separately. // Example: r = a2a1a0 * b2b1b0. diff --git a/Jint/Native/Number/NumberPrototype.cs b/Jint/Native/Number/NumberPrototype.cs index af302d7e5a..333e5a80d0 100644 --- a/Jint/Native/Number/NumberPrototype.cs +++ b/Jint/Native/Number/NumberPrototype.cs @@ -303,7 +303,7 @@ private JsValue ToPrecision(JsValue thisObject, JsValue[] arguments) } } - private string CreateExponentialRepresentation( + private static string CreateExponentialRepresentation( DtoaBuilder buffer, int exponent, bool negative, @@ -384,63 +384,59 @@ private JsValue ToNumberString(JsValue thisObject, JsValue[] arguments) var integer = (long) x; var fraction = x - integer; - string result = ToBase(integer, radix); + string result = NumberPrototype.ToBase(integer, radix); if (fraction != 0) { - result += "." + ToFractionBase(fraction, radix); + result += "." + NumberPrototype.ToFractionBase(fraction, radix); } return result; } - public string ToBase(long n, int radix) + internal static string ToBase(long n, int radix) { - const string digits = "0123456789abcdefghijklmnopqrstuvwxyz"; + const string Digits = "0123456789abcdefghijklmnopqrstuvwxyz"; if (n == 0) { return "0"; } - using (var result = StringBuilderPool.Rent()) + using var result = StringBuilderPool.Rent(); + while (n > 0) { - while (n > 0) - { - var digit = (int) (n % radix); - n = n / radix; - result.Builder.Insert(0, digits[digit]); - } - - return result.ToString(); + var digit = (int) (n % radix); + n = n / radix; + result.Builder.Insert(0, Digits[digit]); } + + return result.ToString(); } - public string ToFractionBase(double n, int radix) + internal static string ToFractionBase(double n, int radix) { // based on the repeated multiplication method // http://www.mathpath.org/concepts/Num/frac.htm - const string digits = "0123456789abcdefghijklmnopqrstuvwxyz"; + const string Digits = "0123456789abcdefghijklmnopqrstuvwxyz"; if (n == 0) { return "0"; } - using (var result = StringBuilderPool.Rent()) + using var result = StringBuilderPool.Rent(); + while (n > 0 && result.Length < 50) // arbitrary limit { - while (n > 0 && result.Length < 50) // arbitrary limit - { - var c = n*radix; - var d = (int) c; - n = c - d; - - result.Builder.Append(digits[d]); - } + var c = n*radix; + var d = (int) c; + n = c - d; - return result.ToString(); + result.Builder.Append(Digits[d]); } + + return result.ToString(); } - private string ToNumberString(double m) + private static string ToNumberString(double m) { using var stringBuilder = StringBuilderPool.Rent(); NumberToString(m, new DtoaBuilder(), stringBuilder.Builder); diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 0942141081..29e2ccc25e 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -1073,7 +1073,7 @@ private object ToObject(ObjectTraverseStack stack) TypedArrayElementType.Uint16 => typedArrayInstance.ToNativeArray(), TypedArrayElementType.Uint32 => typedArrayInstance.ToNativeArray(), TypedArrayElementType.BigUint64 => typedArrayInstance.ToNativeArray(), - _ => throw new ArgumentOutOfRangeException() + _ => throw new ArgumentOutOfRangeException("", "cannot handle element type") }; break; @@ -1475,10 +1475,9 @@ private void ThrowIncompatibleReceiver(JsValue value, string methodName) ExceptionHelper.ThrowTypeError(_engine.Realm, $"Method {methodName} called on incompatible receiver {value}"); } - public override bool Equals(JsValue? obj) - { - return Equals(obj as ObjectInstance); - } + public override bool Equals(object? obj) => Equals(obj as ObjectInstance); + + public override bool Equals(JsValue? other) => Equals(other as ObjectInstance); public bool Equals(ObjectInstance? other) { @@ -1495,6 +1494,8 @@ public bool Equals(ObjectInstance? other) return false; } + public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); + internal IEnumerable GetKeys() { var visited = new HashSet(); diff --git a/Jint/Native/Promise/PromiseOperations.cs b/Jint/Native/Promise/PromiseOperations.cs index ea7100642b..7791befbd5 100644 --- a/Jint/Native/Promise/PromiseOperations.cs +++ b/Jint/Native/Promise/PromiseOperations.cs @@ -58,7 +58,7 @@ private static Action NewPromiseReactionJob(PromiseReaction reaction, JsValue va break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(reaction), "Unknown reaction type"); } } }; diff --git a/Jint/Native/Proxy/JsProxy.cs b/Jint/Native/Proxy/JsProxy.cs index a5877e9803..76be0210dc 100644 --- a/Jint/Native/Proxy/JsProxy.cs +++ b/Jint/Native/Proxy/JsProxy.cs @@ -288,7 +288,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) /// /// https://tc39.es/ecma262/#sec-completepropertydescriptor /// - private void CompletePropertyDescriptor(PropertyDescriptor desc) + private static void CompletePropertyDescriptor(PropertyDescriptor desc) { if (desc.IsGenericDescriptor() || desc.IsDataDescriptor()) { diff --git a/Jint/Native/RegExp/RegExpPrototype.cs b/Jint/Native/RegExp/RegExpPrototype.cs index 279f229a53..cad5be23a5 100644 --- a/Jint/Native/RegExp/RegExpPrototype.cs +++ b/Jint/Native/RegExp/RegExpPrototype.cs @@ -286,9 +286,11 @@ string Evaluator(Match match) if (position >= nextSourcePosition) { +#pragma warning disable CA1845 accumulatedResult = accumulatedResult + s.Substring(nextSourcePosition, position - nextSourcePosition) + replacement; +#pragma warning restore CA1845 nextSourcePosition = position + matchLength; } @@ -299,7 +301,9 @@ string Evaluator(Match match) return accumulatedResult; } +#pragma warning disable CA1845 return accumulatedResult + s.Substring(nextSourcePosition); +#pragma warning restore CA1845 } private static string CallFunctionalReplace(JsValue replacer, List replacerArgs) @@ -347,12 +351,14 @@ internal static string GetSubstitution( case '&': sb.Append(matched); break; +#pragma warning disable CA1846 case '`': sb.Append(str.Substring(0, position)); break; case '\'': sb.Append(str.Substring(position + matched.Length)); break; +#pragma warning restore CA1846 case '<': var gtPos = replacement.IndexOf('>', i + 1); if (gtPos == -1 || namedCaptures.IsUndefined()) diff --git a/Jint/Native/String/StringPrototype.cs b/Jint/Native/String/StringPrototype.cs index c781875a96..d17530bfb8 100644 --- a/Jint/Native/String/StringPrototype.cs +++ b/Jint/Native/String/StringPrototype.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Runtime.CompilerServices; using System.Text; using Jint.Collections; @@ -228,7 +229,7 @@ private JsValue ToLocaleUpperCase(JsValue thisObject, JsValue[] arguments) { TypeConverter.CheckObjectCoercible(_engine, thisObject); var s = TypeConverter.ToString(thisObject); - return new JsString(s.ToUpper()); + return new JsString(s.ToUpper(CultureInfo.InvariantCulture)); } private JsValue ToUpperCase(JsValue thisObject, JsValue[] arguments) @@ -242,7 +243,7 @@ private JsValue ToLocaleLowerCase(JsValue thisObject, JsValue[] arguments) { TypeConverter.CheckObjectCoercible(_engine, thisObject); var s = TypeConverter.ToString(thisObject); - return new JsString(s.ToLower()); + return new JsString(s.ToLower(CultureInfo.InvariantCulture)); } private JsValue ToLowerCase(JsValue thisObject, JsValue[] arguments) @@ -666,7 +667,11 @@ static int StringIndexOf(string s, string search, int fromIndex) if (endOfLastMatch < thisString.Length) { +#if NETFRAMEWORK result.Append(thisString.Substring(endOfLastMatch)); +#else + result.Append(thisString[endOfLastMatch..]); +#endif } return result.ToString(); @@ -1168,7 +1173,8 @@ private JsValue ToWellFormed(JsValue thisObject, JsValue[] arguments) var cp = CodePointAt(s, k); if (cp.IsUnpairedSurrogate) { - result.Append("\uFFFD"); + // \uFFFD + result.Append('�'); } else { diff --git a/Jint/Native/TypedArray/JsTypedArray.cs b/Jint/Native/TypedArray/JsTypedArray.cs index 3991cc84d7..f13fa8ceb7 100644 --- a/Jint/Native/TypedArray/JsTypedArray.cs +++ b/Jint/Native/TypedArray/JsTypedArray.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Runtime.CompilerServices; using Esprima; using Jint.Native.ArrayBuffer; @@ -376,7 +377,7 @@ internal T[] ToNativeArray() { var indexedPosition = i * elementSize + byteOffset; var value = buffer.RawBytesToNumeric(_arrayElementType, indexedPosition, BitConverter.IsLittleEndian); - array[i] = (T) Convert.ChangeType(value, conversionType); + array[i] = (T) Convert.ChangeType(value, conversionType, CultureInfo.InvariantCulture); } return array; diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs index 7871980228..0d1fb2060c 100644 --- a/Jint/Native/TypedArray/TypedArrayConstructor.cs +++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Jint.Collections; using Jint.Native.Array; using Jint.Native.ArrayBuffer; @@ -271,7 +272,7 @@ internal static void FillTypedArrayInstance(JsTypedArray target, T[] values) { for (var i = 0; i < values.Length; ++i) { - target.DoIntegerIndexedElementSet(i, Convert.ToDouble(values[i])); + target.DoIntegerIndexedElementSet(i, Convert.ToDouble(values[i], CultureInfo.InvariantCulture)); } } diff --git a/Jint/Options.cs b/Jint/Options.cs index 980524e4ea..0341414a37 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -201,7 +201,7 @@ PropertyDescriptor CreateMethodInstancePropertyDescriptor(ClrFunctionInstance? f // make sure we register both lower case and upper case if (char.IsUpper(overloads.Key[0])) { - key = char.ToLower(overloads.Key[0]) + overloads.Key.Substring(1); + key = char.ToLower(overloads.Key[0], CultureInfo.InvariantCulture) + overloads.Key.Substring(1); if (prototype.HasOwnProperty(key) && prototype.GetOwnProperty(key).Value is ClrFunctionInstance lowerclrFunctionInstance) @@ -281,7 +281,7 @@ public class InteropOptions /// memory usage to grow when targeting large set and freeing of memory can be delayed due to ConditionalWeakTable semantics. /// Defaults to false. /// - public bool TrackObjectWrapperIdentity { get; set; } = false; + public bool TrackObjectWrapperIdentity { get; set; } /// /// If no known type could be guessed, objects are by default wrapped as an diff --git a/Jint/Pooling/ConcurrentObjectPool.cs b/Jint/Pooling/ConcurrentObjectPool.cs index 596579c364..72582b9a67 100644 --- a/Jint/Pooling/ConcurrentObjectPool.cs +++ b/Jint/Pooling/ConcurrentObjectPool.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1822 + #nullable disable // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. diff --git a/Jint/Pooling/ObjectPool.cs b/Jint/Pooling/ObjectPool.cs index 7eb41caeea..050c43b63e 100644 --- a/Jint/Pooling/ObjectPool.cs +++ b/Jint/Pooling/ObjectPool.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1822 + #nullable disable // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. diff --git a/Jint/Runtime/CallStack/JintCallStack.cs b/Jint/Runtime/CallStack/JintCallStack.cs index eb19b289cc..7c67db4bf1 100644 --- a/Jint/Runtime/CallStack/JintCallStack.cs +++ b/Jint/Runtime/CallStack/JintCallStack.cs @@ -62,7 +62,9 @@ public int Push(FunctionInstance functionInstance, JintExpression? expression, i _stack.Push(item); if (_statistics is not null) { +#pragma warning disable CA1854 if (_statistics.ContainsKey(item)) +#pragma warning restore CA1854 { return ++_statistics[item]; } @@ -126,7 +128,7 @@ static void AppendLocation( if (!string.IsNullOrWhiteSpace(shortDescription)) { sb - .Append(" ") + .Append(' ') .Append(shortDescription); } @@ -144,15 +146,15 @@ static void AppendLocation( var arg = element.Value.Arguments.Value[index]; sb.Append(GetPropertyKey(arg)); } - sb.Append(")"); + sb.Append(')'); } sb - .Append(" ") + .Append(' ') .Append(loc.Source) - .Append(":") + .Append(':') .Append(loc.End.Line) - .Append(":") + .Append(':') .Append(loc.Start.Column + 1) // report column number instead of index .AppendLine(); } diff --git a/Jint/Runtime/CallStack/StackGuard.cs b/Jint/Runtime/CallStack/StackGuard.cs index 2ddf5946a7..e815e02e32 100644 --- a/Jint/Runtime/CallStack/StackGuard.cs +++ b/Jint/Runtime/CallStack/StackGuard.cs @@ -50,7 +50,7 @@ public bool TryEnterOnCurrentStack() return false; } - public TR RunOnEmptyStack(Func action, T1 arg1) + public static TR RunOnEmptyStack(Func action, T1 arg1) { #if NETFRAMEWORK || NETSTANDARD2_0 return RunOnEmptyStackCore(static s => @@ -69,7 +69,7 @@ public TR RunOnEmptyStack(Func action, T1 arg1) } - private R RunOnEmptyStackCore(Func action, object state) + private static R RunOnEmptyStackCore(Func action, object state) { // Using default scheduler rather than picking up the current scheduler. Task task = Task.Factory.StartNew((Func) action, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); diff --git a/Jint/Runtime/Debugger/BreakPointCollection.cs b/Jint/Runtime/Debugger/BreakPointCollection.cs index 7407b63e5f..4a660e0629 100644 --- a/Jint/Runtime/Debugger/BreakPointCollection.cs +++ b/Jint/Runtime/Debugger/BreakPointCollection.cs @@ -25,8 +25,6 @@ public BreakPointCollection() public int Count => _breakPoints.Count; - public bool IsReadOnly => false; - /// /// Sets a new breakpoint. Note that this will replace any breakpoint at the same location (source/column/line). /// diff --git a/Jint/Runtime/DefaultTimeSystem.cs b/Jint/Runtime/DefaultTimeSystem.cs index 5340c070ec..b5c7e80661 100644 --- a/Jint/Runtime/DefaultTimeSystem.cs +++ b/Jint/Runtime/DefaultTimeSystem.cs @@ -137,7 +137,9 @@ private static bool TryParseLargeYear(string date, out long epochMilliseconds) } // create replacement string +#pragma warning disable CA1845 var dateToParse = "2000" + date.Substring(7); +#pragma warning restore CA1845 if (!DateTime.TryParse(dateToParse, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var parsed)) { return false; diff --git a/Jint/Runtime/Interop/ClrFunctionInstance.cs b/Jint/Runtime/Interop/ClrFunctionInstance.cs index 674baf3c4e..f36efceff6 100644 --- a/Jint/Runtime/Interop/ClrFunctionInstance.cs +++ b/Jint/Runtime/Interop/ClrFunctionInstance.cs @@ -57,7 +57,9 @@ private JsValue CallSlow(JsValue thisObject, JsValue[] arguments) } } - public override bool Equals(JsValue? obj) => Equals(obj as ClrFunctionInstance); + public override bool Equals(JsValue? other) => Equals(other as ClrFunctionInstance); + + public override bool Equals(object? obj) => Equals(obj as ClrFunctionInstance); public bool Equals(ClrFunctionInstance? other) { @@ -78,4 +80,6 @@ public bool Equals(ClrFunctionInstance? other) return false; } + + public override int GetHashCode() => _func.GetHashCode(); } diff --git a/Jint/Runtime/Interop/ClrHelper.cs b/Jint/Runtime/Interop/ClrHelper.cs index e11763216c..f59fcc1faa 100644 --- a/Jint/Runtime/Interop/ClrHelper.cs +++ b/Jint/Runtime/Interop/ClrHelper.cs @@ -14,7 +14,9 @@ internal ClrHelper(InteropOptions interopOptions) /// /// Call JsValue.ToString(), mainly for NamespaceReference. /// +#pragma warning disable CA1822 public JsValue ToString(JsValue value) +#pragma warning restore CA1822 { return value.ToString(); } @@ -22,7 +24,9 @@ public JsValue ToString(JsValue value) /// /// Cast `obj as ISomeInterface` to `obj` /// +#pragma warning disable CA1822 public JsValue Unwrap(ObjectWrapper obj) +#pragma warning restore CA1822 { return new ObjectWrapper(obj.Engine, obj.Target); } @@ -30,7 +34,9 @@ public JsValue Unwrap(ObjectWrapper obj) /// /// Cast `obj` to `obj as ISomeInterface` /// +#pragma warning disable CA1822 public JsValue Wrap(ObjectWrapper obj, TypeReference type) +#pragma warning restore CA1822 { if (!type.ReferenceType.IsInstanceOfType(obj.Target)) { @@ -70,7 +76,7 @@ public JsValue ObjectToType(ObjectWrapper obj) } else { - ExceptionHelper.ThrowArgumentException("Must be an ObjectWrapper of Type", "obj"); + ExceptionHelper.ThrowArgumentException("Must be an ObjectWrapper of Type", nameof(obj)); } return JsValue.Undefined; } diff --git a/Jint/Runtime/Interop/DefaultObjectConverter.cs b/Jint/Runtime/Interop/DefaultObjectConverter.cs index acf36264c9..f94fa7bf10 100644 --- a/Jint/Runtime/Interop/DefaultObjectConverter.cs +++ b/Jint/Runtime/Interop/DefaultObjectConverter.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Runtime.CompilerServices; using System.Threading; using Jint.Native; @@ -74,10 +75,10 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW var t = value.GetType(); if (!engine.Options.Interop.AllowSystemReflection - && t.Namespace?.StartsWith("System.Reflection") == true) + && t.Namespace?.StartsWith("System.Reflection", StringComparison.Ordinal) == true) { - const string message = "Cannot access System.Reflection namespace, check Engine's interop options"; - ExceptionHelper.ThrowInvalidOperationException(message); + const string Message = "Cannot access System.Reflection namespace, check Engine's interop options"; + ExceptionHelper.ThrowInvalidOperationException(Message); } if (t.IsEnum) @@ -86,17 +87,17 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW if (ut == typeof(ulong)) { - result = JsNumber.Create(Convert.ToDouble(value)); + result = JsNumber.Create(Convert.ToDouble(value, CultureInfo.InvariantCulture)); } else { if (ut == typeof(uint) || ut == typeof(long)) { - result = JsNumber.Create(Convert.ToInt64(value)); + result = JsNumber.Create(Convert.ToInt64(value, CultureInfo.InvariantCulture)); } else { - result = JsNumber.Create(Convert.ToInt32(value)); + result = JsNumber.Create(Convert.ToInt32(value, CultureInfo.InvariantCulture)); } } } diff --git a/Jint/Runtime/Interop/DelegateWrapper.cs b/Jint/Runtime/Interop/DelegateWrapper.cs index 36bd49b898..4a6c1e1f4e 100644 --- a/Jint/Runtime/Interop/DelegateWrapper.cs +++ b/Jint/Runtime/Interop/DelegateWrapper.cs @@ -36,7 +36,7 @@ public DelegateWrapper( } } - protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArguments) + protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) { var parameterInfos = _d.Method.GetParameters(); @@ -53,7 +53,7 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArgumen int delegateArgumentsCount = parameterInfos.Length; int delegateNonParamsArgumentsCount = _delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount; - int jsArgumentsCount = jsArguments.Length; + int jsArgumentsCount = arguments.Length; int jsArgumentsWithoutParamsCount = Math.Min(jsArgumentsCount, delegateNonParamsArgumentsCount); var clrTypeConverter = Engine.ClrTypeConverter; @@ -64,7 +64,7 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArgumen for (var i = 0; i < jsArgumentsWithoutParamsCount; i++) { var parameterType = parameterInfos[i].ParameterType; - var value = jsArguments[i]; + var value = arguments[i]; object? converted; if (parameterType == typeof(JsValue)) @@ -107,7 +107,7 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] jsArgumen for (var i = paramsArgumentIndex; i < jsArgumentsCount; i++) { var paramsIndex = i - paramsArgumentIndex; - var value = jsArguments[i]; + var value = arguments[i]; object? converted; if (paramsParameterType == typeof(JsValue)) diff --git a/Jint/Runtime/Interop/MethodDescriptor.cs b/Jint/Runtime/Interop/MethodDescriptor.cs index 831cbe60ad..9295371cc9 100644 --- a/Jint/Runtime/Interop/MethodDescriptor.cs +++ b/Jint/Runtime/Interop/MethodDescriptor.cs @@ -148,7 +148,7 @@ public JsValue Call(Engine engine, object? instance, JsValue[] arguments) } else { - throw new Exception("Method is unknown type"); + throw new ArgumentException("Method is unknown type"); } } catch (TargetInvocationException exception) diff --git a/Jint/Runtime/Interop/NamespaceReference.cs b/Jint/Runtime/Interop/NamespaceReference.cs index e72bb9a124..ef6088b40c 100644 --- a/Jint/Runtime/Interop/NamespaceReference.cs +++ b/Jint/Runtime/Interop/NamespaceReference.cs @@ -124,7 +124,7 @@ public JsValue GetPath(string path) { foreach (Type nType in GetAllNestedTypes(type)) { - if (nType.FullName != null && nType.FullName.Replace("+", ".").Equals(comparedPath)) + if (nType.FullName != null && nType.FullName.Replace("+", ".").Equals(comparedPath, StringComparison.Ordinal)) { _engine.TypeCache.Add(comparedPath, nType); return TypeReference.CreateTypeReference(_engine, nType); diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index 245251fee7..b219f69a67 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -301,7 +301,9 @@ internal override ulong GetSmallestIndex(ulong length) return Target is ICollection ? 0 : base.GetSmallestIndex(length); } - public override bool Equals(JsValue? obj) => Equals(obj as ObjectWrapper); + public override bool Equals(object? obj) => Equals(obj as ObjectWrapper); + + public override bool Equals(JsValue? other) => Equals(other as ObjectWrapper); public bool Equals(ObjectWrapper? other) { @@ -318,12 +320,7 @@ public bool Equals(ObjectWrapper? other) return Equals(Target, other.Target); } - public override int GetHashCode() - { - var hashCode = -1468639730; - hashCode = hashCode * -1521134295 + Target.GetHashCode(); - return hashCode; - } + public override int GetHashCode() => Target.GetHashCode(); private sealed class DictionaryIterator : IteratorInstance { diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index d340795568..ea1a990eab 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -204,14 +204,14 @@ static ObjectInstance ObjectCreator(Engine engine, Realm realm, ObjectCreateStat private readonly record struct ObjectCreateState(TypeReference TypeReference, JsValue[] Arguments); - public override bool Equals(JsValue? obj) + public override bool Equals(JsValue? other) { - if (obj is TypeReference typeReference) + if (other is TypeReference typeReference) { return this.ReferenceType == typeReference.ReferenceType; } - return base.Equals(obj); + return base.Equals(other); } internal override bool OrdinaryHasInstance(JsValue v) diff --git a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs index 9a808961bf..4b98329267 100644 --- a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs @@ -265,8 +265,7 @@ private static JsValue HandleArrayPattern( } else { - ExceptionHelper.ThrowArgumentOutOfRangeException("pattern", - "Unable to determine how to handle array pattern element " + left); + ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(pattern), $"Unable to determine how to handle array pattern element {left}"); break; } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs index fa0421a36f..6f0ba09c61 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs @@ -4,6 +4,7 @@ using Jint.Native; using Jint.Native.Function; using Jint.Native.Object; +using Jint.Runtime.CallStack; using Jint.Runtime.Environments; using Jint.Runtime.References; @@ -86,7 +87,7 @@ protected override object EvaluateInternal(EvaluationContext context) if (!context.Engine._stackGuard.TryEnterOnCurrentStack()) { - return context.Engine._stackGuard.RunOnEmptyStack(EvaluateInternal, context); + return StackGuard.RunOnEmptyStack(EvaluateInternal, context); } if (_calleeExpression._expression.Type == Nodes.Super) diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs index d5a9f32036..22229a0454 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs @@ -140,7 +140,7 @@ protected override object EvaluateInternal(EvaluationContext context) /// /// https://tc39.es/ecma262/#sec-makeprivatereference /// - private object MakePrivateReference(Engine engine, JsValue baseValue, JsValue privateIdentifier) + private static object MakePrivateReference(Engine engine, JsValue baseValue, JsValue privateIdentifier) { var privEnv = engine.ExecutionContext.PrivateEnvironment; var privateName = privEnv!.ResolvePrivateIdentifier(privateIdentifier.ToString()); diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index 7903f5ba10..8781bf9ac7 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -148,7 +148,7 @@ 8. Return unused. /// /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluategeneratorbody /// - private Completion EvaluateGeneratorBody(FunctionInstance functionObject, JsValue[] argumentsList) + private static Completion EvaluateGeneratorBody(FunctionInstance functionObject, JsValue[] argumentsList) { ExceptionHelper.ThrowNotImplementedException("generators not implemented"); return default; diff --git a/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs b/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs index f0e03c046d..4d280895eb 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs @@ -80,7 +80,7 @@ protected override Completion ExecuteInternal(EvaluationContext context) /// /// https://tc39.es/ecma262/#sec-initializeboundname /// - private void InitializeBoundName(string name, JsValue value, EnvironmentRecord? environment) + private static void InitializeBoundName(string name, JsValue value, EnvironmentRecord? environment) { if (environment is not null) { diff --git a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs index 2396f3b888..e77f932a1a 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs @@ -317,7 +317,9 @@ private Completion BodyEvaluation( // if we already have and exception, use it if (completionType != CompletionType.Throw) { +#pragma warning disable CA2219 throw; +#pragma warning restore CA2219 } } } diff --git a/Jint/Runtime/Modules/DefaultModuleLoader.cs b/Jint/Runtime/Modules/DefaultModuleLoader.cs index 67dd9f30c5..ee6fbfa5c7 100644 --- a/Jint/Runtime/Modules/DefaultModuleLoader.cs +++ b/Jint/Runtime/Modules/DefaultModuleLoader.cs @@ -151,6 +151,6 @@ public Module LoadModule(Engine engine, ResolvedSpecifier resolved) private static bool IsRelative(string specifier) { - return specifier.StartsWith(".") || specifier.StartsWith("/"); + return specifier.StartsWith(".", StringComparison.Ordinal) || specifier.StartsWith("/", StringComparison.Ordinal); } } diff --git a/Jint/Runtime/Modules/FailFastModuleLoader.cs b/Jint/Runtime/Modules/FailFastModuleLoader.cs index a89163809c..a00e414d11 100644 --- a/Jint/Runtime/Modules/FailFastModuleLoader.cs +++ b/Jint/Runtime/Modules/FailFastModuleLoader.cs @@ -6,7 +6,9 @@ internal sealed class FailFastModuleLoader : IModuleLoader { public static readonly IModuleLoader Instance = new FailFastModuleLoader(); +#pragma warning disable CA1822 public Uri BasePath +#pragma warning restore CA1822 { get { diff --git a/Jint/Runtime/Modules/SourceTextModuleRecord.cs b/Jint/Runtime/Modules/SourceTextModuleRecord.cs index f1c1881366..11a1fb478f 100644 --- a/Jint/Runtime/Modules/SourceTextModuleRecord.cs +++ b/Jint/Runtime/Modules/SourceTextModuleRecord.cs @@ -107,7 +107,7 @@ public override List GetExportedNames(List exportSta for (var j = 0; j < starNames.Count; j++) { var n = starNames[j]; - if (!"default".Equals(n) && !exportedNames.Contains(n)) + if (!"default".Equals(n, StringComparison.Ordinal) && !exportedNames.Contains(n)) { exportedNames.Add(n); } @@ -164,7 +164,7 @@ internal override ResolvedBinding ResolveExport(string exportName, List array.Length - arrayIndex) { diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index 3b6f7e88c6..50da476c8b 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -78,7 +78,7 @@ static TypeConverter() { for (var i = 0; i < intToString.Length; ++i) { - intToString[i] = i.ToString(); + intToString[i] = i.ToString(CultureInfo.InvariantCulture); } for (var i = 0; i < charToString.Length; ++i) @@ -848,7 +848,7 @@ internal static string ToString(long i) var temp = intToString; return (ulong) i < (ulong) temp.Length ? temp[i] - : i.ToString(); + : i.ToString(CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -857,7 +857,7 @@ internal static string ToString(int i) var temp = intToString; return (uint) i < (uint) temp.Length ? temp[i] - : i.ToString(); + : i.ToString(CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -866,7 +866,7 @@ internal static string ToString(uint i) var temp = intToString; return i < (uint) temp.Length ? temp[i] - : i.ToString(); + : i.ToString(CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -884,7 +884,7 @@ internal static string ToString(ulong i) var temp = intToString; return i < (ulong) temp.Length ? temp[i] - : i.ToString(); + : i.ToString(CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -925,7 +925,7 @@ internal static bool CanBeStringifiedAsLong(double d) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string ToString(BigInteger bigInteger) { - return bigInteger.ToString(); + return bigInteger.ToString(CultureInfo.InvariantCulture); } /// From c5657939fca50e94a91d9dd13e3c6bbc5577d6ac Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 12 Nov 2023 19:28:10 +0200 Subject: [PATCH 39/44] Enable Meziantou.Analyzer (#1682) --- .editorconfig | 27 ++++++++++++++++ Directory.Packages.props | 1 + Jint/Collections/DictionarySlim.cs | 2 ++ Jint/Collections/StringDictionarySlim.cs | 3 ++ Jint/Engine.Ast.cs | 2 +- Jint/Engine.Modules.cs | 4 +-- Jint/Engine.cs | 6 ++-- Jint/Extensions/ReflectionExtensions.cs | 6 ++-- Jint/HoistingScope.cs | 14 ++++---- Jint/Jint.csproj | 1 + Jint/JsValueExtensions.cs | 10 +++--- Jint/Key.cs | 12 +++---- Jint/ModuleBuilder.cs | 2 +- Jint/Native/Argument/ArgumentsInstance.cs | 2 +- Jint/Native/Array/ArrayInstance.cs | 8 ++--- Jint/Native/Array/ArrayOperations.cs | 6 ++-- Jint/Native/Array/ArrayPrototype.cs | 4 +-- Jint/Native/Date/DatePrototype.cs | 6 ++-- Jint/Native/Date/MimeKit.cs | 20 +++++++----- Jint/Native/DatePresentation.cs | 10 +++--- .../FinalizationRegistryInstance.cs | 2 ++ Jint/Native/Function/ClassDefinition.cs | 2 +- Jint/Native/Function/EvalFunctionInstance.cs | 6 ++-- Jint/Native/Global/GlobalObject.cs | 10 +++--- Jint/Native/Intl/NumberFormatConstructor.cs | 2 +- Jint/Native/JsString.cs | 20 ++++++------ Jint/Native/JsValue.cs | 8 ++--- Jint/Native/Json/JsonParser.cs | 4 ++- Jint/Native/Number/Dtoa/CachePowers.cs | 4 ++- Jint/Native/Number/Dtoa/DiyFp.cs | 14 ++++---- Jint/Native/Number/Dtoa/DoubleHelper.cs | 4 ++- Jint/Native/Object/ObjectConstructor.cs | 2 +- Jint/Native/Object/ObjectInstance.cs | 30 +++++++++-------- Jint/Native/PrivateName.cs | 12 ++----- Jint/Native/RegExp/JsRegExp.cs | 2 +- Jint/Native/RegExp/RegExpPrototype.cs | 4 +-- Jint/Native/ShadowRealm/ShadowRealm.cs | 4 ++- Jint/Native/String/StringInstance.cs | 8 ++--- Jint/Native/String/StringPrototype.cs | 4 ++- Jint/Native/TypedArray/TypedArrayValue.cs | 2 ++ Jint/Options.cs | 2 +- Jint/Pooling/ArgumentsInstancePool.cs | 2 +- Jint/Pooling/ReferencePool.cs | 2 +- ...onalSourceBreakLocationEqualityComparer.cs | 2 +- Jint/Runtime/DefaultTimeSystem.cs | 4 ++- .../Runtime/Descriptors/PropertyDescriptor.cs | 32 +++++++++++-------- .../Environments/FunctionEnvironmentRecord.cs | 2 +- .../Environments/PrivateEnvironmentRecord.cs | 2 +- Jint/Runtime/ExceptionHelper.cs | 2 ++ Jint/Runtime/Host.cs | 4 ++- Jint/Runtime/Interop/MethodDescriptor.cs | 2 +- Jint/Runtime/Interop/NamespaceReference.cs | 2 +- Jint/Runtime/Interop/ObjectWrapper.cs | 2 +- .../Interop/Reflection/IndexerAccessor.cs | 2 +- Jint/Runtime/Interop/TypeResolver.cs | 11 ++++--- .../Expressions/JintAssignmentExpression.cs | 4 --- .../Expressions/JintBinaryExpression.cs | 2 +- .../Expressions/JintMetaPropertyExpression.cs | 4 +-- .../Expressions/JintObjectExpression.cs | 4 +-- .../Expressions/JintUnaryExpression.cs | 2 +- .../Interpreter/JintFunctionDefinition.cs | 6 ++-- .../Statements/JintDoWhileStatement.cs | 4 +-- .../Statements/JintForInForOfStatement.cs | 8 +++-- .../Statements/JintForStatement.cs | 4 +-- .../Statements/JintLabeledStatement.cs | 2 +- .../Statements/JintSwitchStatement.cs | 2 +- .../Statements/JintWhileStatement.cs | 4 +-- Jint/Runtime/Modules/CyclicModuleRecord.cs | 2 +- Jint/Runtime/Modules/ModuleNamespace.cs | 6 ++-- .../Runtime/Modules/SourceTextModuleRecord.cs | 18 +++++------ Jint/Runtime/References/Reference.cs | 6 ++-- Jint/Runtime/TypeConverter.cs | 6 ++-- 72 files changed, 257 insertions(+), 190 deletions(-) diff --git a/.editorconfig b/.editorconfig index 570d06a3c5..b01871a449 100644 --- a/.editorconfig +++ b/.editorconfig @@ -51,6 +51,33 @@ dotnet_diagnostic.CA1710.severity = none # Error CA1716: Identifiers should have correct suffix dotnet_diagnostic.CA1716.severity = none +# Error MA0026: TODO +dotnet_diagnostic.MA0026.severity = none + +# Error MA0048 : File name must match type name +dotnet_diagnostic.MA0048.severity = none + +# Error MA0016 : Prefer using collection abstraction instead of implementation +dotnet_diagnostic.MA0016.severity = none + +# Error MA0017 : Abstract types should not have public or internal constructors +dotnet_diagnostic.MA0017.severity = none + +# Error MA0051 : Method is too long +dotnet_diagnostic.MA0051.severity = none + +# Error MA0046 : The delegate must return void +dotnet_diagnostic.MA0046.severity = none + +# Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +dotnet_diagnostic.MA0097.severity = none + +# Error MA0025 : Implement the functionality (or raise NotSupportedException or PlatformNotSupportedException) +dotnet_diagnostic.MA0025.severity = none + +# Error MA0091 : Sender parameter should be 'this' for instance events +dotnet_diagnostic.MA0091.severity = none + # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = false diff --git a/Directory.Packages.props b/Directory.Packages.props index 270b05b60d..62210fa4e6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,6 +8,7 @@ + diff --git a/Jint/Collections/DictionarySlim.cs b/Jint/Collections/DictionarySlim.cs index e4547720d0..6f77fafbb3 100644 --- a/Jint/Collections/DictionarySlim.cs +++ b/Jint/Collections/DictionarySlim.cs @@ -7,6 +7,7 @@ using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Jint.Collections { @@ -32,6 +33,7 @@ internal class DictionarySlim : IReadOnlyCollection{next}")] + [StructLayout(LayoutKind.Auto)] private struct Entry { public TKey key; diff --git a/Jint/Collections/StringDictionarySlim.cs b/Jint/Collections/StringDictionarySlim.cs index 15b3a75e4f..d9621f5eb4 100644 --- a/Jint/Collections/StringDictionarySlim.cs +++ b/Jint/Collections/StringDictionarySlim.cs @@ -1,3 +1,6 @@ +#pragma warning disable MA0006 +#pragma warning disable MA0008 + #nullable disable // Licensed to the .NET Foundation under one or more agreements. diff --git a/Jint/Engine.Ast.cs b/Jint/Engine.Ast.cs index 94e61e375c..589fa61f8c 100644 --- a/Jint/Engine.Ast.cs +++ b/Jint/Engine.Ast.cs @@ -43,7 +43,7 @@ public static Module PrepareModule(string script, string? source = null) private sealed class AstAnalyzer { - private readonly Dictionary _bindingNames = new(); + private readonly Dictionary _bindingNames = new(StringComparer.Ordinal); public void NodeVisitor(Node node) { diff --git a/Jint/Engine.Modules.cs b/Jint/Engine.Modules.cs index 19690bf324..6306424082 100644 --- a/Jint/Engine.Modules.cs +++ b/Jint/Engine.Modules.cs @@ -12,8 +12,8 @@ public partial class Engine { internal IModuleLoader ModuleLoader { get; set; } = null!; - private readonly Dictionary _modules = new(); - private readonly Dictionary _builders = new(); + private readonly Dictionary _modules = new(StringComparer.Ordinal); + private readonly Dictionary _builders = new(StringComparer.Ordinal); /// /// https://tc39.es/ecma262/#sec-getactivescriptormodule diff --git a/Jint/Engine.cs b/Jint/Engine.cs index cfb9c4d207..772b7bbafd 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -59,7 +59,7 @@ public sealed partial class Engine : IDisposable public ITypeConverter ClrTypeConverter { get; internal set; } // cache of types used when resolving CLR type names - internal readonly Dictionary TypeCache = new(); + internal readonly Dictionary TypeCache = new(StringComparer.Ordinal); // we use registered type reference as prototype if it's known internal Dictionary? _typeReferences; @@ -553,7 +553,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) ExceptionHelper.ThrowReferenceError(Realm, reference); } - if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == 0 + if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == InternalTypes.None && _referenceResolver.TryPropertyReference(this, reference, ref baseValue)) { return baseValue; @@ -584,7 +584,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) // check if we are accessing a string, boxing operation can be costly to do index access // we have good chance to have fast path with integer or string indexer ObjectInstance? o = null; - if ((property._type & (InternalTypes.String | InternalTypes.Integer)) != 0 + if ((property._type & (InternalTypes.String | InternalTypes.Integer)) != InternalTypes.None && baseValue is JsString s && TryHandleStringValue(property, s, ref o, out var jsValue)) { diff --git a/Jint/Extensions/ReflectionExtensions.cs b/Jint/Extensions/ReflectionExtensions.cs index 05e81c8aac..2645875bd7 100644 --- a/Jint/Extensions/ReflectionExtensions.cs +++ b/Jint/Extensions/ReflectionExtensions.cs @@ -139,7 +139,7 @@ public static bool TryConvertViaTypeCoercion( return true; } - if (memberType == typeof(bool) && (valueCoercionType & ValueCoercionType.Boolean) != 0) + if (memberType == typeof(bool) && (valueCoercionType & ValueCoercionType.Boolean) != ValueCoercionType.None) { converted = TypeConverter.ToBoolean(value); return true; @@ -147,7 +147,7 @@ public static bool TryConvertViaTypeCoercion( if (memberType == typeof(string) && !value.IsNullOrUndefined() - && (valueCoercionType & ValueCoercionType.String) != 0) + && (valueCoercionType & ValueCoercionType.String) != ValueCoercionType.None) { // we know how to print out correct string presentation for primitives // that are non-null and non-undefined @@ -155,7 +155,7 @@ public static bool TryConvertViaTypeCoercion( return true; } - if (memberType is not null && memberType.IsClrNumericCoercible() && (valueCoercionType & ValueCoercionType.Number) != 0) + if (memberType is not null && memberType.IsClrNumericCoercible() && (valueCoercionType & ValueCoercionType.Number) != ValueCoercionType.None) { // we know how to print out correct string presentation for primitives // that are non-null and non-undefined diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index 68a28c6eff..9c396f2c86 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -133,8 +133,8 @@ public static void GetImportsAndExports( treeWalker.Visit(module); importEntries = treeWalker._importEntries; - requestedModules = treeWalker._requestedModules ?? new(); - var importedBoundNames = new HashSet(); + requestedModules = treeWalker._requestedModules ?? new(StringComparer.Ordinal); + var importedBoundNames = new HashSet(StringComparer.Ordinal); if (importEntries != null) { @@ -171,9 +171,9 @@ public static void GetImportsAndExports( for (var j = 0; j < importEntries!.Count; j++) { var ie = importEntries[j]; - if (ie.LocalName == ee.LocalName) + if (string.Equals(ie.LocalName, ee.LocalName, StringComparison.Ordinal)) { - if (ie.ImportName == "*") + if (string.Equals(ie.ImportName, "*", StringComparison.Ordinal)) { localExportEntries.Add(ee); } @@ -187,7 +187,7 @@ public static void GetImportsAndExports( } } } - else if (ee.ImportName == "*" && ee.ExportName is null) + else if (string.Equals(ee.ImportName, "*", StringComparison.Ordinal) && ee.ExportName is null) { starExportEntries.Add(ee); } @@ -300,14 +300,14 @@ internal void Visit(Node node) if (childNode.Type == Nodes.ImportDeclaration) { _importEntries ??= new(); - _requestedModules ??= new(); + _requestedModules ??= new(StringComparer.Ordinal); var import = (ImportDeclaration) childNode; import.GetImportEntries(_importEntries, _requestedModules); } else if (childNode.Type is Nodes.ExportAllDeclaration or Nodes.ExportDefaultDeclaration or Nodes.ExportNamedDeclaration) { _exportEntries ??= new(); - _requestedModules ??= new(); + _requestedModules ??= new(StringComparer.Ordinal); var export = (ExportDeclaration) childNode; export.GetExportEntries(_exportEntries, _requestedModules); } diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index a83d5581a0..abe496c3db 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -18,6 +18,7 @@ + diff --git a/Jint/JsValueExtensions.cs b/Jint/JsValueExtensions.cs index 74a9c708c3..c115cf8cf6 100644 --- a/Jint/JsValueExtensions.cs +++ b/Jint/JsValueExtensions.cs @@ -19,7 +19,7 @@ public static class JsValueExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPrimitive(this JsValue value) { - return (value._type & (InternalTypes.Primitive | InternalTypes.Undefined | InternalTypes.Null)) != 0; + return (value._type & (InternalTypes.Primitive | InternalTypes.Undefined | InternalTypes.Null)) != InternalTypes.None; } [Pure] @@ -76,28 +76,28 @@ public static bool IsRegExp(this JsValue value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsObject(this JsValue value) { - return (value._type & InternalTypes.Object) != 0; + return (value._type & InternalTypes.Object) != InternalTypes.None; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsString(this JsValue value) { - return (value._type & InternalTypes.String) != 0; + return (value._type & InternalTypes.String) != InternalTypes.None; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNumber(this JsValue value) { - return (value._type & (InternalTypes.Number | InternalTypes.Integer)) != 0; + return (value._type & (InternalTypes.Number | InternalTypes.Integer)) != InternalTypes.None; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBigInt(this JsValue value) { - return (value._type & InternalTypes.BigInt) != 0; + return (value._type & InternalTypes.BigInt) != InternalTypes.None; } [Pure] diff --git a/Jint/Key.cs b/Jint/Key.cs index 20057126e4..f987842831 100644 --- a/Jint/Key.cs +++ b/Jint/Key.cs @@ -13,7 +13,7 @@ namespace Jint private Key(string name) { Name = name; - HashCode = name.GetHashCode(); + HashCode = StringComparer.Ordinal.GetHashCode(name); } internal readonly string Name; @@ -29,28 +29,28 @@ public static implicit operator Key(string name) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(in Key a, in Key b) { - return a.HashCode == b.HashCode && a.Name == b.Name; + return a.HashCode == b.HashCode && string.Equals(a.Name, b.Name, StringComparison.Ordinal); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(in Key a, in Key b) { - return a.HashCode != b.HashCode || a.Name != b.Name; + return a.HashCode != b.HashCode || !string.Equals(a.Name, b.Name, StringComparison.Ordinal); } public static bool operator ==(in Key a, string b) { - return a.Name == b; + return string.Equals(a.Name, b, StringComparison.Ordinal); } public static bool operator !=(in Key a, string b) { - return a.Name != b; + return !string.Equals(a.Name, b, StringComparison.Ordinal); } public bool Equals(Key other) { - return HashCode == other.HashCode && Name == other.Name; + return HashCode == other.HashCode && string.Equals(Name, other.Name, StringComparison.Ordinal); } public override bool Equals(object? obj) diff --git a/Jint/ModuleBuilder.cs b/Jint/ModuleBuilder.cs index 08c26dda8c..634f4bc2c2 100644 --- a/Jint/ModuleBuilder.cs +++ b/Jint/ModuleBuilder.cs @@ -13,7 +13,7 @@ public sealed class ModuleBuilder private readonly string _specifier; private Module? _module; private readonly List _sourceRaw = new(); - private readonly Dictionary _exports = new(); + private readonly Dictionary _exports = new(StringComparer.Ordinal); private readonly ParserOptions _options; internal ModuleBuilder(Engine engine, string specifier) diff --git a/Jint/Native/Argument/ArgumentsInstance.cs b/Jint/Native/Argument/ArgumentsInstance.cs index 3bbe3b7032..43693b1eda 100644 --- a/Jint/Native/Argument/ArgumentsInstance.cs +++ b/Jint/Native/Argument/ArgumentsInstance.cs @@ -16,7 +16,7 @@ namespace Jint.Native.Argument internal sealed class ArgumentsInstance : ObjectInstance { // cache property container for array iteration for less allocations - private static readonly ThreadLocal> _mappedNamed = new(() => new HashSet()); + private static readonly ThreadLocal> _mappedNamed = new(() => new HashSet(StringComparer.Ordinal)); private FunctionInstance _func = null!; private Key[] _names = null!; diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index 800a960b27..2d894c2197 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -79,7 +79,7 @@ internal bool CanUseFastAccess { get { - if ((_objectChangeFlags & ObjectChangeFlags.NonDefaultDataDescriptorUsage) != 0) + if ((_objectChangeFlags & ObjectChangeFlags.NonDefaultDataDescriptorUsage) != ObjectChangeFlags.None) { // could be a mutating property for example, length might change, not safe anymore return false; @@ -92,7 +92,7 @@ internal bool CanUseFastAccess return false; } - if ((arrayPrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) != 0) + if ((arrayPrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) != ObjectChangeFlags.None) { // maybe somebody moved integer property to prototype? not safe anymore return false; @@ -104,7 +104,7 @@ internal bool CanUseFastAccess return false; } - return (arrayPrototypePrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) == 0; + return (arrayPrototypePrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) == ObjectChangeFlags.None; } } @@ -316,7 +316,7 @@ protected sealed override bool TryGetProperty(JsValue property, [NotNullWhen(tru public sealed override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol) { - if ((types & Types.String) == 0) + if ((types & Types.String) == Types.None) { return base.GetOwnPropertyKeys(types); } diff --git a/Jint/Native/Array/ArrayOperations.cs b/Jint/Native/Array/ArrayOperations.cs index 96b7020a27..f8d709e1b7 100644 --- a/Jint/Native/Array/ArrayOperations.cs +++ b/Jint/Native/Array/ArrayOperations.cs @@ -56,7 +56,7 @@ public virtual JsValue[] GetAll( for (uint i = 0; i < (uint) jsValues.Length; i++) { var jsValue = skipHoles && !HasProperty(i) ? JsValue.Undefined : Get(i); - if ((jsValue.Type & elementTypes) == 0) + if ((jsValue.Type & elementTypes) == Types.None) { ExceptionHelper.ThrowTypeErrorNoEngine("invalid type"); } @@ -231,7 +231,7 @@ public override bool TryGetValue(ulong index, out JsValue value) public override JsValue Get(ulong index) => _target.Get((uint) index); - public override JsValue[] GetAll(Types elementTypes, bool skipHoles = false) + public override JsValue[] GetAll(Types elementTypes = Types.Undefined | Types.Null | Types.Boolean | Types.String | Types.Symbol | Types.Number | Types.Object, bool skipHoles = false) { var n = _target.GetLength(); @@ -251,7 +251,7 @@ public override JsValue[] GetAll(Types elementTypes, bool skipHoles = false) value = _target.Prototype?.Get(i) ?? JsValue.Undefined; } - if ((value.Type & elementTypes) == 0) + if ((value.Type & elementTypes) == Types.None) { ExceptionHelper.ThrowTypeErrorNoEngine("invalid type"); } diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index 7e6728435c..ff171f6051 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -487,7 +487,7 @@ private JsValue Map(JsValue thisObject, JsValue[] arguments) if (len > ArrayOperations.MaxArrayLength) { - ExceptionHelper.ThrowRangeError(_realm, "Invalid array length");; + ExceptionHelper.ThrowRangeError(_realm, "Invalid array length"); } var callbackfn = arguments.At(0); @@ -1136,7 +1136,7 @@ private JsValue Slice(JsValue thisObject, JsValue[] arguments) if (k < final && final - k > ArrayOperations.MaxArrayLength) { - ExceptionHelper.ThrowRangeError(_realm, "Invalid array length");; + ExceptionHelper.ThrowRangeError(_realm, "Invalid array length"); } var length = (uint) System.Math.Max(0, (long) final - (long) k); diff --git a/Jint/Native/Date/DatePrototype.cs b/Jint/Native/Date/DatePrototype.cs index 19abde6aa5..47eeb4399b 100644 --- a/Jint/Native/Date/DatePrototype.cs +++ b/Jint/Native/Date/DatePrototype.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; @@ -116,11 +117,11 @@ private JsValue ToPrimitive(JsValue thisObject, JsValue[] arguments) var hintString = hint.ToString(); var tryFirst = Types.None; - if (hintString == "default" || hintString == "string") + if (string.Equals(hintString, "default", StringComparison.Ordinal) || string.Equals(hintString, "string", StringComparison.Ordinal)) { tryFirst = Types.String; } - else if (hintString == "number") + else if (string.Equals(hintString, "number", StringComparison.Ordinal)) { tryFirst = Types.Number; } @@ -1262,6 +1263,7 @@ private static bool AreFinite(double value1, double value2, double value3) private static bool AreFinite(double value1, double value2, double value3, double value4) => IsFinite(value1) && IsFinite(value2) && IsFinite(value3) && IsFinite(value4); + [StructLayout(LayoutKind.Auto)] private readonly record struct Date(int Year, int Month, int Day); private static readonly int[] kDaysInMonths = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; diff --git a/Jint/Native/Date/MimeKit.cs b/Jint/Native/Date/MimeKit.cs index 61ba981024..03e905feba 100644 --- a/Jint/Native/Date/MimeKit.cs +++ b/Jint/Native/Date/MimeKit.cs @@ -31,6 +31,7 @@ namespace Jint.Native.Date; using System; using System.Text; using System.Collections.Generic; +using System.Runtime.InteropServices; [Flags] internal enum DateTokenFlags : byte @@ -46,6 +47,7 @@ internal enum DateTokenFlags : byte HasSign = (1 << 7), } +[StructLayout(LayoutKind.Auto)] internal readonly struct DateToken { public DateToken(DateTokenFlags flags, int start, int length) @@ -63,32 +65,32 @@ public DateToken(DateTokenFlags flags, int start, int length) public bool IsNumeric { - get { return (Flags & DateTokenFlags.NonNumeric) == 0; } + get { return (Flags & DateTokenFlags.NonNumeric) == DateTokenFlags.None; } } public bool IsWeekday { - get { return (Flags & DateTokenFlags.NonWeekday) == 0; } + get { return (Flags & DateTokenFlags.NonWeekday) == DateTokenFlags.None; } } public bool IsMonth { - get { return (Flags & DateTokenFlags.NonMonth) == 0; } + get { return (Flags & DateTokenFlags.NonMonth) == DateTokenFlags.None; } } public bool IsTimeOfDay { - get { return (Flags & DateTokenFlags.NonTime) == 0 && (Flags & DateTokenFlags.HasColon) != 0; } + get { return (Flags & DateTokenFlags.NonTime) == DateTokenFlags.None && (Flags & DateTokenFlags.HasColon) != DateTokenFlags.None; } } public bool IsNumericZone { - get { return (Flags & DateTokenFlags.NonNumericZone) == 0 && (Flags & DateTokenFlags.HasSign) != 0; } + get { return (Flags & DateTokenFlags.NonNumericZone) == DateTokenFlags.None && (Flags & DateTokenFlags.HasSign) != DateTokenFlags.None; } } public bool IsAlphaZone { - get { return (Flags & DateTokenFlags.NonAlphaZone) == 0; } + get { return (Flags & DateTokenFlags.NonAlphaZone) == DateTokenFlags.None; } } public bool IsTimeZone @@ -732,7 +734,9 @@ public static bool SkipCommentsAndWhiteSpace(byte[] text, ref int index, int end { if (throwOnError) { +#pragma warning disable MA0015 throw new ArgumentException($"Incomplete comment token at offset {startIndex}"); +#pragma warning restore MA0015 } return false; @@ -814,7 +818,7 @@ private static void SetFlags(string values, CharType bit, CharType bitcopy, bool { for (i = 0; i < 256; i++) { - if ((table[i] & bitcopy) != 0) + if ((table[i] & bitcopy) != CharType.None) table[i] |= bit; } } @@ -871,6 +875,6 @@ static ByteExtensions() public static bool IsWhitespace(this byte c) { - return (table[c] & CharType.IsWhitespace) != 0; + return (table[c] & CharType.IsWhitespace) != CharType.None; } } diff --git a/Jint/Native/DatePresentation.cs b/Jint/Native/DatePresentation.cs index bd0d814ef1..76ea1783f4 100644 --- a/Jint/Native/DatePresentation.cs +++ b/Jint/Native/DatePresentation.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using Jint.Native.Date; namespace Jint.Native; @@ -12,6 +13,7 @@ internal enum DateFlags : byte DateTimeMaxValue = 8 } +[StructLayout(LayoutKind.Auto)] internal readonly record struct DatePresentation(long Value, DateFlags Flags) { public static readonly DatePresentation NaN = new(0, DateFlags.NaN); @@ -19,9 +21,9 @@ internal readonly record struct DatePresentation(long Value, DateFlags Flags) public static readonly DatePresentation MaxValue = new(JsDate.Max, DateFlags.DateTimeMaxValue); public bool DateTimeRangeValid => IsFinite && Value <= JsDate.Max && Value >= JsDate.Min; - public bool IsNaN => (Flags & DateFlags.NaN) != 0; - public bool IsInfinity => (Flags & DateFlags.Infinity) != 0; - public bool IsFinite => (Flags & (DateFlags.NaN | DateFlags.Infinity)) == 0; + public bool IsNaN => (Flags & DateFlags.NaN) != DateFlags.None; + public bool IsInfinity => (Flags & DateFlags.Infinity) != DateFlags.None; + public bool IsFinite => (Flags & (DateFlags.NaN | DateFlags.Infinity)) == DateFlags.None; public DateTime ToDateTime() { @@ -64,7 +66,7 @@ public static implicit operator DatePresentation(double value) internal DatePresentation TimeClip() { - if ((Flags & (DateFlags.NaN | DateFlags.Infinity)) != 0) + if ((Flags & (DateFlags.NaN | DateFlags.Infinity)) != DateFlags.None) { return NaN; } diff --git a/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs b/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs index 1dd7e6dfc9..5d51d09a76 100644 --- a/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs +++ b/Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs @@ -60,7 +60,9 @@ public Observer(JobCallback callable) _callable = callable; } +#pragma warning disable MA0055 ~Observer() +#pragma warning restore MA0055 { _callable.Callback.Call(Undefined); } diff --git a/Jint/Native/Function/ClassDefinition.cs b/Jint/Native/Function/ClassDefinition.cs index f82d7fba2e..d19a8982ec 100644 --- a/Jint/Native/Function/ClassDefinition.cs +++ b/Jint/Native/Function/ClassDefinition.cs @@ -169,7 +169,7 @@ public JsValue BuildConstructor(EvaluationContext context, EnvironmentRecord env if (element is PrivateElement privateElement) { var container = !isStatic ? instancePrivateMethods : staticPrivateMethods; - var index = container.FindIndex(x => x.Key.Description == privateElement.Key.Description); + var index = container.FindIndex(x => string.Equals(x.Key.Description, privateElement.Key.Description, StringComparison.Ordinal)); if (index != -1) { var pe = container[index]; diff --git a/Jint/Native/Function/EvalFunctionInstance.cs b/Jint/Native/Function/EvalFunctionInstance.cs index 7ec449ef1a..5be0789fee 100644 --- a/Jint/Native/Function/EvalFunctionInstance.cs +++ b/Jint/Native/Function/EvalFunctionInstance.cs @@ -83,7 +83,7 @@ public JsValue PerformEval(JsValue x, Realm callerRealm, bool strictCaller, bool } catch (ParserException e) { - if (e.Description == Messages.InvalidLHSInAssignment) + if (string.Equals(e.Description, Messages.InvalidLHSInAssignment, StringComparison.Ordinal)) { ExceptionHelper.ThrowReferenceError(callerRealm, (string?) null); } @@ -201,13 +201,13 @@ private sealed class EvalScriptAnalyzer : AstVisitor protected override object VisitIdentifier(Identifier identifier) { - _containsArguments |= identifier.Name == "arguments"; + _containsArguments |= string.Equals(identifier.Name, "arguments", StringComparison.Ordinal); return identifier; } protected override object VisitMetaProperty(MetaProperty metaProperty) { - _containsNewTarget |= metaProperty.Meta.Name == "new" && metaProperty.Property.Name == "target"; + _containsNewTarget |= string.Equals(metaProperty.Meta.Name, "new", StringComparison.Ordinal) && string.Equals(metaProperty.Property.Name, "target", StringComparison.Ordinal); return metaProperty; } diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index 86ee63c231..383295e18a 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -54,7 +54,7 @@ public static JsValue ParseInt(JsValue thisObject, JsValue[] arguments) } // check fast case - if (radix == 10 && int.TryParse(trimmed, out var number)) + if (radix == 10 && int.TryParse(trimmed, NumberStyles.Integer, CultureInfo.InvariantCulture, out var number)) { return JsNumber.Create(number); } @@ -476,7 +476,9 @@ private JsValue Decode(string uriString, string? reservedSet) else { var n = 0; - for (; ((B << n) & 0x80) != 0; n++); + for (; ((B << n) & 0x80) != 0; n++) + { + } if (n == 1 || n > 4) { @@ -689,7 +691,7 @@ internal bool DefineOwnProperty(Key property, PropertyDescriptor desc) } // check fast path - if ((current._flags & PropertyFlag.MutableBinding) != 0) + if ((current._flags & PropertyFlag.MutableBinding) != PropertyFlag.None) { current._value = desc.Value; return true; @@ -728,7 +730,7 @@ internal bool SetFromMutableBinding(Key property, JsValue value, bool strict) } // check fast path - if ((existingDescriptor._flags & PropertyFlag.MutableBinding) != 0) + if ((existingDescriptor._flags & PropertyFlag.MutableBinding) != PropertyFlag.None) { existingDescriptor._value = value; return true; diff --git a/Jint/Native/Intl/NumberFormatConstructor.cs b/Jint/Native/Intl/NumberFormatConstructor.cs index a180225ac4..89b705400f 100644 --- a/Jint/Native/Intl/NumberFormatConstructor.cs +++ b/Jint/Native/Intl/NumberFormatConstructor.cs @@ -225,7 +225,7 @@ private static bool IsWellFormedUnitIdentifier(JsValue unitIdentifier) return false; } - private static readonly HashSet _sanctionedSingleUnitIdentifiers = new() + private static readonly HashSet _sanctionedSingleUnitIdentifiers = new(StringComparer.Ordinal) { "acre", "bit", diff --git a/Jint/Native/JsString.cs b/Jint/Native/JsString.cs index 819c232f77..ea055949a7 100644 --- a/Jint/Native/JsString.cs +++ b/Jint/Native/JsString.cs @@ -54,7 +54,7 @@ static JsString() } - _stringCache = new ConcurrentDictionary(); + _stringCache = new ConcurrentDictionary(StringComparer.Ordinal); Empty = new JsString("", InternalTypes.String); NullString = CachedCreate("null"); UndefinedString = CachedCreate("undefined"); @@ -267,13 +267,13 @@ internal int IndexOf(char value) internal bool StartsWith(string value, int start = 0) { - return value.Length + start <= Length && ToString().AsSpan(start).StartsWith(value.AsSpan()); + return value.Length + start <= Length && ToString().AsSpan(start).StartsWith(value.AsSpan(), StringComparison.Ordinal); } internal bool EndsWith(string value, int end = 0) { var start = end - value.Length; - return start >= 0 && ToString().AsSpan(start, value.Length).EndsWith(value.AsSpan()); + return start >= 0 && ToString().AsSpan(start, value.Length).EndsWith(value.AsSpan(), StringComparison.Ordinal); } internal string Substring(int startIndex, int length) @@ -290,7 +290,7 @@ internal string Substring(int startIndex) public sealed override bool Equals(JsValue? other) => Equals(other as JsString); - public virtual bool Equals(string? other) => other != null && ToString() == other; + public virtual bool Equals(string? other) => other != null && string.Equals(ToString(), other, StringComparison.Ordinal); public virtual bool Equals(JsString? other) { @@ -304,7 +304,7 @@ public virtual bool Equals(JsString? other) return true; } - return _value == other.ToString(); + return string.Equals(_value, other.ToString(), StringComparison.Ordinal); } public override bool IsLooselyEqual(JsValue value) @@ -322,7 +322,7 @@ public override bool IsLooselyEqual(JsValue value) return base.IsLooselyEqual(value); } - public override int GetHashCode() => _value.GetHashCode(); + public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(_value); internal sealed class ConcatenatedString : JsString { @@ -398,7 +398,7 @@ public override bool Equals(string? s) return true; } - return _value == s; + return string.Equals(_value, s, StringComparison.Ordinal); } public override bool Equals(JsString? other) @@ -422,7 +422,7 @@ public override bool Equals(JsString? other) return true; } - return ToString() == cs.ToString(); + return string.Equals(ToString(), cs.ToString(), StringComparison.Ordinal); } if (other is null || other.Length != Length) @@ -430,10 +430,10 @@ public override bool Equals(JsString? other) return false; } - return ToString() == other.ToString(); + return string.Equals(ToString(), other.ToString(), StringComparison.Ordinal); } - public override int GetHashCode() => _stringBuilder?.GetHashCode() ?? _value.GetHashCode(); + public override int GetHashCode() => _stringBuilder?.GetHashCode() ?? StringComparer.Ordinal.GetHashCode(_value); internal override JsValue DoClone() { diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 1a6e413efb..cfab33dbab 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -334,12 +334,12 @@ public virtual bool IsLooselyEqual(JsValue value) return x.IsLooselyEqual(TypeConverter.ToNumber(y)); } - if (y.IsObject() && (x._type & InternalTypes.Primitive) != 0) + if (y.IsObject() && (x._type & InternalTypes.Primitive) != InternalTypes.None) { return x.IsLooselyEqual(TypeConverter.ToPrimitive(y)); } - if (x.IsObject() && (y._type & InternalTypes.Primitive) != 0) + if (x.IsObject() && (y._type & InternalTypes.Primitive) != InternalTypes.None) { return y.IsLooselyEqual(TypeConverter.ToPrimitive(x)); } @@ -366,7 +366,7 @@ public virtual bool IsLooselyEqual(JsValue value) internal JsValue Clone() { // concatenated string and arguments currently may require cloning - return (_type & InternalTypes.RequiresCloning) == 0 + return (_type & InternalTypes.RequiresCloning) == InternalTypes.None ? this : DoClone(); } @@ -458,7 +458,7 @@ internal static bool SameValue(JsValue x, JsValue y) return false; case Types.String: - return TypeConverter.ToString(x) == TypeConverter.ToString(y); + return string.Equals(TypeConverter.ToString(x), TypeConverter.ToString(y), StringComparison.Ordinal); case Types.Boolean: return TypeConverter.ToBoolean(x) == TypeConverter.ToBoolean(y); case Types.Undefined: diff --git a/Jint/Native/Json/JsonParser.cs b/Jint/Native/Json/JsonParser.cs index 449371e4b5..a1a593f0ee 100644 --- a/Jint/Native/Json/JsonParser.cs +++ b/Jint/Native/Json/JsonParser.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Esprima; using Jint.Native.Object; @@ -251,7 +252,7 @@ private Token ScanNumericLiteral(ref State state) sb.Clear(); JsNumber value; - if (canBeInteger && long.TryParse(number, out var longResult) && longResult != -0) + if (canBeInteger && long.TryParse(number, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longResult) && longResult != -0) { value = JsNumber.Create(longResult); } @@ -759,6 +760,7 @@ private sealed class Token public TextRange Range; } + [StructLayout(LayoutKind.Auto)] private readonly struct TextRange { public TextRange(int start, int end) diff --git a/Jint/Native/Number/Dtoa/CachePowers.cs b/Jint/Native/Number/Dtoa/CachePowers.cs index 159708f567..ffb948a79d 100644 --- a/Jint/Native/Number/Dtoa/CachePowers.cs +++ b/Jint/Native/Number/Dtoa/CachePowers.cs @@ -31,6 +31,7 @@ // The original revision was 67d1049b0bf9 from the mozilla-central tree. using System.Diagnostics; +using System.Runtime.InteropServices; namespace Jint.Native.Number.Dtoa { @@ -52,6 +53,7 @@ internal CachedPower(ulong significand, short binaryExponent, short decimalExpon } } + [StructLayout(LayoutKind.Auto)] internal readonly struct GetCachedPowerResult { public GetCachedPowerResult(short decimalExponent, DiyFp cMk) @@ -180,4 +182,4 @@ internal static GetCachedPowerResult GetCachedPowerForBinaryExponentRange(int mi const int kMinDecimalExponent = -348; const int kMaxDecimalExponent = 340; } -} \ No newline at end of file +} diff --git a/Jint/Native/Number/Dtoa/DiyFp.cs b/Jint/Native/Number/Dtoa/DiyFp.cs index 27bc299de5..7ffc720b34 100644 --- a/Jint/Native/Number/Dtoa/DiyFp.cs +++ b/Jint/Native/Number/Dtoa/DiyFp.cs @@ -31,15 +31,17 @@ // The original revision was 67d1049b0bf9 from the mozilla-central tree. using System.Diagnostics; +using System.Runtime.InteropServices; namespace Jint.Native.Number.Dtoa { -// This "Do It Yourself Floating Point" class implements a floating-point number -// with a uint64 significand and an int exponent. Normalized DiyFp numbers will -// have the most significant bit of the significand set. -// Multiplication and Subtraction do not normalize their results. -// DiyFp are not designed to contain special doubles (NaN and Infinity). + // This "Do It Yourself Floating Point" class implements a floating-point number + // with a uint64 significand and an int exponent. Normalized DiyFp numbers will + // have the most significant bit of the significand set. + // Multiplication and Subtraction do not normalize their results. + // DiyFp are not designed to contain special doubles (NaN and Infinity). + [StructLayout(LayoutKind.Auto)] internal readonly struct DiyFp { internal const int KSignificandSize = 64; @@ -114,4 +116,4 @@ public override string ToString() return "[DiyFp f:" + F + ", e:" + E + "]"; } } -} \ No newline at end of file +} diff --git a/Jint/Native/Number/Dtoa/DoubleHelper.cs b/Jint/Native/Number/Dtoa/DoubleHelper.cs index 91a4c2a48d..bc2773bc93 100644 --- a/Jint/Native/Number/Dtoa/DoubleHelper.cs +++ b/Jint/Native/Number/Dtoa/DoubleHelper.cs @@ -31,6 +31,7 @@ // The original revision was 67d1049b0bf9 from the mozilla-central tree. using System.Diagnostics; +using System.Runtime.InteropServices; namespace Jint.Native.Number.Dtoa { @@ -112,6 +113,7 @@ private static bool IsSpecial(ulong d64) return (d64 & KExponentMask) == KExponentMask; } + [StructLayout(LayoutKind.Auto)] internal readonly struct NormalizedBoundariesResult { public NormalizedBoundariesResult(DiyFp minus, DiyFp plus) @@ -155,4 +157,4 @@ internal static NormalizedBoundariesResult NormalizedBoundaries(ulong d64) private const int KExponentBias = 0x3FF + KSignificandSize; private const int KDenormalExponent = -KExponentBias + 1; } -} \ No newline at end of file +} diff --git a/Jint/Native/Object/ObjectConstructor.cs b/Jint/Native/Object/ObjectConstructor.cs index 0b9a17513c..c40a745bfd 100644 --- a/Jint/Native/Object/ObjectConstructor.cs +++ b/Jint/Native/Object/ObjectConstructor.cs @@ -552,7 +552,7 @@ private CreateDataPropertyOnObject() { } - public JsValue Call(JsValue thisObject, JsValue[] arguments) + public JsValue Call(JsValue thisObject, params JsValue[] arguments) { var o = (ObjectInstance) thisObject; var key = arguments.At(0); diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 29e2ccc25e..c1124297c7 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -44,7 +44,9 @@ internal ObjectInstance( _class = objectClass; // if engine is ready, we can take default prototype for object _prototype = engine.Realm.Intrinsics?.Object?.PrototypeObject; +#pragma warning disable MA0056 Extensible = true; +#pragma warning restore MA0056 } public Engine Engine @@ -230,11 +232,11 @@ 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 returningSymbols = (types & Types.Symbol) != Types.None && _symbols?.Count > 0; + var returningStringKeys = (types & Types.String) != Types.None && _properties?.Count > 0; var propertyKeys = new List(); - if ((types & Types.String) != 0) + if ((types & Types.String) != Types.None) { var initialOwnStringPropertyKeys = GetInitialOwnStringPropertyKeys(); if (!ReferenceEquals(initialOwnStringPropertyKeys, System.Linq.Enumerable.Empty())) @@ -270,7 +272,7 @@ public virtual List GetOwnPropertyKeys(Types types = Types.String | Typ return propertyKeys; } - if ((types & Types.String) == 0 && (types & Types.Symbol) != 0) + if ((types & Types.String) == Types.None && (types & Types.Symbol) != Types.None) { // only symbols requested if (_symbols != null) @@ -363,7 +365,7 @@ public virtual void RemoveOwnProperty(JsValue property) public override JsValue Get(JsValue property, JsValue receiver) { - if ((_type & InternalTypes.PlainObject) != 0 && ReferenceEquals(this, receiver) && property is JsString jsString) + if ((_type & InternalTypes.PlainObject) != InternalTypes.None && ReferenceEquals(this, receiver) && property is JsString jsString) { EnsureInitialized(); if (_properties?.TryGetValue(jsString.ToString(), out var ownDesc) == true) @@ -391,12 +393,12 @@ internal JsValue UnwrapJsValue(PropertyDescriptor desc) internal static JsValue UnwrapJsValue(PropertyDescriptor desc, JsValue thisObject) { - var value = (desc._flags & PropertyFlag.CustomJsValue) != 0 + var value = (desc._flags & PropertyFlag.CustomJsValue) != PropertyFlag.None ? desc.CustomValue : desc._value; // IsDataDescriptor inlined - if ((desc._flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0 || value is not null) + if ((desc._flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != PropertyFlag.None || value is not null) { return value ?? Undefined; } @@ -519,12 +521,12 @@ public bool Set(JsValue p, JsValue v, bool throwOnError) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Set(JsValue property, JsValue value) { - if ((_type & InternalTypes.PlainObject) != 0 && property is JsString jsString) + if ((_type & InternalTypes.PlainObject) != InternalTypes.None && property is JsString jsString) { var key = (Key) jsString.ToString(); if (_properties?.TryGetValue(key, out var ownDesc) == true) { - if ((ownDesc._flags & PropertyFlag.Writable) != 0) + if ((ownDesc._flags & PropertyFlag.Writable) != PropertyFlag.None) { ownDesc._value = value; return true; @@ -542,12 +544,12 @@ public bool Set(JsValue property, JsValue value) /// public override bool Set(JsValue property, JsValue value, JsValue receiver) { - if ((_type & InternalTypes.PlainObject) != 0 && ReferenceEquals(this, receiver) && property is JsString jsString) + if ((_type & InternalTypes.PlainObject) != InternalTypes.None && ReferenceEquals(this, receiver) && property is JsString jsString) { var key = (Key) jsString.ToString(); if (_properties?.TryGetValue(key, out var ownDesc) == true) { - if ((ownDesc._flags & PropertyFlag.Writable) != 0) + if ((ownDesc._flags & PropertyFlag.Writable) != PropertyFlag.None) { ownDesc._value = value; return true; @@ -781,7 +783,7 @@ protected static bool ValidateAndApplyPropertyDescriptor(ObjectInstance? o, JsVa { propertyDescriptor = new PropertyDescriptor(descValue ?? Undefined, PropertyFlag.ConfigurableEnumerableWritable); } - else if ((desc._flags & PropertyFlag.ConfigurableEnumerableWritable) == 0) + else if ((desc._flags & PropertyFlag.ConfigurableEnumerableWritable) == PropertyFlag.None) { propertyDescriptor = new PropertyDescriptor(descValue ?? Undefined, PropertyFlag.AllForbidden); } @@ -816,7 +818,7 @@ protected static bool ValidateAndApplyPropertyDescriptor(ObjectInstance? o, JsVa var currentValue = current.Value; // 4. If every field in Desc is absent, return true. - if ((current._flags & (PropertyFlag.ConfigurableSet | PropertyFlag.EnumerableSet | PropertyFlag.WritableSet)) == 0 && + if ((current._flags & (PropertyFlag.ConfigurableSet | PropertyFlag.EnumerableSet | PropertyFlag.WritableSet)) == PropertyFlag.None && ReferenceEquals(currentGet, null) && ReferenceEquals(currentSet, null) && ReferenceEquals(currentValue, null)) @@ -1073,7 +1075,7 @@ private object ToObject(ObjectTraverseStack stack) TypedArrayElementType.Uint16 => typedArrayInstance.ToNativeArray(), TypedArrayElementType.Uint32 => typedArrayInstance.ToNativeArray(), TypedArrayElementType.BigUint64 => typedArrayInstance.ToNativeArray(), - _ => throw new ArgumentOutOfRangeException("", "cannot handle element type") + _ => throw new NotSupportedException("cannot handle element type") }; break; diff --git a/Jint/Native/PrivateName.cs b/Jint/Native/PrivateName.cs index 1340330c7c..8a0396452e 100644 --- a/Jint/Native/PrivateName.cs +++ b/Jint/Native/PrivateName.cs @@ -50,7 +50,7 @@ public bool Equals(PrivateName? other) public override int GetHashCode() { - return _identifier.Name.GetHashCode(); + return StringComparer.Ordinal.GetHashCode(_identifier.Name); } } @@ -61,14 +61,8 @@ internal sealed class PrivateIdentifierNameComparer : IEqualityComparer string.Equals(x?.Name, y?.Name, StringComparison.Ordinal); - public int GetHashCode(PrivateIdentifier obj) - { - return obj.Name.GetHashCode(); - } + public int GetHashCode(PrivateIdentifier obj) => StringComparer.Ordinal.GetHashCode(obj.Name); } diff --git a/Jint/Native/RegExp/JsRegExp.cs b/Jint/Native/RegExp/JsRegExp.cs index 26782c0504..e0ee9df23a 100644 --- a/Jint/Native/RegExp/JsRegExp.cs +++ b/Jint/Native/RegExp/JsRegExp.cs @@ -110,7 +110,7 @@ public override IEnumerable> GetOwnPro } } - public override List GetOwnPropertyKeys(Types types) + public override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) { var keys = new List(); if (_prototypeDescriptor != null) diff --git a/Jint/Native/RegExp/RegExpPrototype.cs b/Jint/Native/RegExp/RegExpPrototype.cs index cad5be23a5..7b00c2dcd1 100644 --- a/Jint/Native/RegExp/RegExpPrototype.cs +++ b/Jint/Native/RegExp/RegExpPrototype.cs @@ -473,7 +473,7 @@ private JsValue Split(JsValue thisObject, JsValue[] arguments) { // we can take faster path - if (R.Source == JsRegExp.regExpForMatchingAllCharacters) + if (string.Equals(R.Source, JsRegExp.regExpForMatchingAllCharacters, StringComparison.Ordinal)) { // if empty string, just a string split return StringPrototype.SplitWithStringSeparator(_realm, "", s, (uint) s.Length); @@ -855,7 +855,7 @@ private static JsValue RegExpBuiltinExec(JsRegExp R, string s) lastIndex = 0; } - if (R.Source == JsRegExp.regExpForMatchingAllCharacters) // Reg Exp is really "" + if (string.Equals(R.Source, JsRegExp.regExpForMatchingAllCharacters, StringComparison.Ordinal)) // Reg Exp is really "" { if (lastIndex > (ulong) s.Length) { diff --git a/Jint/Native/ShadowRealm/ShadowRealm.cs b/Jint/Native/ShadowRealm/ShadowRealm.cs index e9e9f20051..cc3db89dca 100644 --- a/Jint/Native/ShadowRealm/ShadowRealm.cs +++ b/Jint/Native/ShadowRealm/ShadowRealm.cs @@ -17,7 +17,9 @@ namespace Jint.Native.ShadowRealm; /// /// https://tc39.es/proposal-shadowrealm/#sec-properties-of-shadowrealm-instances /// +#pragma warning disable MA0049 public sealed class ShadowRealm : ObjectInstance +#pragma warning restore MA0049 { private readonly JavaScriptParser _parser; internal readonly Realm _shadowRealm; @@ -111,7 +113,7 @@ internal JsValue PerformShadowRealmEval(string sourceText, Realm callerRealm) } catch (ParserException e) { - if (e.Description == Messages.InvalidLHSInAssignment) + if (string.Equals(e.Description, Messages.InvalidLHSInAssignment, StringComparison.Ordinal)) { ExceptionHelper.ThrowReferenceError(callerRealm, Messages.InvalidLHSInAssignment); } diff --git a/Jint/Native/String/StringInstance.cs b/Jint/Native/String/StringInstance.cs index a91d03ef09..f98990df27 100644 --- a/Jint/Native/String/StringInstance.cs +++ b/Jint/Native/String/StringInstance.cs @@ -51,7 +51,7 @@ public sealed override PropertyDescriptor GetOwnProperty(JsValue property) return desc; } - if ((property._type & (InternalTypes.Number | InternalTypes.Integer | InternalTypes.String)) == 0) + if ((property._type & (InternalTypes.Number | InternalTypes.Integer | InternalTypes.String)) == InternalTypes.None) { return PropertyDescriptor.Undefined; } @@ -84,10 +84,10 @@ internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() yield return JsString.LengthString; } - public sealed override List GetOwnPropertyKeys(Types types) + public sealed override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) { var keys = new List(StringData.Length + 1); - if ((types & Types.String) != 0) + if ((types & Types.String) != Types.None) { for (uint i = 0; i < StringData.Length; ++i) { @@ -97,7 +97,7 @@ public sealed override List GetOwnPropertyKeys(Types types) keys.AddRange(base.GetOwnPropertyKeys(Types.String)); } - if ((types & Types.Symbol) != 0) + if ((types & Types.Symbol) != Types.None) { keys.AddRange(base.GetOwnPropertyKeys(Types.Symbol)); } diff --git a/Jint/Native/String/StringPrototype.cs b/Jint/Native/String/StringPrototype.cs index d17530bfb8..408db21452 100644 --- a/Jint/Native/String/StringPrototype.cs +++ b/Jint/Native/String/StringPrototype.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Jint.Collections; using Jint.Native.Json; @@ -351,7 +352,7 @@ private JsValue Split(JsValue thisObject, JsValue[] arguments) var limit = arguments.At(1); // fast path for empty regexp - if (separator is JsRegExp R && R.Source == JsRegExp.regExpForMatchingAllCharacters) + if (separator is JsRegExp R && string.Equals(R.Source, JsRegExp.regExpForMatchingAllCharacters, StringComparison.Ordinal)) { separator = JsString.Empty; } @@ -873,6 +874,7 @@ private JsValue CodePointAt(JsValue thisObject, JsValue[] arguments) return CodePointAt(s, position).CodePoint; } + [StructLayout(LayoutKind.Auto)] private readonly record struct CodePointResult(int CodePoint, int CodeUnitCount, bool IsUnpairedSurrogate); private static CodePointResult CodePointAt(string s, int position) diff --git a/Jint/Native/TypedArray/TypedArrayValue.cs b/Jint/Native/TypedArray/TypedArrayValue.cs index a09c329079..1096007299 100644 --- a/Jint/Native/TypedArray/TypedArrayValue.cs +++ b/Jint/Native/TypedArray/TypedArrayValue.cs @@ -1,4 +1,5 @@ using System.Numerics; +using System.Runtime.InteropServices; using Jint.Runtime; namespace Jint.Native.TypedArray; @@ -6,6 +7,7 @@ namespace Jint.Native.TypedArray; /// /// Container for either double or BigInteger. /// +[StructLayout(LayoutKind.Auto)] internal readonly record struct TypedArrayValue(Types Type, double DoubleValue, BigInteger BigInteger) : IConvertible { public static implicit operator TypedArrayValue(double value) diff --git a/Jint/Options.cs b/Jint/Options.cs index 0341414a37..ffc622c00c 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -167,7 +167,7 @@ private static void AttachExtensionMethodsToPrototype(Engine engine, ObjectInsta return; } - foreach (var overloads in methods.GroupBy(x => x.Name)) + foreach (var overloads in methods.GroupBy(x => x.Name, StringComparer.Ordinal)) { PropertyDescriptor CreateMethodInstancePropertyDescriptor(ClrFunctionInstance? function) { diff --git a/Jint/Pooling/ArgumentsInstancePool.cs b/Jint/Pooling/ArgumentsInstancePool.cs index 2f7e98fc35..2562b4ed4e 100644 --- a/Jint/Pooling/ArgumentsInstancePool.cs +++ b/Jint/Pooling/ArgumentsInstancePool.cs @@ -49,7 +49,7 @@ public void Return(ArgumentsInstance instance) { return; } - _pool.Free(instance);; + _pool.Free(instance); } } } diff --git a/Jint/Pooling/ReferencePool.cs b/Jint/Pooling/ReferencePool.cs index 7d6420bd93..24e10fdd77 100644 --- a/Jint/Pooling/ReferencePool.cs +++ b/Jint/Pooling/ReferencePool.cs @@ -32,7 +32,7 @@ public void Return(Reference? reference) { return; } - _pool.Free(reference);; + _pool.Free(reference); } } } diff --git a/Jint/Runtime/Debugger/OptionalSourceBreakLocationEqualityComparer.cs b/Jint/Runtime/Debugger/OptionalSourceBreakLocationEqualityComparer.cs index c7e7e9ade3..8f3544a831 100644 --- a/Jint/Runtime/Debugger/OptionalSourceBreakLocationEqualityComparer.cs +++ b/Jint/Runtime/Debugger/OptionalSourceBreakLocationEqualityComparer.cs @@ -24,7 +24,7 @@ public bool Equals(BreakLocation? x, BreakLocation? y) return x.Line == y.Line && x.Column == y.Column && - (x.Source == null || y.Source == null || x.Source == y.Source); + (x.Source == null || y.Source == null || string.Equals(x.Source, y.Source, StringComparison.Ordinal)); } public int GetHashCode(BreakLocation? obj) diff --git a/Jint/Runtime/DefaultTimeSystem.cs b/Jint/Runtime/DefaultTimeSystem.cs index b5c7e80661..72f348abe2 100644 --- a/Jint/Runtime/DefaultTimeSystem.cs +++ b/Jint/Runtime/DefaultTimeSystem.cs @@ -89,7 +89,9 @@ public virtual bool TryParse(string date, out long epochMilliseconds) if (DateUtils.TryParse(date, out var mimeKitResult)) { var dateAsUtc = mimeKitResult.ToUniversalTime(); +#pragma warning disable MA0132 epochMilliseconds = (long) Math.Floor((dateAsUtc - DateConstructor.Epoch).TotalMilliseconds); +#pragma warning restore MA0132 return true; } @@ -125,7 +127,7 @@ private static bool TryParseLargeYear(string date, out long epochMilliseconds) epochMilliseconds = long.MinValue; var yearString = date.Substring(0, 7); - if (!int.TryParse(yearString, out var year)) + if (!int.TryParse(yearString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) { return false; } diff --git a/Jint/Runtime/Descriptors/PropertyDescriptor.cs b/Jint/Runtime/Descriptors/PropertyDescriptor.cs index 57543f2fca..3b3f290dd9 100644 --- a/Jint/Runtime/Descriptors/PropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/PropertyDescriptor.cs @@ -27,9 +27,11 @@ protected PropertyDescriptor(PropertyFlag flags) [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal PropertyDescriptor(JsValue? value, PropertyFlag flags) : this(flags) { - if ((_flags & PropertyFlag.CustomJsValue) != 0) + if ((_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None) { +#pragma warning disable MA0056 CustomValue = value; +#pragma warning restore MA0056 } _value = value; } @@ -37,9 +39,11 @@ protected internal PropertyDescriptor(JsValue? value, PropertyFlag flags) : this [MethodImpl(MethodImplOptions.AggressiveInlining)] public PropertyDescriptor(JsValue? value, bool? writable, bool? enumerable, bool? configurable) { - if ((_flags & PropertyFlag.CustomJsValue) != 0) + if ((_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None) { +#pragma warning disable MA0056 CustomValue = value; +#pragma warning restore MA0056 } _value = value; @@ -82,7 +86,7 @@ public PropertyDescriptor(PropertyDescriptor descriptor) public bool Enumerable { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & PropertyFlag.Enumerable) != 0; + get => (_flags & PropertyFlag.Enumerable) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] set { @@ -101,7 +105,7 @@ public bool Enumerable public bool EnumerableSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & (PropertyFlag.EnumerableSet | PropertyFlag.Enumerable)) != 0; + get => (_flags & (PropertyFlag.EnumerableSet | PropertyFlag.Enumerable)) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set { @@ -119,7 +123,7 @@ private set public bool Writable { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & PropertyFlag.Writable) != 0; + get => (_flags & PropertyFlag.Writable) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] set { @@ -138,7 +142,7 @@ public bool Writable public bool WritableSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0; + get => (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set { @@ -156,7 +160,7 @@ private set public bool Configurable { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & PropertyFlag.Configurable) != 0; + get => (_flags & PropertyFlag.Configurable) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] set { @@ -175,7 +179,7 @@ public bool Configurable public bool ConfigurableSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & (PropertyFlag.ConfigurableSet | PropertyFlag.Configurable)) != 0; + get => (_flags & (PropertyFlag.ConfigurableSet | PropertyFlag.Configurable)) != PropertyFlag.None; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set { @@ -195,7 +199,7 @@ public JsValue Value [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((_flags & PropertyFlag.CustomJsValue) != 0) + if ((_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None) { return CustomValue!; } @@ -205,7 +209,7 @@ public JsValue Value [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - if ((_flags & PropertyFlag.CustomJsValue) != 0) + if ((_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None) { CustomValue = value; } @@ -396,8 +400,8 @@ public bool IsDataDescriptor() { return false; } - return (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0 - || (_flags & PropertyFlag.CustomJsValue) != 0 && !ReferenceEquals(CustomValue, null) + return (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != PropertyFlag.None + || (_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None && !ReferenceEquals(CustomValue, null) || !ReferenceEquals(_value, null); } @@ -417,9 +421,9 @@ internal bool TryGetValue(ObjectInstance thisArg, out JsValue value) value = JsValue.Undefined; // IsDataDescriptor logic inlined - if ((_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0) + if ((_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != PropertyFlag.None) { - var val = (_flags & PropertyFlag.CustomJsValue) != 0 + var val = (_flags & PropertyFlag.CustomJsValue) != PropertyFlag.None ? CustomValue : _value; diff --git a/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs b/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs index 20c593bab6..1423e4474f 100644 --- a/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs @@ -320,7 +320,7 @@ private void HandleAssignmentPatternOrExpression( var idLeft = left as Identifier; if (idLeft != null && right is Identifier idRight - && idLeft.Name == idRight.Name) + && string.Equals(idLeft.Name, idRight.Name, StringComparison.Ordinal)) { ExceptionHelper.ThrowReferenceNameError(_functionObject._realm, idRight.Name); } diff --git a/Jint/Runtime/Environments/PrivateEnvironmentRecord.cs b/Jint/Runtime/Environments/PrivateEnvironmentRecord.cs index 3afb57a1ee..7c782f8d48 100644 --- a/Jint/Runtime/Environments/PrivateEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/PrivateEnvironmentRecord.cs @@ -23,7 +23,7 @@ public PrivateEnvironmentRecord(PrivateEnvironmentRecord? outerPrivEnv) { foreach (var pn in Names) { - if (pn.Value.Description == identifier) + if (string.Equals(pn.Value.Description, identifier, StringComparison.Ordinal)) { return pn.Value; } diff --git a/Jint/Runtime/ExceptionHelper.cs b/Jint/Runtime/ExceptionHelper.cs index 68f02d9880..ea71268655 100644 --- a/Jint/Runtime/ExceptionHelper.cs +++ b/Jint/Runtime/ExceptionHelper.cs @@ -114,7 +114,9 @@ public static void ThrowStatementsCountOverflowException() [DoesNotReturn] public static void ThrowArgumentOutOfRangeException() { +#pragma warning disable MA0015 throw new ArgumentOutOfRangeException(); +#pragma warning restore MA0015 } [DoesNotReturn] diff --git a/Jint/Runtime/Host.cs b/Jint/Runtime/Host.cs index 2c28a64a8f..0f2881eef9 100644 --- a/Jint/Runtime/Host.cs +++ b/Jint/Runtime/Host.cs @@ -211,6 +211,8 @@ internal void HostEnqueuePromiseJob(Action job, Realm realm) Engine.AddToEventLoop(job); } } + + internal sealed record JobCallback(ICallable Callback, object? HostDefined); } -internal sealed record JobCallback(ICallable Callback, object? HostDefined); + diff --git a/Jint/Runtime/Interop/MethodDescriptor.cs b/Jint/Runtime/Interop/MethodDescriptor.cs index 9295371cc9..21002c2549 100644 --- a/Jint/Runtime/Interop/MethodDescriptor.cs +++ b/Jint/Runtime/Interop/MethodDescriptor.cs @@ -148,7 +148,7 @@ public JsValue Call(Engine engine, object? instance, JsValue[] arguments) } else { - throw new ArgumentException("Method is unknown type"); + throw new NotSupportedException("Method is unknown type"); } } catch (TargetInvocationException exception) diff --git a/Jint/Runtime/Interop/NamespaceReference.cs b/Jint/Runtime/Interop/NamespaceReference.cs index ef6088b40c..4a58c21f67 100644 --- a/Jint/Runtime/Interop/NamespaceReference.cs +++ b/Jint/Runtime/Interop/NamespaceReference.cs @@ -159,7 +159,7 @@ public JsValue GetPath(string path) Type[] types = assembly.GetTypes(); foreach (Type t in types) { - if (t.FullName?.Replace("+", ".") == compared) + if (string.Equals(t.FullName?.Replace("+", "."), compared, StringComparison.Ordinal)) { return t; } diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index b219f69a67..2fc549d7c1 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -135,7 +135,7 @@ public override IEnumerable> GetOwnPro private IEnumerable EnumerateOwnPropertyKeys(Types types) { // prefer object order, add possible other properties after - var includeStrings = (types & Types.String) != 0; + var includeStrings = (types & Types.String) != Types.None; if (includeStrings && _typeDescriptor.IsStringKeyedGenericDictionary) // expando object for instance { var keys = _typeDescriptor.GetKeys(Target); diff --git a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs index 3d5aa0a8f7..3ad306f3ab 100644 --- a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs @@ -44,7 +44,7 @@ internal static bool TryFindIndexer( // integer keys can be ambiguous as we only know string keys int? integerKey = null; - if (int.TryParse(propertyName, out var intKeyTemp)) + if (int.TryParse(propertyName, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intKeyTemp)) { integerKey = intKeyTemp; } diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index c3964ff754..b4fcb7f6b2 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Dynamic; +using System.Globalization; using System.Reflection; using System.Threading; using Jint.Runtime.Interop.Reflection; @@ -22,7 +23,7 @@ public sealed class TypeResolver internal bool Filter(Engine engine, MemberInfo m) { - return (engine.Options.Interop.AllowGetType || m.Name != nameof(GetType)) && MemberFilter(m); + return (engine.Options.Interop.AllowGetType || !string.Equals(m.Name, nameof(GetType), StringComparison.Ordinal)) && MemberFilter(m); } /// @@ -75,7 +76,7 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( string memberName, bool forWrite) { - var isInteger = long.TryParse(memberName, out _); + var isInteger = long.TryParse(memberName, NumberStyles.Integer, CultureInfo.InvariantCulture, out _); // we can always check indexer if there's one, and then fall back to properties if indexer returns null IndexerAccessor.TryFindIndexer(engine, type, memberName, out var indexerAccessor, out var indexer); @@ -111,7 +112,7 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( continue; } - if (iprop.Name == "Item" && iprop.GetIndexParameters().Length == 1) + if (string.Equals(iprop.Name, "Item", StringComparison.Ordinal) && iprop.GetIndexParameters().Length == 1) { // never take indexers, should use the actual indexer continue; @@ -262,7 +263,7 @@ internal bool TryFindMemberAccessor( } // only if it's not an indexer, we can do case-ignoring matches - var isStandardIndexer = p.GetIndexParameters().Length == 1 && p.Name == "Item"; + var isStandardIndexer = p.GetIndexParameters().Length == 1 && string.Equals(p.Name, "Item", StringComparison.Ordinal); if (!isStandardIndexer) { foreach (var name in typeResolverMemberNameCreator(p)) @@ -431,7 +432,7 @@ public override bool Equals(string? x, string? y) #if SUPPORTS_SPAN_PARSE equals = x.AsSpan(1).SequenceEqual(y.AsSpan(1)); #else - equals = x.Substring(1) == y.Substring(1); + equals = string.Equals(x.Substring(1), y.Substring(1), StringComparison.Ordinal); #endif } diff --git a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs index 1bac40f5af..4689ec45e5 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs @@ -199,10 +199,6 @@ protected override object EvaluateInternal(EvaluationContext context) { newLeftValue = JsValue.Undefined; } - else if (!AreIntegerOperands(originalLeftValue, rval)) - { - newLeftValue = TypeConverter.ToNumber(originalLeftValue) % TypeConverter.ToNumber(rval); - } else { newLeftValue = TypeConverter.ToNumber(originalLeftValue) % TypeConverter.ToNumber(rval); diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs index b17a6dd703..76a9abeb12 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs @@ -63,7 +63,7 @@ internal static bool TryOperatorOverloading( var leftMethods = leftType.GetOperatorOverloadMethods(); var rightMethods = rightType.GetOperatorOverloadMethods(); - var methods = leftMethods.Concat(rightMethods).Where(x => x.Name == clrName && x.GetParameters().Length == 2); + var methods = leftMethods.Concat(rightMethods).Where(x => string.Equals(x.Name, clrName, StringComparison.Ordinal) && x.GetParameters().Length == 2); var methodDescriptors = MethodDescriptor.Build(methods.ToArray()); return TypeConverter.FindBestMatch(context.Engine, methodDescriptors, _ => arguments).FirstOrDefault().Method; diff --git a/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs index e4d302efd1..da6e7f87ca 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs @@ -15,12 +15,12 @@ public JintMetaPropertyExpression(MetaProperty expression) : base(expression) protected override object EvaluateInternal(EvaluationContext context) { var expression = (MetaProperty) _expression; - if (expression.Meta.Name == "new" && expression.Property.Name == "target") + if (string.Equals(expression.Meta.Name, "new", StringComparison.Ordinal) && string.Equals(expression.Property.Name, "target", StringComparison.Ordinal)) { return context.Engine.GetNewTarget(); } - if (expression.Meta.Name == "import" && expression.Property.Name == "meta") + if (string.Equals(expression.Meta.Name, "import", StringComparison.Ordinal) && string.Equals(expression.Property.Name, "meta", StringComparison.Ordinal)) { var module = (SourceTextModuleRecord) context.Engine.ExecutionContext.ScriptOrModule!; var importMeta = module.ImportMeta; diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs index 437c7899a1..1231f79537 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs @@ -87,7 +87,7 @@ private void Initialize() if (!p.Computed && p.Key is Identifier identifier) { propName = identifier.Name; - _canBuildFast &= propName != "__proto__"; + _canBuildFast &= !string.Equals(propName, "__proto__", StringComparison.Ordinal); } _properties[i] = new ObjectProperty(propName, p); @@ -206,7 +206,7 @@ private object BuildObjectNormal(EvaluationContext context) } var propValue = completion.Clone(); - if (objectProperty._key == "__proto__" && !objectProperty._value.Computed && !objectProperty._value.Shorthand) + if (string.Equals(objectProperty._key, "__proto__", StringComparison.Ordinal) && !objectProperty._value.Computed && !objectProperty._value.Shorthand) { if (propValue.IsObject() || propValue.IsNull()) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs index aa02c80fe4..3d0b97a600 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs @@ -306,7 +306,7 @@ internal static bool TryOperatorOverloading(EvaluationContext context, JsValue v MethodInfo? foundMethod = null; foreach (var x in operandType.GetOperatorOverloadMethods()) { - if (x.Name == clrName && x.GetParameters().Length == 1) + if (string.Equals(x.Name, clrName, StringComparison.Ordinal) && x.GetParameters().Length == 1) { foundMethod = x; break; diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index 8781bf9ac7..7b8f20237b 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -341,7 +341,7 @@ private static void GetBoundNames( { _hasDuplicates |= checkDuplicates && target.Contains(identifier.Name); target.Add(identifier.Name); - hasArguments |= identifier.Name == "arguments"; + hasArguments |= string.Equals(identifier.Name, "arguments", StringComparison.Ordinal); return; } @@ -431,7 +431,7 @@ private static void ProcessParameters( { var id = (Identifier) parameter; state.HasDuplicates |= parameterNames.Contains(id.Name); - hasArguments = id.Name == "arguments"; + hasArguments = string.Equals(id.Name, "arguments", StringComparison.Ordinal); parameterNames.Add(id.Name); } else if (type != Nodes.Literal) @@ -485,7 +485,7 @@ private static bool HasArgumentsReference(Node node) var childType = childNode.Type; if (childType == Nodes.Identifier) { - if (((Identifier) childNode).Name == "arguments") + if (string.Equals(((Identifier) childNode).Name, "arguments", StringComparison.Ordinal)) { return true; } diff --git a/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs b/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs index fca4e299ee..10ab83138c 100644 --- a/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs @@ -37,9 +37,9 @@ protected override Completion ExecuteInternal(EvaluationContext context) v = completion.Value; } - if (completion.Type != CompletionType.Continue || context.Target != _labelSetName) + if (completion.Type != CompletionType.Continue || !string.Equals(context.Target, _labelSetName, StringComparison.Ordinal)) { - if (completion.Type == CompletionType.Break && (context.Target == null || context.Target == _labelSetName)) + if (completion.Type == CompletionType.Break && (context.Target == null || string.Equals(context.Target, _labelSetName, StringComparison.Ordinal))) { return new Completion(CompletionType.Normal, v, _statement); } diff --git a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs index e77f932a1a..40acf7ffaa 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs @@ -246,6 +246,7 @@ private Completion BodyEvaluation( { // DestructuringAssignmentEvaluation of assignmentPattern using nextValue as the argument. } +#pragma warning disable MA0140 else if (lhsKind == LhsKind.VarBinding) { // BindingInitialization for lhs passing nextValue and undefined as the arguments. @@ -254,6 +255,7 @@ private Completion BodyEvaluation( { // BindingInitialization for lhs passing nextValue and iterationEnv as arguments } +#pragma warning restore MA0140 } if (status != CompletionType.Normal) @@ -282,13 +284,13 @@ private Completion BodyEvaluation( v = result.Value; } - if (result.Type == CompletionType.Break && (context.Target == null || context.Target == _statement?.LabelSet?.Name)) + if (result.Type == CompletionType.Break && (context.Target == null || string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal))) { completionType = CompletionType.Normal; return new Completion(CompletionType.Normal, v, _statement!); } - if (result.Type != CompletionType.Continue || (context.Target != null && context.Target != _statement?.LabelSet?.Name)) + if (result.Type != CompletionType.Continue || (context.Target != null && !string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal))) { completionType = result.Type; if (result.IsAbrupt()) @@ -318,7 +320,9 @@ private Completion BodyEvaluation( if (completionType != CompletionType.Throw) { #pragma warning disable CA2219 +#pragma warning disable MA0072 throw; +#pragma warning restore MA0072 #pragma warning restore CA2219 } } diff --git a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs index 3ec3c92454..42f5df951f 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs @@ -141,12 +141,12 @@ private Completion ForBodyEvaluation(EvaluationContext context) v = result.Value; } - if (result.Type == CompletionType.Break && (context.Target == null || context.Target == _statement?.LabelSet?.Name)) + if (result.Type == CompletionType.Break && (context.Target == null || string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal))) { return new Completion(CompletionType.Normal, result.Value!, ((JintStatement) this)._statement); } - if (result.Type != CompletionType.Continue || (context.Target != null && context.Target != _statement?.LabelSet?.Name)) + if (result.Type != CompletionType.Continue || (context.Target != null && !string.Equals(context.Target, _statement?.LabelSet?.Name, StringComparison.Ordinal))) { if (result.Type != CompletionType.Normal) { diff --git a/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.cs b/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.cs index 28047649bc..dca3c85358 100644 --- a/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.cs @@ -23,7 +23,7 @@ protected override Completion ExecuteInternal(EvaluationContext context) // containing label and could keep a table per program with all the labels // labeledStatement.Body.LabelSet = labeledStatement.Label; var result = _body.Execute(context); - if (result.Type == CompletionType.Break && context.Target == _labelName) + if (result.Type == CompletionType.Break && string.Equals(context.Target, _labelName, StringComparison.Ordinal)) { var value = result.Value; return new Completion(CompletionType.Normal, value, _statement); diff --git a/Jint/Runtime/Interpreter/Statements/JintSwitchStatement.cs b/Jint/Runtime/Interpreter/Statements/JintSwitchStatement.cs index e7e2227aa0..3f026cd942 100644 --- a/Jint/Runtime/Interpreter/Statements/JintSwitchStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintSwitchStatement.cs @@ -25,7 +25,7 @@ protected override Completion ExecuteInternal(EvaluationContext context) { var value = _discriminant.GetValue(context); var r = _switchBlock.Execute(context, value); - if (r.Type == CompletionType.Break && context.Target == _statement.LabelSet?.Name) + if (r.Type == CompletionType.Break && string.Equals(context.Target, _statement.LabelSet?.Name, StringComparison.Ordinal)) { return new Completion(CompletionType.Normal, r.Value, ((JintStatement) this)._statement); } diff --git a/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs b/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs index 8d984c76bd..fd8a1d7656 100644 --- a/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs @@ -47,9 +47,9 @@ protected override Completion ExecuteInternal(EvaluationContext context) v = completion.Value; } - if (completion.Type != CompletionType.Continue || context.Target != _labelSetName) + if (completion.Type != CompletionType.Continue || !string.Equals(context.Target, _labelSetName, StringComparison.Ordinal)) { - if (completion.Type == CompletionType.Break && (context.Target == null || context.Target == _labelSetName)) + if (completion.Type == CompletionType.Break && (context.Target == null || string.Equals(context.Target, _labelSetName, StringComparison.Ordinal))) { return new Completion(CompletionType.Normal, v, _statement); } diff --git a/Jint/Runtime/Modules/CyclicModuleRecord.cs b/Jint/Runtime/Modules/CyclicModuleRecord.cs index eedaf497be..268c147090 100644 --- a/Jint/Runtime/Modules/CyclicModuleRecord.cs +++ b/Jint/Runtime/Modules/CyclicModuleRecord.cs @@ -435,7 +435,7 @@ private Completion ExecuteAsyncModule() /// /// https://tc39.es/ecma262/#sec-async-module-execution-fulfilled /// - private JsValue AsyncModuleExecutionFulfilled(JsValue thisObject, JsValue[] arguments) + private static JsValue AsyncModuleExecutionFulfilled(JsValue thisObject, JsValue[] arguments) { var module = (CyclicModuleRecord) arguments.At(0); if (module.Status == ModuleStatus.Evaluated) diff --git a/Jint/Runtime/Modules/ModuleNamespace.cs b/Jint/Runtime/Modules/ModuleNamespace.cs index 6523b87a5d..3213797d67 100644 --- a/Jint/Runtime/Modules/ModuleNamespace.cs +++ b/Jint/Runtime/Modules/ModuleNamespace.cs @@ -20,7 +20,7 @@ internal sealed class ModuleNamespace : ObjectInstance public ModuleNamespace(Engine engine, ModuleRecord module, List exports) : base(engine) { _module = module; - _exports = new HashSet(exports); + _exports = new HashSet(exports, StringComparer.Ordinal); } protected override void Initialize() @@ -162,7 +162,7 @@ public override JsValue Get(JsValue property, JsValue receiver) var binding = m.ResolveExport(p); var targetModule = binding.Module; - if (binding.BindingName == "*namespace*") + if (string.Equals(binding.BindingName, "*namespace*", StringComparison.Ordinal)) { return ModuleRecord.GetModuleNamespace(targetModule); } @@ -204,7 +204,7 @@ public override bool Delete(JsValue property) public override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) { var result = new List(); - if ((types & Types.String) != 0) + if ((types & Types.String) != Types.None) { result.Capacity = _exports.Count; foreach (var export in _exports) diff --git a/Jint/Runtime/Modules/SourceTextModuleRecord.cs b/Jint/Runtime/Modules/SourceTextModuleRecord.cs index 11a1fb478f..06d55650f8 100644 --- a/Jint/Runtime/Modules/SourceTextModuleRecord.cs +++ b/Jint/Runtime/Modules/SourceTextModuleRecord.cs @@ -127,7 +127,7 @@ internal override ResolvedBinding ResolveExport(string exportName, List(); + var declaredVarNames = new HashSet(StringComparer.Ordinal); if (varDeclarations != null) { var boundNames = new List(); @@ -320,7 +320,7 @@ protected override void InitializeEnvironment() env.CreateMutableBinding(fn, true); // TODO private scope var fo = realm.Intrinsics.Function.InstantiateFunctionObject(fd, env, privateEnv: null); - if (fn == "*default*") + if (string.Equals(fn, "*default*", StringComparison.Ordinal)) { fo.SetFunctionName("default"); } diff --git a/Jint/Runtime/References/Reference.cs b/Jint/Runtime/References/Reference.cs index f1a19fb14d..cdf88353e1 100644 --- a/Jint/Runtime/References/Reference.cs +++ b/Jint/Runtime/References/Reference.cs @@ -52,7 +52,7 @@ public bool Strict public bool HasPrimitiveBase { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_base._type & InternalTypes.Primitive) != 0; + get => (_base._type & InternalTypes.Primitive) != InternalTypes.None; } public bool IsUnresolvableReference @@ -68,7 +68,7 @@ public bool IsUnresolvableReference public bool IsPropertyReference { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_base._type & (InternalTypes.Primitive | InternalTypes.Object)) != 0; + get => (_base._type & (InternalTypes.Primitive | InternalTypes.Object)) != InternalTypes.None; } public JsValue ThisValue @@ -96,7 +96,7 @@ internal Reference Reassign(JsValue baseValue, JsValue name, bool strict, JsValu internal void AssertValid(Realm realm) { if (_strict - && (_base._type & InternalTypes.ObjectEnvironmentRecord) != 0 + && (_base._type & InternalTypes.ObjectEnvironmentRecord) != InternalTypes.None && (CommonProperties.Eval.Equals(_referencedName) || CommonProperties.Arguments.Equals(_referencedName))) { ExceptionHelper.ThrowSyntaxError(realm); diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index 50da476c8b..381f22bf4d 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -800,7 +800,7 @@ internal static BigInteger BigIntegerModulo(BigInteger a, BigInteger n) if (value is JsString jsString) { - if (jsString.ToString() == "-0") + if (string.Equals(jsString.ToString(), "-0", StringComparison.Ordinal)) { return JsNumber.NegativeZero._value; } @@ -935,7 +935,7 @@ internal static string ToString(BigInteger bigInteger) public static JsValue ToPropertyKey(JsValue o) { const InternalTypes PropertyKeys = InternalTypes.String | InternalTypes.Symbol | InternalTypes.PrivateName; - return (o._type & PropertyKeys) != 0 + return (o._type & PropertyKeys) != InternalTypes.None ? o : ToPropertyKeyNonString(o); } @@ -945,7 +945,7 @@ private static JsValue ToPropertyKeyNonString(JsValue o) { const InternalTypes PropertyKeys = InternalTypes.String | InternalTypes.Symbol | InternalTypes.PrivateName; var primitive = ToPrimitive(o, Types.String); - return (primitive._type & PropertyKeys) != 0 + return (primitive._type & PropertyKeys) != InternalTypes.None ? primitive : ToStringNonString(primitive); } From 6c36bf255454feb5827838d1b87c986e11cab3c4 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 15 Nov 2023 17:25:04 +0200 Subject: [PATCH 40/44] Limit PrepareScript string caching to length 10 or less (#1683) --- Jint/Native/JsString.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jint/Native/JsString.cs b/Jint/Native/JsString.cs index ea055949a7..6a43ac3657 100644 --- a/Jint/Native/JsString.cs +++ b/Jint/Native/JsString.cs @@ -171,7 +171,7 @@ internal static JsString Create(string value) internal static JsString CachedCreate(string value) { - if (value.Length < 2) + if (value.Length is < 2 or > 10) { return Create(value); } From 6653ec842c0edde682574755a7b3c1f359d824c2 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 18 Nov 2023 18:08:01 +0200 Subject: [PATCH 41/44] Code changes to keep NET 8 analyzers happy (#1685) --- Jint/Engine.Modules.cs | 4 ++-- Jint/Extensions/Polyfills.cs | 12 ++++++++++++ Jint/Native/Array/ArrayConstructor.cs | 2 ++ Jint/Native/Array/ArrayPrototype.cs | 2 ++ Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs | 2 ++ Jint/Native/Boolean/BooleanPrototype.cs | 2 +- Jint/Native/DataView/DataViewPrototype.cs | 2 ++ Jint/Native/Date/DateConstructor.cs | 2 ++ Jint/Native/Date/DatePrototype.cs | 2 ++ Jint/Native/Date/MimeKit.cs | 1 + Jint/Native/Function/FunctionConstructor.cs | 6 +++--- Jint/Native/Function/FunctionInstance.Dynamic.cs | 4 ++-- Jint/Native/Function/FunctionPrototype.cs | 2 ++ Jint/Native/Global/GlobalObject.cs | 6 +++--- Jint/Native/Intl/IntlInstance.cs | 2 ++ Jint/Native/Iterator/IteratorInstance.cs | 2 +- Jint/Native/Json/JsonParser.cs | 4 ++-- Jint/Native/Map/MapConstructor.cs | 2 ++ Jint/Native/Map/MapPrototype.cs | 2 ++ Jint/Native/Object/ObjectConstructor.cs | 2 ++ Jint/Native/Object/ObjectPrototype.cs | 2 ++ Jint/Native/Promise/PromiseConstructor.cs | 2 +- Jint/Native/Promise/PromisePrototype.cs | 4 ++-- Jint/Native/Proxy/ProxyConstructor.cs | 2 ++ Jint/Native/Reflect/ReflectInstance.cs | 2 ++ Jint/Native/RegExp/RegExpConstructor.cs | 2 +- Jint/Native/RegExp/RegExpPrototype.cs | 16 +++++++++------- Jint/Native/Set/SetPrototype.cs | 2 ++ Jint/Native/ShadowRealm/ShadowRealm.cs | 2 +- Jint/Native/String/StringConstructor.cs | 4 +++- Jint/Native/String/StringPrototype.cs | 2 ++ Jint/Native/Symbol/SymbolConstructor.cs | 2 ++ Jint/Native/Symbol/SymbolPrototype.cs | 2 ++ .../TypedArray/IntrinsicTypedArrayConstructor.cs | 2 ++ .../TypedArray/IntrinsicTypedArrayPrototype.cs | 2 ++ Jint/Native/WeakMap/WeakMapPrototype.cs | 2 ++ Jint/Native/WeakSet/WeakSetPrototype.cs | 2 ++ Jint/Runtime/CallStack/JintCallStack.cs | 2 ++ Jint/Runtime/Interop/DefaultObjectConverter.cs | 2 +- Jint/Runtime/Interop/NamespaceReference.cs | 2 +- Jint/Runtime/Interop/ObjectWrapper.cs | 2 +- Jint/Runtime/Interop/TypeReference.cs | 6 ++++-- .../Expressions/JintCallExpression.cs | 2 +- .../Expressions/JintMemberExpression.cs | 2 +- .../Expressions/JintObjectExpression.cs | 2 +- Jint/Runtime/Modules/DefaultModuleLoader.cs | 2 +- Jint/Runtime/Modules/ModuleRecord.cs | 2 +- 47 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 Jint/Extensions/Polyfills.cs diff --git a/Jint/Engine.Modules.cs b/Jint/Engine.Modules.cs index 6306424082..d79bc9ae12 100644 --- a/Jint/Engine.Modules.cs +++ b/Jint/Engine.Modules.cs @@ -49,7 +49,7 @@ internal ModuleRecord LoadModule(string? referencingModuleLocation, string speci return module; } - private CyclicModuleRecord LoadFromBuilder(string specifier, ModuleBuilder moduleBuilder, ResolvedSpecifier moduleResolution) + private BuilderModuleRecord LoadFromBuilder(string specifier, ModuleBuilder moduleBuilder, ResolvedSpecifier moduleResolution) { var parsedModule = moduleBuilder.Parse(); var module = new BuilderModuleRecord(this, Realm, parsedModule, null, false); @@ -59,7 +59,7 @@ private CyclicModuleRecord LoadFromBuilder(string specifier, ModuleBuilder modul return module; } - private CyclicModuleRecord LoaderFromModuleLoader(ResolvedSpecifier moduleResolution) + private SourceTextModuleRecord LoaderFromModuleLoader(ResolvedSpecifier moduleResolution) { var parsedModule = ModuleLoader.LoadModule(this, moduleResolution); var module = new SourceTextModuleRecord(this, Realm, parsedModule, moduleResolution.Uri?.LocalPath, false); diff --git a/Jint/Extensions/Polyfills.cs b/Jint/Extensions/Polyfills.cs new file mode 100644 index 0000000000..f818922b0f --- /dev/null +++ b/Jint/Extensions/Polyfills.cs @@ -0,0 +1,12 @@ +namespace Jint; + +internal static class Polyfills +{ +#if NETFRAMEWORK || NETSTANDARD2_0 + internal static bool Contains(this string source, char c) => source.IndexOf(c) != -1; +#endif + +#if NETFRAMEWORK || NETSTANDARD2_0 + internal static bool StartsWith(this string source, char c) => source.Length > 0 && source[0] == c; +#endif +} diff --git a/Jint/Native/Array/ArrayConstructor.cs b/Jint/Native/Array/ArrayConstructor.cs index 52de03cb64..204e0156fc 100644 --- a/Jint/Native/Array/ArrayConstructor.cs +++ b/Jint/Native/Array/ArrayConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using System.Collections; using Jint.Collections; using Jint.Native.Function; diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index ff171f6051..f1e16039d6 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using System.Linq; using Jint.Collections; using Jint.Native.Iterator; diff --git a/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs b/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs index ae7aab0533..d89f20738e 100644 --- a/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs +++ b/Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/Boolean/BooleanPrototype.cs b/Jint/Native/Boolean/BooleanPrototype.cs index 2379426429..303fed71ac 100644 --- a/Jint/Native/Boolean/BooleanPrototype.cs +++ b/Jint/Native/Boolean/BooleanPrototype.cs @@ -52,7 +52,7 @@ private JsValue ValueOf(JsValue thisObject, JsValue[] arguments) return Undefined; } - private JsValue ToBooleanString(JsValue thisObject, JsValue[] arguments) + private JsString ToBooleanString(JsValue thisObject, JsValue[] arguments) { var b = ValueOf(thisObject, Arguments.Empty); return ((JsBoolean) b)._value ? JsString.TrueString : JsString.FalseString; diff --git a/Jint/Native/DataView/DataViewPrototype.cs b/Jint/Native/DataView/DataViewPrototype.cs index 304de07084..342e539af7 100644 --- a/Jint/Native/DataView/DataViewPrototype.cs +++ b/Jint/Native/DataView/DataViewPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.ArrayBuffer; using Jint.Native.Object; diff --git a/Jint/Native/Date/DateConstructor.cs b/Jint/Native/Date/DateConstructor.cs index 144162d420..ac89e19ccb 100644 --- a/Jint/Native/Date/DateConstructor.cs +++ b/Jint/Native/Date/DateConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Function; using Jint.Native.Object; diff --git a/Jint/Native/Date/DatePrototype.cs b/Jint/Native/Date/DatePrototype.cs index 47eeb4399b..68459a1f9e 100644 --- a/Jint/Native/Date/DatePrototype.cs +++ b/Jint/Native/Date/DatePrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/Jint/Native/Date/MimeKit.cs b/Jint/Native/Date/MimeKit.cs index 03e905feba..39d28e075c 100644 --- a/Jint/Native/Date/MimeKit.cs +++ b/Jint/Native/Date/MimeKit.cs @@ -1,4 +1,5 @@ #nullable disable +#pragma warning disable CA1510 namespace Jint.Native.Date; diff --git a/Jint/Native/Function/FunctionConstructor.cs b/Jint/Native/Function/FunctionConstructor.cs index e362f75265..c9637dbed6 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -67,7 +67,7 @@ internal FunctionInstance InstantiateFunctionObject( /// /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiateasyncfunctionobject /// - private FunctionInstance InstantiateAsyncFunctionObject( + private ScriptFunctionInstance InstantiateAsyncFunctionObject( JintFunctionDefinition functionDeclaration, EnvironmentRecord env, PrivateEnvironmentRecord? privateEnv) @@ -87,7 +87,7 @@ private FunctionInstance InstantiateAsyncFunctionObject( /// /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionobject /// - private FunctionInstance InstantiateOrdinaryFunctionObject( + private ScriptFunctionInstance InstantiateOrdinaryFunctionObject( JintFunctionDefinition functionDeclaration, EnvironmentRecord env, PrivateEnvironmentRecord? privateEnv) @@ -108,7 +108,7 @@ private FunctionInstance InstantiateOrdinaryFunctionObject( /// /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiategeneratorfunctionobject /// - private FunctionInstance InstantiateGeneratorFunctionObject( + private ScriptFunctionInstance InstantiateGeneratorFunctionObject( JintFunctionDefinition functionDeclaration, EnvironmentRecord scope, PrivateEnvironmentRecord? privateScope) diff --git a/Jint/Native/Function/FunctionInstance.Dynamic.cs b/Jint/Native/Function/FunctionInstance.Dynamic.cs index b3537b833c..57714f8429 100644 --- a/Jint/Native/Function/FunctionInstance.Dynamic.cs +++ b/Jint/Native/Function/FunctionInstance.Dynamic.cs @@ -115,7 +115,7 @@ internal FunctionInstance CreateDynamicFunction( break; } - if (p.IndexOf('/') != -1) + if (p.Contains('/')) { // ensure comments don't screw up things functionExpression += "\n" + p + "\n"; @@ -127,7 +127,7 @@ internal FunctionInstance CreateDynamicFunction( functionExpression += ")"; - if (body.IndexOf('/') != -1) + if (body.Contains('/')) { // ensure comments don't screw up things functionExpression += "{\n" + body + "\n}"; diff --git a/Jint/Native/Function/FunctionPrototype.cs b/Jint/Native/Function/FunctionPrototype.cs index 8bc50f9460..37f1c4b943 100644 --- a/Jint/Native/Function/FunctionPrototype.cs +++ b/Jint/Native/Function/FunctionPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Array; using Jint.Native.Object; diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index 383295e18a..5bb0bb41aa 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -320,7 +320,7 @@ private JsValue Encode(string uriString, string unescapedUriSet) for (var k = 0; k < strLen; k++) { var c = uriString[k]; - if (c is >= 'a' and <= 'z' || c is >= 'A' and <= 'Z' || c is >= '0' and <= '9' || unescapedUriSet.IndexOf(c) != -1) + if (c is >= 'a' and <= 'z' || c is >= 'A' and <= 'Z' || c is >= '0' and <= '9' || unescapedUriSet.Contains(c)) { _stringBuilder.Append(c); } @@ -594,7 +594,7 @@ private static bool IsDigit(char c, int radix, out int result) /// public JsValue Escape(JsValue thisObject, JsValue[] arguments) { - const string WhiteList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./"; + const string AllowList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./"; var uriString = TypeConverter.ToString(arguments.At(0)); var strLen = uriString.Length; @@ -605,7 +605,7 @@ public JsValue Escape(JsValue thisObject, JsValue[] arguments) for (var k = 0; k < strLen; k++) { var c = uriString[k]; - if (WhiteList.IndexOf(c) != -1) + if (AllowList.Contains(c)) { _stringBuilder.Append(c); } diff --git a/Jint/Native/Intl/IntlInstance.cs b/Jint/Native/Intl/IntlInstance.cs index 1e7e9f97e2..0853bff8a4 100644 --- a/Jint/Native/Intl/IntlInstance.cs +++ b/Jint/Native/Intl/IntlInstance.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/Iterator/IteratorInstance.cs b/Jint/Native/Iterator/IteratorInstance.cs index fd8c86ef4e..0aaa57907d 100644 --- a/Jint/Native/Iterator/IteratorInstance.cs +++ b/Jint/Native/Iterator/IteratorInstance.cs @@ -27,7 +27,7 @@ public virtual void Close(CompletionType completion) /// /// https://tc39.es/ecma262/#sec-createiterresultobject /// - private ObjectInstance CreateIterResultObject(JsValue value, bool done) + private IteratorResult CreateIterResultObject(JsValue value, bool done) { return new IteratorResult(_engine, value, JsBoolean.Create(done)); } diff --git a/Jint/Native/Json/JsonParser.cs b/Jint/Native/Json/JsonParser.cs index a1a593f0ee..c0a559b05d 100644 --- a/Jint/Native/Json/JsonParser.cs +++ b/Jint/Native/Json/JsonParser.cs @@ -506,7 +506,7 @@ public bool Match(char value) return _lookahead.Type == Tokens.Punctuator && value == _lookahead.FirstCharacter; } - private ObjectInstance ParseJsonArray(ref State state) + private JsArray ParseJsonArray(ref State state) { if ((++state.CurrentDepth) > _maxDepth) { @@ -603,7 +603,7 @@ when its full. return result ?? new JsArray(_engine, elements!.ToArray()); } - private ObjectInstance ParseJsonObject(ref State state) + private JsObject ParseJsonObject(ref State state) { if ((++state.CurrentDepth) > _maxDepth) { diff --git a/Jint/Native/Map/MapConstructor.cs b/Jint/Native/Map/MapConstructor.cs index 26d39f334b..a77d97993e 100644 --- a/Jint/Native/Map/MapConstructor.cs +++ b/Jint/Native/Map/MapConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Function; using Jint.Native.Iterator; diff --git a/Jint/Native/Map/MapPrototype.cs b/Jint/Native/Map/MapPrototype.cs index ff77ac3f70..2135387e04 100644 --- a/Jint/Native/Map/MapPrototype.cs +++ b/Jint/Native/Map/MapPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/Object/ObjectConstructor.cs b/Jint/Native/Object/ObjectConstructor.cs index c40a745bfd..9e5a651221 100644 --- a/Jint/Native/Object/ObjectConstructor.cs +++ b/Jint/Native/Object/ObjectConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Iterator; using Jint.Runtime; diff --git a/Jint/Native/Object/ObjectPrototype.cs b/Jint/Native/Object/ObjectPrototype.cs index 9dbaa826df..7b1655397b 100644 --- a/Jint/Native/Object/ObjectPrototype.cs +++ b/Jint/Native/Object/ObjectPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Array; using Jint.Native.Proxy; diff --git a/Jint/Native/Promise/PromiseConstructor.cs b/Jint/Native/Promise/PromiseConstructor.cs index 56b227b4e6..da3da29b27 100644 --- a/Jint/Native/Promise/PromiseConstructor.cs +++ b/Jint/Native/Promise/PromiseConstructor.cs @@ -114,7 +114,7 @@ internal JsValue Resolve(JsValue thisObject, JsValue[] arguments) return PromiseResolve(thisObject, x); } - private JsValue WithResolvers(JsValue thisObject, JsValue[] arguments) + private JsObject WithResolvers(JsValue thisObject, JsValue[] arguments) { var promiseCapability = NewPromiseCapability(_engine, thisObject); var obj = OrdinaryObjectCreate(_engine, _engine.Realm.Intrinsics.Object.PrototypeObject); diff --git a/Jint/Native/Promise/PromisePrototype.cs b/Jint/Native/Promise/PromisePrototype.cs index c81f91c7ef..5150ccebe9 100644 --- a/Jint/Native/Promise/PromisePrototype.cs +++ b/Jint/Native/Promise/PromisePrototype.cs @@ -118,7 +118,7 @@ private JsValue Finally(JsValue thisValue, JsValue[] arguments) } // https://tc39.es/ecma262/#sec-thenfinallyfunctions - private JsValue ThenFinallyFunctions(ICallable onFinally, IConstructor ctor) => + private ClrFunctionInstance ThenFinallyFunctions(ICallable onFinally, IConstructor ctor) => new ClrFunctionInstance(_engine, "", (_, args) => { var value = args.At(0); @@ -137,7 +137,7 @@ private JsValue ThenFinallyFunctions(ICallable onFinally, IConstructor ctor) => }, 1, PropertyFlag.Configurable); // https://tc39.es/ecma262/#sec-catchfinallyfunctions - private JsValue CatchFinallyFunctions(ICallable onFinally, IConstructor ctor) => + private ClrFunctionInstance CatchFinallyFunctions(ICallable onFinally, IConstructor ctor) => new ClrFunctionInstance(_engine, "", (_, args) => { var reason = args.At(0); diff --git a/Jint/Native/Proxy/ProxyConstructor.cs b/Jint/Native/Proxy/ProxyConstructor.cs index d06fdcd237..be8e81ec98 100644 --- a/Jint/Native/Proxy/ProxyConstructor.cs +++ b/Jint/Native/Proxy/ProxyConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Runtime; diff --git a/Jint/Native/Reflect/ReflectInstance.cs b/Jint/Native/Reflect/ReflectInstance.cs index 4713fe1516..a12f063ca1 100644 --- a/Jint/Native/Reflect/ReflectInstance.cs +++ b/Jint/Native/Reflect/ReflectInstance.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Function; using Jint.Native.Object; diff --git a/Jint/Native/RegExp/RegExpConstructor.cs b/Jint/Native/RegExp/RegExpConstructor.cs index 9e0a174377..1c1db22dbc 100644 --- a/Jint/Native/RegExp/RegExpConstructor.cs +++ b/Jint/Native/RegExp/RegExpConstructor.cs @@ -92,7 +92,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) return RegExpInitialize(r, p, f); } - private ObjectInstance RegExpInitialize(JsRegExp r, JsValue pattern, JsValue flags) + private JsRegExp RegExpInitialize(JsRegExp r, JsValue pattern, JsValue flags) { var p = pattern.IsUndefined() ? "" : TypeConverter.ToString(pattern); if (string.IsNullOrEmpty(p)) diff --git a/Jint/Native/RegExp/RegExpPrototype.cs b/Jint/Native/RegExp/RegExpPrototype.cs index 7b00c2dcd1..ab1d50ff82 100644 --- a/Jint/Native/RegExp/RegExpPrototype.cs +++ b/Jint/Native/RegExp/RegExpPrototype.cs @@ -1,4 +1,6 @@ -using System.Text.RegularExpressions; +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + +using System.Text.RegularExpressions; using Jint.Collections; using Jint.Native.Number; using Jint.Native.Object; @@ -139,17 +141,17 @@ private JsValue Replace(JsValue thisObject, JsValue[] arguments) { var value = TypeConverter.ToString(replaceValue); replaceValue = value; - mayHaveNamedCaptures = value.IndexOf('$') != -1; + mayHaveNamedCaptures = value.Contains('$'); } var flags = TypeConverter.ToString(rx.Get(PropertyFlags)); - var global = flags.IndexOf('g') != -1; + var global = flags.Contains('g'); var fullUnicode = false; if (global) { - fullUnicode = flags.IndexOf('u') != -1; + fullUnicode = flags.Contains('u'); rx.Set(JsRegExp.PropertyLastIndex, 0, true); } @@ -527,7 +529,7 @@ private JsValue Split(JsValue thisObject, JsValue[] arguments) return SplitSlow(s, splitter, unicodeMatching, lengthA, lim); } - private JsValue SplitSlow(string s, ObjectInstance splitter, bool unicodeMatching, uint lengthA, long lim) + private JsArray SplitSlow(string s, ObjectInstance splitter, bool unicodeMatching, uint lengthA, long lim) { var a = _realm.Intrinsics.Array.ArrayCreate(0); ulong previousStringIndex = 0; @@ -684,13 +686,13 @@ private JsValue Match(JsValue thisObject, JsValue[] arguments) var s = TypeConverter.ToString(arguments.At(0)); var flags = TypeConverter.ToString(rx.Get(PropertyFlags)); - var global = flags.IndexOf('g') != -1; + var global = flags.Contains('g'); if (!global) { return RegExpExec(rx, s); } - var fullUnicode = flags.IndexOf('u') != -1; + var fullUnicode = flags.Contains('u'); rx.Set(JsRegExp.PropertyLastIndex, JsNumber.PositiveZero, true); if (!fullUnicode diff --git a/Jint/Native/Set/SetPrototype.cs b/Jint/Native/Set/SetPrototype.cs index 5f97ca8c0b..11e0753c52 100644 --- a/Jint/Native/Set/SetPrototype.cs +++ b/Jint/Native/Set/SetPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/ShadowRealm/ShadowRealm.cs b/Jint/Native/ShadowRealm/ShadowRealm.cs index cc3db89dca..116af5c0c8 100644 --- a/Jint/Native/ShadowRealm/ShadowRealm.cs +++ b/Jint/Native/ShadowRealm/ShadowRealm.cs @@ -210,7 +210,7 @@ private static JsValue GetWrappedValue(Realm throwerRealm, Realm callerRealm, Js /// /// https://tc39.es/proposal-shadowrealm/#sec-wrappedfunctioncreate /// - private static JsValue WrappedFunctionCreate(Realm throwerRealm, Realm callerRealm, ObjectInstance target) + private static WrappedFunction WrappedFunctionCreate(Realm throwerRealm, Realm callerRealm, ObjectInstance target) { var wrapped = new WrappedFunction(callerRealm.GlobalEnv._engine, callerRealm, target); try diff --git a/Jint/Native/String/StringConstructor.cs b/Jint/Native/String/StringConstructor.cs index 28caa2cc20..5a30c3df72 100644 --- a/Jint/Native/String/StringConstructor.cs +++ b/Jint/Native/String/StringConstructor.cs @@ -1,4 +1,6 @@ -using Jint.Collections; +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + +using Jint.Collections; using Jint.Native.Array; using Jint.Native.Function; using Jint.Native.Object; diff --git a/Jint/Native/String/StringPrototype.cs b/Jint/Native/String/StringPrototype.cs index 408db21452..2beeece784 100644 --- a/Jint/Native/String/StringPrototype.cs +++ b/Jint/Native/String/StringPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/Jint/Native/Symbol/SymbolConstructor.cs b/Jint/Native/Symbol/SymbolConstructor.cs index 0252db8164..44dc27d33e 100644 --- a/Jint/Native/Symbol/SymbolConstructor.cs +++ b/Jint/Native/Symbol/SymbolConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Function; using Jint.Native.Object; diff --git a/Jint/Native/Symbol/SymbolPrototype.cs b/Jint/Native/Symbol/SymbolPrototype.cs index 7c29ecd05d..97e3522102 100644 --- a/Jint/Native/Symbol/SymbolPrototype.cs +++ b/Jint/Native/Symbol/SymbolPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Runtime; diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs index 9b019a099c..7695d36029 100644 --- a/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs index 039b84a97d..dff4978054 100644 --- a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using System.Linq; using Jint.Collections; using Jint.Native.Array; diff --git a/Jint/Native/WeakMap/WeakMapPrototype.cs b/Jint/Native/WeakMap/WeakMapPrototype.cs index 528328567f..822854c5b0 100644 --- a/Jint/Native/WeakMap/WeakMapPrototype.cs +++ b/Jint/Native/WeakMap/WeakMapPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Native/WeakSet/WeakSetPrototype.cs b/Jint/Native/WeakSet/WeakSetPrototype.cs index 43e8ea2c02..15e8e038bb 100644 --- a/Jint/Native/WeakSet/WeakSetPrototype.cs +++ b/Jint/Native/WeakSet/WeakSetPrototype.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue + using Jint.Collections; using Jint.Native.Object; using Jint.Native.Symbol; diff --git a/Jint/Runtime/CallStack/JintCallStack.cs b/Jint/Runtime/CallStack/JintCallStack.cs index 7c67db4bf1..d6a234791d 100644 --- a/Jint/Runtime/CallStack/JintCallStack.cs +++ b/Jint/Runtime/CallStack/JintCallStack.cs @@ -63,8 +63,10 @@ public int Push(FunctionInstance functionInstance, JintExpression? expression, i if (_statistics is not null) { #pragma warning disable CA1854 +#pragma warning disable CA1864 if (_statistics.ContainsKey(item)) #pragma warning restore CA1854 +#pragma warning restore CA1864 { return ++_statistics[item]; } diff --git a/Jint/Runtime/Interop/DefaultObjectConverter.cs b/Jint/Runtime/Interop/DefaultObjectConverter.cs index f94fa7bf10..e7cc6654d4 100644 --- a/Jint/Runtime/Interop/DefaultObjectConverter.cs +++ b/Jint/Runtime/Interop/DefaultObjectConverter.cs @@ -162,7 +162,7 @@ private static bool TryConvertConvertible(Engine engine, IConvertible convertibl return result is not null; } - private static JsValue ConvertArray(Engine e, object v) + private static JsArray ConvertArray(Engine e, object v) { var array = (Array) v; var arrayLength = (uint) array.Length; diff --git a/Jint/Runtime/Interop/NamespaceReference.cs b/Jint/Runtime/Interop/NamespaceReference.cs index 4a58c21f67..46dfe9515a 100644 --- a/Jint/Runtime/Interop/NamespaceReference.cs +++ b/Jint/Runtime/Interop/NamespaceReference.cs @@ -117,7 +117,7 @@ public JsValue GetPath(string path) return TypeReference.CreateTypeReference(_engine, type); } - var lastPeriodPos = path.LastIndexOf(".", StringComparison.Ordinal); + var lastPeriodPos = path.LastIndexOf('.'); var trimPath = path.Substring(0, lastPeriodPos); type = GetType(assembly, trimPath); if (type != null) diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index 2fc549d7c1..ec7a127a5e 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -290,7 +290,7 @@ private static JsValue Iterator(JsValue thisObject, JsValue[] arguments) : new EnumerableIterator(wrapper._engine, (IEnumerable) wrapper.Target); } - private static JsValue GetLength(JsValue thisObject, JsValue[] arguments) + private static JsNumber GetLength(JsValue thisObject, JsValue[] arguments) { var wrapper = (ObjectWrapper) thisObject; return JsNumber.Create((int) (wrapper._typeDescriptor.LengthProperty?.GetValue(wrapper.Target) ?? 0)); diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index ea1a990eab..40f14cbbb5 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -327,7 +327,7 @@ private static ReflectionAccessor ResolveMemberAccessor(Engine engine, Type type public object Target => ReferenceType; - private static JsValue HasInstance(JsValue thisObject, JsValue[] arguments) + private static JsBoolean HasInstance(JsValue thisObject, JsValue[] arguments) { var typeReference = thisObject as TypeReference; var other = arguments.At(0); @@ -346,7 +346,9 @@ private static JsValue HasInstance(JsValue thisObject, JsValue[] arguments) _ => null }; - return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType)); + return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType)) + ? JsBoolean.True + : JsBoolean.False; } public override string ToString() diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs index 6f0ba09c61..53c8e1241f 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs @@ -251,7 +251,7 @@ private JsValue HandleEval(EvaluationContext context, JsValue func, Engine engin return value; } - private JsValue SuperCall(EvaluationContext context) + private ObjectInstance SuperCall(EvaluationContext context) { var engine = context.Engine; var thisEnvironment = (FunctionEnvironmentRecord) engine.ExecutionContext.GetThisEnvironment(); diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs index 22229a0454..29251b90a0 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs @@ -140,7 +140,7 @@ protected override object EvaluateInternal(EvaluationContext context) /// /// https://tc39.es/ecma262/#sec-makeprivatereference /// - private static object MakePrivateReference(Engine engine, JsValue baseValue, JsValue privateIdentifier) + private static Reference MakePrivateReference(Engine engine, JsValue baseValue, JsValue privateIdentifier) { var privEnv = engine.ExecutionContext.PrivateEnvironment; var privateName = privEnv!.ResolvePrivateIdentifier(privateIdentifier.ToString()); diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs index 1231f79537..9b7c3d32e7 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs @@ -134,7 +134,7 @@ protected override object EvaluateInternal(EvaluationContext context) /// /// Version that can safely build plain object with only normal init/data fields fast. /// - private object BuildObjectFast(EvaluationContext context) + private JsObject BuildObjectFast(EvaluationContext context) { var obj = new JsObject(context.Engine); var properties = new PropertyDictionary(_properties.Length, checkExistingKeys: true); diff --git a/Jint/Runtime/Modules/DefaultModuleLoader.cs b/Jint/Runtime/Modules/DefaultModuleLoader.cs index ee6fbfa5c7..b019f8c54e 100644 --- a/Jint/Runtime/Modules/DefaultModuleLoader.cs +++ b/Jint/Runtime/Modules/DefaultModuleLoader.cs @@ -151,6 +151,6 @@ public Module LoadModule(Engine engine, ResolvedSpecifier resolved) private static bool IsRelative(string specifier) { - return specifier.StartsWith(".", StringComparison.Ordinal) || specifier.StartsWith("/", StringComparison.Ordinal); + return specifier.StartsWith('.') || specifier.StartsWith('/'); } } diff --git a/Jint/Runtime/Modules/ModuleRecord.cs b/Jint/Runtime/Modules/ModuleRecord.cs index 21b27460c0..043efbb890 100644 --- a/Jint/Runtime/Modules/ModuleRecord.cs +++ b/Jint/Runtime/Modules/ModuleRecord.cs @@ -67,7 +67,7 @@ public static ObjectInstance GetModuleNamespace(ModuleRecord module) /// /// https://tc39.es/ecma262/#sec-modulenamespacecreate /// - private static ObjectInstance CreateModuleNamespace(ModuleRecord module, List unambiguousNames) + private static ModuleNamespace CreateModuleNamespace(ModuleRecord module, List unambiguousNames) { var m = new ModuleNamespace(module._engine, module, unambiguousNames); module._namespace = m; From 270e41459e185f2e0b948ca4ae407c7e35a1aa1b Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 19 Nov 2023 10:20:04 +0200 Subject: [PATCH 42/44] Refine interop member search with readable/writable requirement (#1687) --- Jint.Tests/Runtime/InteropTests.cs | 88 +++++++++++++++++++ .../Specialized/ReflectionDescriptor.cs | 5 +- Jint/Runtime/Interop/ObjectWrapper.cs | 28 ++++-- Jint/Runtime/Interop/TypeResolver.cs | 13 +-- 4 files changed, 122 insertions(+), 12 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index e054e0bf42..3fb9c50ab9 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -3333,5 +3333,93 @@ public void CanDestructureInteropTargetMethod() var result = engine.Evaluate("const { getStrings } = test; getStrings().Count;"); Assert.Equal(3, result); } + + private class MetadataWrapper : IDictionary + { + public IEnumerator> GetEnumerator() => throw new NotImplementedException(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Add(KeyValuePair item) => throw new NotImplementedException(); + public void Clear() => throw new NotImplementedException(); + public bool Contains(KeyValuePair item) => throw new NotImplementedException(); + public void CopyTo(KeyValuePair[] array, int arrayIndex) => throw new NotImplementedException(); + public bool Remove(KeyValuePair item) => throw new NotImplementedException(); + public int Count { get; set; } + public bool IsReadOnly { get; set; } + public bool ContainsKey(string key) => throw new NotImplementedException(); + public void Add(string key, object value) => throw new NotImplementedException(); + public bool Remove(string key) => throw new NotImplementedException(); + public bool TryGetValue(string key, out object value) + { + value = "from-wrapper"; + return true; + } + + public object this[string key] + { + get => "from-wrapper"; + set + { + } + } + + public ICollection Keys { get; set; } + public ICollection Values { get; set; } + } + + private class ShadowedGetter : IReadOnlyDictionary + { + private Dictionary _dictionary = new(); + + public void SetInitial(object? value, string? key) + { + _dictionary[key] = value; + } + + public IEnumerator> GetEnumerator() => throw new NotImplementedException(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count { get; } + public bool ContainsKey(string key) => _dictionary.ContainsKey(key); + + public bool TryGetValue(string key, out object value) => _dictionary.TryGetValue(key, out value); + + public object this[string key] + { + get + { + _dictionary.TryGetValue(key, out var value); + return value; + } + } + + public IEnumerable Keys { get; set; } + public IEnumerable Values { get; set; } + } + + private class ShadowingSetter : ShadowedGetter + { + public Dictionary Metadata + { + set + { + SetInitial(new MetadataWrapper(), "metadata"); + } + } + } + + [Fact] + public void CanSelectShadowedPropertiesBasedOnReadableAndWritable() + { + var engine = new Engine(); + engine.SetValue("test", new ShadowingSetter + { + Metadata = null + }); + + engine.Evaluate("test.metadata['abc'] = 123"); + var result = engine.Evaluate("test.metadata['abc']"); + Assert.Equal("from-wrapper", result); + } } } diff --git a/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs index abfe7b4bb5..bec4af0ce7 100644 --- a/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs @@ -43,13 +43,14 @@ protected internal override JsValue? CustomValue set => DoSet(null, value); } - JsValue DoGet(JsValue? thisObj) + private JsValue DoGet(JsValue? thisObj) { var value = _reflectionAccessor.GetValue(_engine, _target); var type = _reflectionAccessor.MemberType; return JsValue.FromObjectWithType(_engine, value, type); } - void DoSet(JsValue? thisObj, JsValue? v) + + private void DoSet(JsValue? thisObj, JsValue? v) { try { diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index ec7a127a5e..7703613325 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -50,7 +50,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) if (_properties is null || !_properties.ContainsKey(member)) { // can try utilize fast path - var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member, forWrite: true); + var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member, mustBeReadable: false, mustBeWritable: true); if (ReferenceEquals(accessor, ConstantValueAccessor.NullAccessor)) { @@ -116,7 +116,13 @@ public override JsValue Get(JsValue property, JsValue receiver) return (uint) index < list.Count ? FromObject(_engine, list[index]) : Undefined; } - return base.Get(property, receiver); + var desc = GetOwnProperty(property, mustBeReadable: true, mustBeWritable: false); + if (desc != PropertyDescriptor.Undefined) + { + return UnwrapJsValue(desc, receiver); + } + + return Prototype?.Get(property, receiver) ?? Undefined; } public override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol) @@ -182,6 +188,12 @@ private IEnumerable EnumerateOwnPropertyKeys(Types types) } public override PropertyDescriptor GetOwnProperty(JsValue property) + { + // we do not know if we need to read or write + return GetOwnProperty(property, mustBeReadable: false, mustBeWritable: false); + } + + private PropertyDescriptor GetOwnProperty(JsValue property, bool mustBeReadable, bool mustBeWritable) { if (TryGetProperty(property, out var x)) { @@ -234,13 +246,17 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) return new PropertyDescriptor(result, PropertyFlag.OnlyEnumerable); } - var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member); + var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member, mustBeReadable, mustBeWritable); var descriptor = accessor.CreatePropertyDescriptor(_engine, Target, enumerable: !isDictionary); - if (!isDictionary && !ReferenceEquals(descriptor, PropertyDescriptor.Undefined)) + if (!isDictionary + && !ReferenceEquals(descriptor, PropertyDescriptor.Undefined) + && (!mustBeReadable || accessor.Readable) + && (!mustBeWritable || accessor.Writable)) { // cache the accessor for faster subsequent accesses SetProperty(member, descriptor); } + return descriptor; } @@ -258,7 +274,9 @@ public static PropertyDescriptor GetPropertyDescriptor(Engine engine, object tar _ => null }; } - return engine.Options.Interop.TypeResolver.GetAccessor(engine, target.GetType(), member.Name, Factory).CreatePropertyDescriptor(engine, target); + + var accessor = engine.Options.Interop.TypeResolver.GetAccessor(engine, target.GetType(), member.Name, mustBeReadable: false, mustBeWritable: false, Factory); + return accessor.CreatePropertyDescriptor(engine, target); } internal static Type GetClrType(object obj, Type? type) diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index b4fcb7f6b2..eb212b5bf0 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -47,8 +47,9 @@ internal ReflectionAccessor GetAccessor( Engine engine, Type type, string member, - Func? accessorFactory = null, - bool forWrite = false) + bool mustBeReadable, + bool mustBeWritable, + Func? accessorFactory = null) { var key = new Engine.ClrPropertyDescriptorFactoriesKey(type, member); @@ -58,7 +59,7 @@ internal ReflectionAccessor GetAccessor( return accessor; } - accessor = accessorFactory?.Invoke() ?? ResolvePropertyDescriptorFactory(engine, type, member, forWrite); + accessor = accessorFactory?.Invoke() ?? ResolvePropertyDescriptorFactory(engine, type, member, mustBeReadable, mustBeWritable); // racy, we don't care, worst case we'll catch up later Interlocked.CompareExchange(ref engine._reflectionAccessors, @@ -74,7 +75,8 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( Engine engine, Type type, string memberName, - bool forWrite) + bool mustBeReadable, + bool mustBeWritable) { var isInteger = long.TryParse(memberName, NumberStyles.Integer, CultureInfo.InvariantCulture, out _); @@ -86,7 +88,8 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( // properties and fields cannot be numbers if (!isInteger && TryFindMemberAccessor(engine, type, memberName, BindingFlags, indexer, out var temp) - && (!forWrite || temp.Writable)) + && (!mustBeReadable || temp.Readable) + && (!mustBeWritable || temp.Writable)) { return temp; } From e8f74fa40c52e37c444c7a22f7c0e908844d5b66 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 19 Nov 2023 10:34:11 +0200 Subject: [PATCH 43/44] Add README.md to NuGet package (#1688) --- Jint/Jint.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index abe496c3db..de8422640d 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -16,6 +16,10 @@ $(DefineConstants);SUPPORTS_SPAN_PARSE;SUPPORTS_WEAK_TABLE_ADD_OR_UPDATE;SUPPORTS_WEAK_TABLE_CLEAR + + + + From 4b4d64f64463f23585ab55e39af47eac7182abce Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sun, 19 Nov 2023 10:57:53 +0200 Subject: [PATCH 44/44] Package XML documentation (#1689) --- Directory.Build.props | 7 +++++++ Jint.Tests/Runtime/InteropTests.cs | 4 ++-- Jint.sln | 1 + Jint/Collections/DictionarySlim.cs | 2 +- Jint/Collections/StringDictionarySlim.cs | 2 +- Jint/Jint.csproj | 11 ++++++++++- Jint/Options.Extensions.cs | 3 +++ Jint/Options.cs | 2 +- Jint/Runtime/Debugger/DebuggerStatementHandling.cs | 2 +- 9 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000000..d4159e07cc --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,7 @@ + + + + true + + + diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 3fb9c50ab9..a62b514872 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -3334,7 +3334,7 @@ public void CanDestructureInteropTargetMethod() Assert.Equal(3, result); } - private class MetadataWrapper : IDictionary + private class MetadataWrapper : IDictionary { public IEnumerator> GetEnumerator() => throw new NotImplementedException(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -3370,7 +3370,7 @@ private class ShadowedGetter : IReadOnlyDictionary { private Dictionary _dictionary = new(); - public void SetInitial(object? value, string? key) + public void SetInitial(object value, string key) { _dictionary[key] = value; } diff --git a/Jint.sln b/Jint.sln index 7cc5e19cee..fad3255cfe 100644 --- a/Jint.sln +++ b/Jint.sln @@ -22,6 +22,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md .editorconfig = .editorconfig Directory.Packages.props = Directory.Packages.props + Directory.Build.props = Directory.Build.props EndProjectSection EndProject Global diff --git a/Jint/Collections/DictionarySlim.cs b/Jint/Collections/DictionarySlim.cs index 6f77fafbb3..03729cd2d3 100644 --- a/Jint/Collections/DictionarySlim.cs +++ b/Jint/Collections/DictionarySlim.cs @@ -12,7 +12,7 @@ namespace Jint.Collections { /// - /// DictionarySlim is similar to Dictionary but optimized in three ways: + /// DictionarySlim<string, TValue> is similar to Dictionary<TKey, TValue> but optimized in three ways: /// 1) It allows access to the value by ref replacing the common TryGetValue and Add pattern. /// 2) It does not store the hash code (assumes it is cheap to equate values). /// 3) It does not accept an equality comparer (assumes Object.GetHashCode() and Object.Equals() or overridden implementation are cheap and sufficient). diff --git a/Jint/Collections/StringDictionarySlim.cs b/Jint/Collections/StringDictionarySlim.cs index d9621f5eb4..2ff194f1ee 100644 --- a/Jint/Collections/StringDictionarySlim.cs +++ b/Jint/Collections/StringDictionarySlim.cs @@ -15,7 +15,7 @@ namespace Jint.Collections { /// - /// DictionarySlim is similar to Dictionary but optimized in three ways: + /// DictionarySlim<string, TValue> is similar to Dictionary<TKey, TValue> but optimized in three ways: /// 1) It allows access to the value by ref replacing the common TryGetValue and Add pattern. /// 2) It does not store the hash code (assumes it is cheap to equate values). /// 3) It does not accept an equality comparer (assumes Object.GetHashCode() and Object.Equals() or overridden implementation are cheap and sufficient). diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index de8422640d..bcc00fb46c 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -2,14 +2,23 @@ en-US net462;netstandard2.0;netstandard2.1;net6.0 + Jint.snk true - latest true + + latest enable enable true + latest-Recommended + + true + README.md + + $(NoWarn);1591 + diff --git a/Jint/Options.Extensions.cs b/Jint/Options.Extensions.cs index 6c682fb1a2..c103dac322 100644 --- a/Jint/Options.Extensions.cs +++ b/Jint/Options.Extensions.cs @@ -75,6 +75,7 @@ public static Options AddObjectConverter(this Options options, IObjectConverter /// /// Sets maximum allowed depth of recursion. /// + /// Options to modify /// /// The allowed depth. /// a) In case max depth is zero no recursion is allowed. @@ -140,6 +141,7 @@ public static Options SetTypeConverter(this Options options, Func + /// Options to modify /// /// The delegate to invoke for each CLR member. If the delegate /// returns null, the standard evaluation is performed. @@ -246,6 +248,7 @@ public static Options SetTypeResolver(this Options options, TypeResolver resolve /// Registers some custom logic to apply on an instance when the options /// are loaded. /// + /// Options to modify /// The action to register. public static Options Configure(this Options options, Action configuration) { diff --git a/Jint/Options.cs b/Jint/Options.cs index ffc622c00c..7c5a119e9d 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -400,7 +400,7 @@ public class ConstraintOptions /// /// Chrome and V8 based engines (ClearScript) that can handle 13955. /// When set to a different value except -1, it can reduce slight performance/stack trace readability drawback. (after hitting the engine's own limit), - /// When max stack size to be exceeded, Engine throws an exception . + /// When max stack size to be exceeded, Engine throws an exception . /// public int MaxExecutionStackCount { get; set; } = StackGuard.Disabled; diff --git a/Jint/Runtime/Debugger/DebuggerStatementHandling.cs b/Jint/Runtime/Debugger/DebuggerStatementHandling.cs index ff756eb121..33452f7c2e 100644 --- a/Jint/Runtime/Debugger/DebuggerStatementHandling.cs +++ b/Jint/Runtime/Debugger/DebuggerStatementHandling.cs @@ -16,7 +16,7 @@ public enum DebuggerStatementHandling Clr, /// - /// debugger statements will trigger a break in Jint's DebugHandler. See . + /// debugger statements will trigger a break in Jint's DebugHandler. See . /// Script }