using System.Threading; using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Descriptors.Specialized; using Jint.Runtime.Environments; using Jint.Runtime.Interop; namespace Jint.Native; /// /// https://tc39.es/ecma262/#sec-arguments-exotic-objects /// public sealed class JsArguments : ObjectInstance { // cache property container for array iteration for less allocations private static readonly ThreadLocal> _mappedNamed = new(() => []); private Function.Function _func = null!; private Key[] _names = null!; private JsValue[] _args = null!; private DeclarativeEnvironment _env = null!; private bool _canReturnToPool; private bool _hasRestParameter; private bool _materialized; internal JsArguments(Engine engine) : base(engine, ObjectClass.Arguments) { } internal void Prepare( Function.Function func, Key[] names, JsValue[] args, DeclarativeEnvironment env, bool hasRestParameter) { _func = func; _names = names; _args = args; _env = env; _hasRestParameter = hasRestParameter; _canReturnToPool = true; ClearProperties(); } protected override void Initialize() { _canReturnToPool = false; var args = _args; DefinePropertyOrThrow(CommonProperties.Length, new PropertyDescriptor(_args.Length, PropertyFlag.NonEnumerable)); if (_func is null) { // unmapped ParameterMap = null; for (uint i = 0; i < (uint) args.Length; i++) { var val = args[i]; CreateDataProperty(JsString.Create(i), val); } DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.None)); } else { ObjectInstance? map = null; if (args.Length > 0) { var mappedNamed = _mappedNamed.Value!; mappedNamed.Clear(); map = Engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); for (uint i = 0; i < (uint) args.Length; i++) { SetOwnProperty(JsString.Create(i), new PropertyDescriptor(args[i], PropertyFlag.ConfigurableEnumerableWritable)); if (i < _names.Length) { var name = _names[i]; if (mappedNamed.Add(name)) { map.SetOwnProperty(JsString.Create(i), new ClrAccessDescriptor(_env, Engine, name)); } } } } ParameterMap = map; // step 13 DefinePropertyOrThrow(CommonProperties.Callee, new PropertyDescriptor(_func, PropertyFlag.NonEnumerable)); } var iteratorFunction = new ClrFunction(Engine, "iterator", _engine.Realm.Intrinsics.Array.PrototypeObject.Values, 0, PropertyFlag.Configurable); DefinePropertyOrThrow(GlobalSymbolRegistry.Iterator, new PropertyDescriptor(iteratorFunction, PropertyFlag.Writable | PropertyFlag.Configurable)); } internal ObjectInstance? ParameterMap { get; set; } internal override bool IsArrayLike => true; internal override bool IsIntegerIndexedArray => true; public uint Length => (uint) _args.Length; public override PropertyDescriptor GetOwnProperty(JsValue property) { EnsureInitialized(); if (ParameterMap is not null) { var desc = base.GetOwnProperty(property); if (desc == PropertyDescriptor.Undefined) { return desc; } if (ParameterMap.TryGetValue(property, out var jsValue) && !jsValue.IsUndefined()) { desc.Value = jsValue; } return desc; } return base.GetOwnProperty(property); } /// Implementation from ObjectInstance official specs as the one /// in ObjectInstance is optimized for the general case and wouldn't work /// for arrays public override bool Set(JsValue property, JsValue value, JsValue receiver) { EnsureInitialized(); if (!CanPut(property)) { return false; } var ownDesc = GetOwnProperty(property); if (ownDesc.IsDataDescriptor()) { var valueDesc = new PropertyDescriptor(value, PropertyFlag.None); return DefineOwnProperty(property, valueDesc); } // property is an accessor or inherited var desc = GetOwnProperty(property); if (desc.IsAccessorDescriptor()) { if (desc.Set is not ICallable setter) { return false; } setter.Call(receiver, value); } else { var newDesc = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); return DefineOwnProperty(property, newDesc); } return true; } public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc) { if (_hasRestParameter) { // immutable return true; } EnsureInitialized(); if (ParameterMap is not null) { var map = ParameterMap; var isMapped = map.GetOwnProperty(property); var allowed = base.DefineOwnProperty(property, desc); if (!allowed) { return false; } if (isMapped != PropertyDescriptor.Undefined) { if (desc.IsAccessorDescriptor()) { map.Delete(property); } else { var descValue = desc.Value; if (descValue is not null && !descValue.IsUndefined()) { map.Set(property, descValue, false); } if (desc.WritableSet && !desc.Writable) { map.Delete(property); } } } return true; } return base.DefineOwnProperty(property, desc); } public override bool Delete(JsValue property) { EnsureInitialized(); if (ParameterMap is not null) { var map = ParameterMap; var isMapped = map.GetOwnProperty(property); var result = base.Delete(property); if (result && isMapped != PropertyDescriptor.Undefined) { map.Delete(property); } return result; } return base.Delete(property); } internal void Materialize() { if (_materialized) { // already done return; } _materialized = true; EnsureInitialized(); var args = _args; var copiedArgs = new JsValue[args.Length]; System.Array.Copy(args, copiedArgs, args.Length); _args = copiedArgs; _canReturnToPool = false; } internal void FunctionWasCalled() { // should no longer expose arguments which is special name ParameterMap = null; if (_canReturnToPool) { _engine._argumentsInstancePool.Return(this); // prevent double-return _canReturnToPool = false; } } }