using System.Collections.Generic; using Esprima.Ast; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Descriptors.Specialized; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter; namespace Jint.Native.Function { public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor { internal readonly JintFunctionDefinition _function; /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2 /// public ScriptFunctionInstance( Engine engine, IFunction functionDeclaration, LexicalEnvironment scope, bool strict) : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict ? FunctionThisMode.Strict : FunctionThisMode.Global) { } internal ScriptFunctionInstance( Engine engine, JintFunctionDefinition function, LexicalEnvironment scope, FunctionThisMode thisMode) : base(engine, function, scope, thisMode) { _function = function; _prototype = _engine.Function.PrototypeObject; _length = new LazyPropertyDescriptor(() => JsNumber.Create(function.Initialize(engine, this).Length), PropertyFlag.Configurable); var proto = new ObjectInstanceWithConstructor(engine, this) { _prototype = _engine.Object.PrototypeObject }; _prototypeDescriptor = new PropertyDescriptor(proto, PropertyFlag.OnlyWritable); if (thisMode == FunctionThisMode.Strict) { DefineOwnProperty(CommonProperties.Caller, engine._getSetThrower); DefineOwnProperty(CommonProperties.Arguments, engine._getSetThrower); } } // for example RavenDB wants to inspect this public IFunction FunctionDeclaration => _function.Function; /// /// https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist /// public override JsValue Call(JsValue thisArgument, JsValue[] arguments) { // ** 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 localEnv = LexicalEnvironment.NewFunctionEnvironment(_engine, this, Undefined); // 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(localEnv, localEnv); // ** OrdinaryCallBindThis ** JsValue thisValue; if (_thisMode == FunctionThisMode.Strict) { thisValue = thisArgument; } else { if (thisArgument.IsNullOrUndefined()) { var globalEnv = _engine.GlobalEnvironment; var globalEnvRec = (GlobalEnvironmentRecord) globalEnv._record; thisValue = globalEnvRec.GlobalThisValue; } else { thisValue = TypeConverter.ToObject(_engine, thisArgument); } } var envRec = (FunctionEnvironmentRecord) localEnv._record; envRec.BindThisValue(thisValue); // actual call var strict = _thisMode == FunctionThisMode.Strict || _engine._isStrict; using (new StrictModeScope(strict, true)) { try { var argumentsInstance = _engine.FunctionDeclarationInstantiation( functionInstance: this, arguments, localEnv); var result = _function.Body.Execute(); var value = result.GetValueOrDefault().Clone(); argumentsInstance?.FunctionWasCalled(); if (result.Type == CompletionType.Throw) { ExceptionHelper.ThrowJavaScriptException(_engine, value, result); } if (result.Type == CompletionType.Return) { return value; } } finally { _engine.LeaveExecutionContext(); } return Undefined; } } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2 /// public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { var thisArgument = OrdinaryCreateFromConstructor(TypeConverter.ToObject(_engine, newTarget), _engine.Object.PrototypeObject); var result = Call(thisArgument, arguments).TryCast(); if (!ReferenceEquals(result, null)) { return result; } return thisArgument; } private 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 (property == CommonProperties.Constructor) { return _constructor ?? PropertyDescriptor.Undefined; } return base.GetOwnProperty(property); } protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc) { if (property == CommonProperties.Constructor) { _constructor = desc; } else { base.SetOwnProperty(property, desc); } } public override bool HasOwnProperty(JsValue property) { if (property == CommonProperties.Constructor) { return _constructor != null; } return base.HasOwnProperty(property); } public override void RemoveOwnProperty(JsValue property) { if (property == CommonProperties.Constructor) { _constructor = null; } else { base.RemoveOwnProperty(property); } } } } }