using System.Diagnostics; using System.Runtime.CompilerServices; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter; using Environment = Jint.Runtime.Environments.Environment; namespace Jint.Native.Function; [DebuggerDisplay("{ToString(),nq}")] #pragma warning disable MA0049 public abstract partial class Function : ObjectInstance, ICallable #pragma warning restore MA0049 { protected PropertyDescriptor? _prototypeDescriptor; protected internal PropertyDescriptor? _length; internal PropertyDescriptor? _nameDescriptor; internal Environment? _environment; internal readonly JintFunctionDefinition? _functionDefinition; internal readonly FunctionThisMode _thisMode; internal JsValue _homeObject = Undefined; internal ConstructorKind _constructorKind = ConstructorKind.Base; internal Realm _realm; internal PrivateEnvironment? _privateEnvironment; private readonly IScriptOrModule? _scriptOrModule; protected Function( Engine engine, Realm realm, JsString? name) : this(engine, realm, name, FunctionThisMode.Global) { } internal Function( Engine engine, Realm realm, JintFunctionDefinition function, Environment env, FunctionThisMode thisMode) : this( engine, realm, !string.IsNullOrWhiteSpace(function.Name) ? new JsString(function.Name!) : null, thisMode) { _functionDefinition = function; _environment = env; } internal Function( Engine engine, Realm realm, JsString? name, FunctionThisMode thisMode = FunctionThisMode.Global) : base(engine, ObjectClass.Function) { if (name is not null) { _nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable); } _realm = realm; _thisMode = thisMode; _scriptOrModule = _engine.GetActiveScriptOrModule(); } // for example RavenDB wants to inspect this public IFunction? FunctionDeclaration => _functionDefinition?.Function; internal override bool IsCallable => true; JsValue ICallable.Call(JsValue thisObject, params JsCallArguments arguments) => Call(thisObject, arguments); /// /// Executed when a function object is used as a function /// protected internal abstract JsValue Call(JsValue thisObject, JsCallArguments arguments); public bool Strict => _thisMode == FunctionThisMode.Strict; internal override bool IsConstructor => this is IConstructor; public override IEnumerable> GetOwnProperties() { if (_prototypeDescriptor != null) { yield return new KeyValuePair(CommonProperties.Prototype, _prototypeDescriptor); } if (_length != null) { yield return new KeyValuePair(CommonProperties.Length, _length); } if (_nameDescriptor != null) { yield return new KeyValuePair(CommonProperties.Name, GetOwnProperty(CommonProperties.Name)); } foreach (var entry in base.GetOwnProperties()) { yield return entry; } } internal sealed override IEnumerable GetInitialOwnStringPropertyKeys() { if (_length != null) { yield return CommonProperties.Length; } if (_nameDescriptor != null) { yield return CommonProperties.Name; } if (_prototypeDescriptor != null) { yield return CommonProperties.Prototype; } } public override PropertyDescriptor GetOwnProperty(JsValue property) { if (CommonProperties.Prototype.Equals(property)) { return _prototypeDescriptor ?? PropertyDescriptor.Undefined; } if (CommonProperties.Length.Equals(property)) { return _length ?? PropertyDescriptor.Undefined; } if (CommonProperties.Name.Equals(property)) { return _nameDescriptor ?? PropertyDescriptor.Undefined; } return base.GetOwnProperty(property); } protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { if (CommonProperties.Prototype.Equals(property)) { _prototypeDescriptor = desc; } else if (CommonProperties.Length.Equals(property)) { _length = desc; } else if (CommonProperties.Name.Equals(property)) { _nameDescriptor = desc; } else { base.SetOwnProperty(property, desc); } } public override void RemoveOwnProperty(JsValue property) { if (CommonProperties.Prototype.Equals(property)) { _prototypeDescriptor = null; } if (CommonProperties.Length.Equals(property)) { _length = null; } if (CommonProperties.Name.Equals(property)) { _nameDescriptor = null; } base.RemoveOwnProperty(property); } /// /// https://tc39.es/ecma262/#sec-setfunctionname /// internal void SetFunctionName(JsValue name, string? prefix = null, bool force = false) { if (!force && _nameDescriptor != null && UnwrapJsValue(_nameDescriptor) != JsString.Empty) { return; } if (name is JsSymbol symbol) { name = symbol._value.IsUndefined() ? JsString.Empty : new JsString("[" + symbol._value + "]"); } else if (name is PrivateName privateName) { name = "#" + privateName.Description; } if (!string.IsNullOrWhiteSpace(prefix)) { name = prefix + " " + name; } _nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable); } /// /// https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor /// /// /// Uses separate builder to get correct type with state support to prevent allocations. /// In spec intrinsicDefaultProto is string pointing to intrinsic, but we do a selector. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal T OrdinaryCreateFromConstructor( JsValue constructor, Func intrinsicDefaultProto, Func objectCreator, TState? state = default) where T : ObjectInstance { var proto = GetPrototypeFromConstructor(constructor, intrinsicDefaultProto); var obj = objectCreator(_engine, _realm, state); obj._prototype = proto; return obj; } /// /// https://tc39.es/ecma262/#sec-getprototypefromconstructor /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ObjectInstance GetPrototypeFromConstructor(JsValue constructor, Func intrinsicDefaultProto) { if (constructor.Get(CommonProperties.Prototype) is not ObjectInstance proto) { var realm = GetFunctionRealm(constructor); proto = intrinsicDefaultProto(realm.Intrinsics); } return proto; } /// /// https://tc39.es/ecma262/#sec-getfunctionrealm /// internal Realm GetFunctionRealm(JsValue obj) { if (obj is Function functionInstance && functionInstance._realm is not null) { return functionInstance._realm; } if (obj is BindFunction bindFunctionInstance) { return GetFunctionRealm(bindFunctionInstance.BoundTargetFunction); } if (obj is JsProxy proxyInstance) { if (proxyInstance._handler is null) { Throw.TypeErrorNoEngine(); } return GetFunctionRealm(proxyInstance._target); } return _engine.ExecutionContext.Realm; } /// /// https://tc39.es/ecma262/#sec-makemethod /// internal void MakeMethod(ObjectInstance homeObject) { _homeObject = homeObject; } /// /// https://tc39.es/ecma262/#sec-ordinarycallbindthis /// internal void OrdinaryCallBindThis(in ExecutionContext calleeContext, JsValue thisArgument) { if (_thisMode == FunctionThisMode.Lexical) { return; } var calleeRealm = _realm; var localEnv = (FunctionEnvironment) calleeContext.LexicalEnvironment; JsValue thisValue; if (_thisMode == FunctionThisMode.Strict) { thisValue = thisArgument; } else { if (thisArgument is null || thisArgument.IsNullOrUndefined()) { var globalEnv = calleeRealm.GlobalEnv; thisValue = globalEnv.GlobalThisValue; } else { thisValue = TypeConverter.ToObject(calleeRealm, thisArgument); } } localEnv.BindThisValue(thisValue); } /// /// https://tc39.es/ecma262/#sec-prepareforordinarycall /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref readonly ExecutionContext PrepareForOrdinaryCall(JsValue newTarget) { var callerContext = _engine.ExecutionContext; var localEnv = JintEnvironment.NewFunctionEnvironment(_engine, this, newTarget); var calleeRealm = _realm; var calleeContext = new ExecutionContext( _scriptOrModule, lexicalEnvironment: localEnv, variableEnvironment: localEnv, _privateEnvironment, calleeRealm, generator: null, function: this); // If callerContext is not already suspended, suspend callerContext. // Push calleeContext onto the execution context stack; calleeContext is now the running execution context. // NOTE: Any exception objects produced after this point are associated with calleeRealm. // Return calleeContext. _engine.EnterExecutionContext(calleeContext); return ref _engine.ExecutionContext; } internal void MakeConstructor(bool writableProperty = true, ObjectInstance? prototype = null) { _constructorKind = ConstructorKind.Base; if (prototype is null) { prototype = new ObjectInstanceWithConstructor(_engine, this) { _prototype = _realm.Intrinsics.Object.PrototypeObject }; } _prototypeDescriptor = new PropertyDescriptor(prototype, writableProperty, enumerable: false, configurable: false); } internal void SetFunctionLength(JsNumber length) { DefinePropertyOrThrow(CommonProperties.Length, new PropertyDescriptor(length, writable: false, enumerable: false, configurable: true)); } // native syntax doesn't expect to have private identifier indicator private static readonly char[] _functionNameTrimStartChars = ['#']; public sealed override object ToObject() { return (JsCallDelegate) Call; } public override string ToString() { if (_functionDefinition?.Function is Node node && _engine.Options.Host.FunctionToStringHandler(this, node) is { } s) { return s; } var nameValue = _nameDescriptor != null ? UnwrapJsValue(_nameDescriptor) : JsString.Empty; var name = ""; if (!nameValue.IsUndefined()) { name = TypeConverter.ToString(nameValue); } name = name.TrimStart(_functionNameTrimStartChars); return $"function {name}() {{ [native code] }}"; } private sealed class ObjectInstanceWithConstructor : ObjectInstance { private PropertyDescriptor? _constructor; public ObjectInstanceWithConstructor(Engine engine, ObjectInstance thisObj) : base(engine) { _constructor = new PropertyDescriptor(thisObj, PropertyFlag.NonEnumerable); } public override IEnumerable> GetOwnProperties() { if (_constructor != null) { yield return new KeyValuePair(CommonProperties.Constructor, _constructor); } foreach (var entry in base.GetOwnProperties()) { yield return entry; } } public override PropertyDescriptor GetOwnProperty(JsValue property) { if (CommonProperties.Constructor.Equals(property)) { return _constructor ?? PropertyDescriptor.Undefined; } return base.GetOwnProperty(property); } protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { if (CommonProperties.Constructor.Equals(property)) { _constructor = desc; } else { base.SetOwnProperty(property, desc); } } public override void RemoveOwnProperty(JsValue property) { if (CommonProperties.Constructor.Equals(property)) { _constructor = null; } else { base.RemoveOwnProperty(property); } } } }