using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter; namespace Jint.Native.Function { public abstract class FunctionInstance : ObjectInstance, ICallable { protected PropertyDescriptor _prototypeDescriptor; protected internal PropertyDescriptor _length; private PropertyDescriptor _nameDescriptor; protected internal LexicalEnvironment _environment; internal readonly JintFunctionDefinition _functionDefinition; internal readonly FunctionThisMode _thisMode; internal JsValue _homeObject = Undefined; internal ConstructorKind _constructorKind = ConstructorKind.Base; internal FunctionInstance( Engine engine, JintFunctionDefinition function, LexicalEnvironment scope, FunctionThisMode thisMode) : this(engine, !string.IsNullOrWhiteSpace(function.Name) ? new JsString(function.Name) : null, thisMode) { _functionDefinition = function; _environment = scope; } internal FunctionInstance( Engine engine, JsString name, FunctionThisMode thisMode = FunctionThisMode.Global, ObjectClass objectClass = ObjectClass.Function) : base(engine, objectClass) { if (name is not null) { _nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable); } _thisMode = thisMode; } protected FunctionInstance( Engine engine, JsString name) : this(engine, name, FunctionThisMode.Global, ObjectClass.Function) { } // for example RavenDB wants to inspect this public IFunction FunctionDeclaration => _functionDefinition.Function; /// /// Executed when a function object is used as a function /// /// /// /// public abstract JsValue Call(JsValue thisObject, JsValue[] 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; } } public override List GetOwnPropertyKeys(Types types) { var keys = new List(); if (_prototypeDescriptor != null) { keys.Add(CommonProperties.Prototype); } if (_length != null) { keys.Add(CommonProperties.Length); } if (_nameDescriptor != null) { keys.Add(CommonProperties.Name); } keys.AddRange(base.GetOwnPropertyKeys(types)); return keys; } public override PropertyDescriptor GetOwnProperty(JsValue property) { if (property == CommonProperties.Prototype) { return _prototypeDescriptor ?? PropertyDescriptor.Undefined; } if (property == CommonProperties.Length) { return _length ?? PropertyDescriptor.Undefined; } if (property == CommonProperties.Name) { return _nameDescriptor ?? PropertyDescriptor.Undefined; } return base.GetOwnProperty(property); } protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { if (property == CommonProperties.Prototype) { _prototypeDescriptor = desc; } else if (property == CommonProperties.Length) { _length = desc; } else if (property == CommonProperties.Name) { _nameDescriptor = desc; } else { base.SetOwnProperty(property, desc); } } public override bool HasOwnProperty(JsValue property) { if (property == CommonProperties.Prototype) { return _prototypeDescriptor != null; } if (property == CommonProperties.Length) { return _length != null; } if (property == CommonProperties.Name) { return _nameDescriptor != null; } return base.HasOwnProperty(property); } public override void RemoveOwnProperty(JsValue property) { if (property == CommonProperties.Prototype) { _prototypeDescriptor = null; } if (property == CommonProperties.Length) { _length = null; } if (property == CommonProperties.Name) { _nameDescriptor = null; } base.RemoveOwnProperty(property); } internal void SetFunctionName(JsValue name, string prefix = null, bool force = false) { if (!force && _nameDescriptor != null && !UnwrapJsValue(_nameDescriptor).IsUndefined()) { return; } if (name is JsSymbol symbol) { name = symbol._value.IsUndefined() ? JsString.Empty : new JsString("[" + symbol._value + "]"); } 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal T OrdinaryCreateFromConstructor( JsValue constructor, ObjectInstance intrinsicDefaultProto, Func objectCreator, JsValue state = null) where T : ObjectInstance { var proto = GetPrototypeFromConstructor(constructor, intrinsicDefaultProto); var obj = objectCreator(_engine, state); obj._prototype = proto; return obj; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ObjectInstance GetPrototypeFromConstructor(JsValue constructor, ObjectInstance intrinsicDefaultProto) { var proto = constructor.Get(CommonProperties.Prototype, constructor) as ObjectInstance; // If Type(proto) is not Object, then // Let realm be ? GetFunctionRealm(constructor). // Set proto to realm's intrinsic object named intrinsicDefaultProto. return proto ?? intrinsicDefaultProto; } internal void MakeMethod(ObjectInstance homeObject) { _homeObject = homeObject; } /// /// https://tc39.es/ecma262/#sec-ordinarycallbindthis /// protected void OrdinaryCallBindThis(ExecutionContext calleeContext, JsValue thisArgument) { var thisMode = _thisMode; if (thisMode == FunctionThisMode.Lexical) { return; } // Let calleeRealm be F.[[Realm]]. var localEnv = (FunctionEnvironmentRecord) calleeContext.LexicalEnvironment._record; JsValue thisValue; if (_thisMode == FunctionThisMode.Strict) { thisValue = thisArgument; } else { if (thisArgument.IsNullOrUndefined()) { // Let globalEnv be calleeRealm.[[GlobalEnv]]. var globalEnv = _engine.GlobalEnvironment; var globalEnvRec = (GlobalEnvironmentRecord) globalEnv._record; thisValue = globalEnvRec.GlobalThisValue; } else { thisValue = TypeConverter.ToObject(_engine, thisArgument); } } localEnv.BindThisValue(thisValue); } protected Completion OrdinaryCallEvaluateBody( JsValue[] arguments, ExecutionContext calleeContext) { var argumentsInstance = _engine.FunctionDeclarationInstantiation( functionInstance: this, arguments, calleeContext.LexicalEnvironment); var result = _functionDefinition.Execute(); var value = result.GetValueOrDefault().Clone(); argumentsInstance?.FunctionWasCalled(); return new Completion(result.Type, value, result.Identifier, result.Location); } /// /// https://tc39.es/ecma262/#sec-prepareforordinarycall /// protected ExecutionContext PrepareForOrdinaryCall(JsValue newTarget) { // ** PrepareForOrdinaryCall ** // var callerContext = _engine.ExecutionContext; // Let calleeRealm be F.[[Realm]]. // Set the Realm of calleeContext to calleeRealm. // Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]]. var calleeContext = LexicalEnvironment.NewFunctionEnvironment(_engine, this, newTarget); // 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. return _engine.EnterExecutionContext(calleeContext, calleeContext); } public override string ToString() { // TODO no way to extract SourceText from Esprima at the moment, just returning native code var nameValue = _nameDescriptor != null ? UnwrapJsValue(_nameDescriptor) : JsString.Empty; var name = ""; if (!nameValue.IsUndefined()) { name = TypeConverter.ToString(nameValue); } return "function " + name + "() { [native code] }"; } } }