using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Esprima;
using Esprima.Ast;
using Jint.Native;
using Jint.Native.Function;
using Jint.Native.Generator;
using Jint.Native.Object;
using Jint.Native.Promise;
using Jint.Native.Symbol;
using Jint.Pooling;
using Jint.Runtime;
using Jint.Runtime.CallStack;
using Jint.Runtime.Debugger;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Environments;
using Jint.Runtime.Interop;
using Jint.Runtime.Interop.Reflection;
using Jint.Runtime.Interpreter;
using Jint.Runtime.Interpreter.Expressions;
using Environment = Jint.Runtime.Environments.Environment;
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();
private readonly ParserOptions _defaultParserOptions;
private readonly JavaScriptParser _defaultParser;
private readonly ExecutionContextStack _executionContexts;
private JsValue _completionValue = JsValue.Undefined;
internal EvaluationContext? _activeEvaluationContext;
internal ErrorDispatchInfo? _error;
private readonly EventLoop _eventLoop = new();
private readonly Agent _agent = new();
// lazy properties
private DebugHandler? _debugger;
// cached access
internal readonly IObjectConverter[]? _objectConverters;
internal readonly Constraint[] _constraints;
internal readonly bool _isDebugMode;
internal readonly bool _isStrict;
internal readonly IReferenceResolver _referenceResolver;
internal readonly ReferencePool _referencePool;
internal readonly ArgumentsInstancePool _argumentsInstancePool;
internal readonly JsValueArrayPool _jsValueArrayPool;
internal readonly ExtensionMethodCache _extensionMethods;
public ITypeConverter TypeConverter { get; internal set; }
// cache of types used when resolving CLR type names
internal readonly Dictionary TypeCache = new(StringComparer.Ordinal);
// 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;
internal readonly JintCallStack CallStack;
internal readonly StackGuard _stackGuard;
// needed in initial engine setup, for example CLR function construction
internal Intrinsics _originalIntrinsics = null!;
internal Host _host = null!;
// we need to cache reflection accessors on engine level as configuration options can affect outcome
internal readonly record struct ClrPropertyDescriptorFactoriesKey(Type Type, Key PropertyName);
internal Dictionary _reflectionAccessors = new();
///
/// Constructs a new engine instance.
///
public Engine() : this(null, null)
{
}
///
/// Constructs a new engine instance and allows customizing options.
///
public Engine(Action? options)
: this(null, options != null ? (_, opts) => options.Invoke(opts) : null)
{
}
///
/// Constructs a new engine with a custom instance.
///
public Engine(Options options) : this(options, null)
{
}
///
/// 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) : this(null, options)
{
}
private Engine(Options? options, Action? configure)
{
Advanced = new AdvancedOperations(this);
Constraints = new ConstraintOperations(this);
TypeConverter = new DefaultTypeConverter(this);
_executionContexts = new ExecutionContextStack(2);
// 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);
Reset();
// gather some options as fields for faster checks
_isDebugMode = Options.Debugger.Enabled;
_isStrict = Options.Strict;
_objectConverters = Options.Interop.ObjectConverters.Count > 0
? Options.Interop.ObjectConverters.ToArray()
: null;
_constraints = Options.Constraints.Constraints.ToArray();
_referenceResolver = Options.ReferenceResolver;
_referencePool = new ReferencePool();
_argumentsInstancePool = new ArgumentsInstancePool(this);
_jsValueArrayPool = new JsValueArrayPool();
Options.Apply(this);
CallStack = new JintCallStack(Options.Constraints.MaxRecursionDepth >= 0);
_stackGuard = new StackGuard(this);
_defaultParserOptions = ParserOptions.Default with
{
AllowReturnOutsideFunction = true,
RegexTimeout = Options.Constraints.RegexTimeout
};
_defaultParser = new JavaScriptParser(_defaultParserOptions);
}
private void Reset()
{
_host = Options.Host.Factory(this);
_host.Initialize(this);
}
internal ref readonly ExecutionContext ExecutionContext
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _executionContexts.Peek();
}
// temporary state for realm so that we can easily pass it to functions while still not
// having a proper execution context established
internal Realm? _realmInConstruction;
internal SyntaxElement? _lastSyntaxElement;
internal Realm Realm => _realmInConstruction ?? ExecutionContext.Realm;
///
/// The well-known intrinsics for this engine instance.
///
public Intrinsics Intrinsics => Realm.Intrinsics;
///
/// The global object for this engine instance.
///
public ObjectInstance Global => Realm.GlobalObject;
internal GlobalSymbolRegistry GlobalSymbolRegistry { get; } = new();
internal long CurrentMemoryUsage { get; private set; }
internal Options Options
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
private set;
}
public DebugHandler Debugger => _debugger ??= new DebugHandler(this, Options.Debugger.InitialStepMode);
internal ExecutionContext EnterExecutionContext(
Environment lexicalEnvironment,
Environment variableEnvironment,
Realm realm,
PrivateEnvironment? privateEnvironment)
{
var context = new ExecutionContext(
null,
lexicalEnvironment,
variableEnvironment,
privateEnvironment,
realm,
null);
_executionContexts.Push(context);
return context;
}
internal ExecutionContext EnterExecutionContext(in ExecutionContext context)
{
_executionContexts.Push(context);
return context;
}
///
/// Registers a delegate with given name. Delegate becomes a JavaScript function that can be called.
///
public Engine SetValue(string name, Delegate value)
{
Realm.GlobalObject.FastSetProperty(name, new PropertyDescriptor(new DelegateWrapper(this, value), PropertyFlag.NonEnumerable));
return this;
}
///
/// Registers a string value as variable.
///
public Engine SetValue(string name, string? value)
{
return SetValue(name, value is null ? JsValue.Null : JsString.Create(value));
}
///
/// Registers a double value as variable.
///
public Engine SetValue(string name, double value)
{
return SetValue(name, (JsValue) JsNumber.Create(value));
}
///
/// Registers an integer value as variable.
///
public Engine SetValue(string name, int value)
{
return SetValue(name, (JsValue) JsNumber.Create(value));
}
///
/// Registers a boolean value as variable.
///
public Engine SetValue(string name, bool value)
{
return SetValue(name, (JsValue) (value ? JsBoolean.True : JsBoolean.False));
}
///
/// Registers a native JS value as variable.
///
public Engine SetValue(string name, JsValue value)
{
Realm.GlobalObject.Set(name, value);
return this;
}
///
/// Registers an object value as variable, creates an interop wrapper when needed.
///
public Engine SetValue(string name, object? obj)
{
var value = obj is Type t
? TypeReference.CreateTypeReference(this, t)
: JsValue.FromObject(this, obj);
return SetValue(name, value);
}
///
/// Registers an object value as variable, creates an interop wrapper when needed.
///
public Engine SetValue(string name, [DynamicallyAccessedMembers(InteropHelper.DefaultDynamicallyAccessedMemberTypes)] Type type)
{
#pragma warning disable IL2111
return SetValue(name, TypeReference.CreateTypeReference(this, type));
#pragma warning restore IL2111
}
///
/// Registers an object value as variable, creates an interop wrapper when needed.
///
public Engine SetValue<[DynamicallyAccessedMembers(InteropHelper.DefaultDynamicallyAccessedMemberTypes)] T>(string name, T? obj)
{
return obj is Type t
? SetValue(name, t)
: SetValue(name, JsValue.FromObject(this, obj));
}
internal void LeaveExecutionContext()
{
_executionContexts.Pop();
}
internal void ResetConstraints()
{
foreach (var constraint in _constraints)
{
constraint.Reset();
}
}
///
/// Initializes list of references of called functions
///
internal void ResetCallStack()
{
CallStack.Clear();
}
///
/// Evaluates code and returns last return value.
///
public JsValue Evaluate(string code)
=> Evaluate(code, "", _defaultParserOptions);
///
/// Evaluates code and returns last return value.
///
public JsValue Evaluate(string code, string source)
=> Evaluate(code, source, _defaultParserOptions);
///
/// Evaluates code and returns last return value.
///
public JsValue Evaluate(string code, ParserOptions parserOptions)
=> Evaluate(code, "", parserOptions);
///
/// Evaluates code and returns last return value.
///
public JsValue Evaluate(string code, string source, ParserOptions parserOptions)
{
var parser = ReferenceEquals(_defaultParserOptions, parserOptions)
? _defaultParser
: new JavaScriptParser(parserOptions);
var script = parser.ParseScript(code, source, _isStrict);
return Evaluate(script);
}
///
/// Evaluates code and returns last return value.
///
public JsValue Evaluate(Script script)
=> Execute(script)._completionValue;
///
/// Executes code into engine and returns the engine instance (useful for chaining).
///
public Engine Execute(string code, string? source = null)
=> Execute(code, source ?? "", _defaultParserOptions);
///
/// Executes code into engine and returns the engine instance (useful for chaining).
///
public Engine Execute(string code, ParserOptions parserOptions)
=> Execute(code, "", parserOptions);
///
/// Executes code into engine and returns the engine instance (useful for chaining).
///
public Engine Execute(string code, string source, ParserOptions parserOptions)
{
var parser = ReferenceEquals(_defaultParserOptions, parserOptions)
? _defaultParser
: new JavaScriptParser(parserOptions);
var script = parser.ParseScript(code, source, _isStrict);
return Execute(script);
}
///
/// Executes code into engine and returns the engine instance (useful for chaining).
///
public Engine Execute(Script script)
{
var strict = _isStrict || script.Strict;
ExecuteWithConstraints(strict, () => ScriptEvaluation(new ScriptRecord(Realm, script, script.Location.Source)));
return this;
}
///
/// https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation
///
private Engine ScriptEvaluation(ScriptRecord scriptRecord)
{
Debugger.OnBeforeEvaluate(scriptRecord.EcmaScriptCode);
var globalEnv = Realm.GlobalEnv;
var scriptContext = new ExecutionContext(
scriptRecord,
lexicalEnvironment: globalEnv,
variableEnvironment: globalEnv,
privateEnvironment: null,
Realm);
EnterExecutionContext(scriptContext);
try
{
var script = scriptRecord.EcmaScriptCode;
GlobalDeclarationInstantiation(script, globalEnv);
var list = new JintStatementList(null, script.Body);
Completion result;
try
{
result = list.Execute(_activeEvaluationContext!);
}
catch
{
// unhandled exception
ResetCallStack();
throw;
}
if (result.Type == CompletionType.Throw)
{
var ex = new JavaScriptException(result.GetValueOrDefault()).SetJavaScriptCallstack(this, result.Location);
ResetCallStack();
throw ex;
}
_completionValue = result.GetValueOrDefault();
// TODO what about callstack and thrown exceptions?
RunAvailableContinuations();
return this;
}
finally
{
LeaveExecutionContext();
}
}
///
/// EXPERIMENTAL! Subject to change.
///
/// Registers a promise within the currently running EventLoop (has to be called within "ExecuteWithEventLoop" call).
/// Note that ExecuteWithEventLoop will not trigger "onFinished" callback until ALL manual promises are settled.
///
/// NOTE: that resolve and reject need to be called withing the same thread as "ExecuteWithEventLoop".
/// The API assumes that the Engine is called from a single thread.
///
/// a Promise instance and functions to either resolve or reject it
internal ManualPromise RegisterPromise()
{
var promise = new JsPromise(this)
{
_prototype = Realm.Intrinsics.Promise.PrototypeObject
};
var (resolve, reject) = promise.CreateResolvingFunctions();
Action SettleWith(Function settle) => value =>
{
settle.Call(JsValue.Undefined, new[] { value });
RunAvailableContinuations();
};
return new ManualPromise(promise, SettleWith(resolve), SettleWith(reject));
}
internal void AddToEventLoop(Action continuation)
{
_eventLoop.Events.Enqueue(continuation);
}
internal void AddToKeptObjects(JsValue target)
{
_agent.AddToKeptObjects(target);
}
internal void RunAvailableContinuations()
{
var queue = _eventLoop.Events;
if (queue.Count == 0)
{
return;
}
DoProcessEventLoop(queue);
}
private static void DoProcessEventLoop(Queue queue)
{
while (true)
{
if (queue.Count == 0)
{
return;
}
var nextContinuation = queue.Dequeue();
// note that continuation can enqueue new events
nextContinuation();
}
}
internal void RunBeforeExecuteStatementChecks(StatementListItem? statement)
{
// Avoid allocating the enumerator because we run this loop very often.
foreach (var constraint in _constraints)
{
constraint.Check();
}
if (_isDebugMode && statement != null && statement.Type != Nodes.BlockStatement)
{
Debugger.OnStep(statement);
}
}
internal JsValue GetValue(object value)
{
return GetValue(value, false);
}
internal JsValue GetValue(object value, bool returnReferenceToPool)
{
if (value is JsValue jsValue)
{
return jsValue;
}
if (value is not Reference reference)
{
return ((Completion) value).Value;
}
return GetValue(reference, returnReferenceToPool);
}
internal JsValue GetValue(Reference reference, bool returnReferenceToPool)
{
var baseValue = reference.Base;
if (baseValue.IsUndefined())
{
if (_referenceResolver.TryUnresolvableReference(this, reference, out var val))
{
return val;
}
ExceptionHelper.ThrowReferenceError(Realm, reference);
}
if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == InternalTypes.Empty
&& _referenceResolver.TryPropertyReference(this, reference, ref baseValue))
{
return baseValue;
}
if (reference.IsPropertyReference)
{
var property = reference.ReferencedName;
if (returnReferenceToPool)
{
_referencePool.Return(reference);
}
if (baseValue.IsObject())
{
var baseObj = Runtime.TypeConverter.ToObject(Realm, baseValue);
if (reference.IsPrivateReference)
{
return baseObj.PrivateGet((PrivateName) reference.ReferencedName);
}
var v = baseObj.Get(property, reference.ThisValue);
return v;
}
else
{
// 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)) != InternalTypes.Empty
&& baseValue is JsString s
&& TryHandleStringValue(property, s, ref o, out var jsValue))
{
return jsValue;
}
if (o is null)
{
o = Runtime.TypeConverter.ToObject(Realm, baseValue);
}
if (reference.IsPrivateReference)
{
return o.PrivateGet((PrivateName) reference.ReferencedName);
}
var desc = o.GetProperty(property);
if (desc == PropertyDescriptor.Undefined)
{
return JsValue.Undefined;
}
if (desc.IsDataDescriptor())
{
return desc.Value;
}
var getter = desc.Get!;
if (getter.IsUndefined())
{
return JsValue.Undefined;
}
var callable = (ICallable) getter;
return callable.Call(baseValue, Arguments.Empty);
}
}
var record = baseValue as Environment;
if (record is null)
{
ExceptionHelper.ThrowArgumentException();
}
var bindingValue = record.GetBindingValue(reference.ReferencedName.ToString(), reference.Strict);
if (returnReferenceToPool)
{
_referencePool.Return(reference);
}
return bindingValue;
}
private bool TryHandleStringValue(JsValue property, JsString s, ref ObjectInstance? o, out JsValue jsValue)
{
if (CommonProperties.Length.Equals(property))
{
jsValue = JsNumber.Create((uint) s.Length);
return true;
}
if (property is JsNumber number && number.IsInteger())
{
var index = number.AsInteger();
var str = s._value;
if (index < 0 || index >= str.Length)
{
jsValue = JsValue.Undefined;
return true;
}
jsValue = JsString.Create(str[index]);
return true;
}
if (property is JsString propertyString
&& propertyString._value.Length > 0
&& char.IsLower(propertyString._value[0]))
{
// trying to find property that's always in prototype
o = Realm.Intrinsics.String.PrototypeObject;
}
jsValue = JsValue.Undefined;
return false;
}
///
/// https://tc39.es/ecma262/#sec-putvalue
///
internal void PutValue(Reference reference, JsValue value)
{
if (reference.IsUnresolvableReference)
{
if (reference.Strict && reference.ReferencedName != CommonProperties.Arguments)
{
ExceptionHelper.ThrowReferenceError(Realm, reference);
}
Realm.GlobalObject.Set(reference.ReferencedName, value, throwOnError: false);
}
else if (reference.IsPropertyReference)
{
var baseObject = Runtime.TypeConverter.ToObject(Realm, reference.Base);
if (reference.IsPrivateReference)
{
baseObject.PrivateSet((PrivateName) reference.ReferencedName, value);
return;
}
var succeeded = baseObject.Set(reference.ReferencedName, value, reference.ThisValue);
if (!succeeded && reference.Strict)
{
ExceptionHelper.ThrowTypeError(Realm, "Cannot assign to read only property '" + reference.ReferencedName + "' of " + baseObject);
}
}
else
{
((Environment) reference.Base).SetMutableBinding(Runtime.TypeConverter.ToString(reference.ReferencedName), value, reference.Strict);
}
}
///
/// Invoke the current value as function.
///
/// The name of the function to call.
/// The arguments of the function call.
/// The value returned by the function call.
public JsValue Invoke(string propertyName, params object?[] arguments)
{
return Invoke(propertyName, thisObj: null, arguments);
}
///
/// Invoke the current value as function.
///
/// The name of the function to call.
/// The this value inside the function call.
/// The arguments of the function call.
/// The value returned by the function call.
public JsValue Invoke(string propertyName, object? thisObj, object?[] arguments)
{
var value = GetValue(propertyName);
return Invoke(value, thisObj, arguments);
}
///
/// Invoke the current value as function.
///
/// The function to call.
/// The arguments of the function call.
/// The value returned by the function call.
public JsValue Invoke(JsValue value, params object?[] arguments)
{
return Invoke(value, thisObj: null, arguments);
}
///
/// Invoke the current value as function.
///
/// The function to call.
/// The this value inside the function call.
/// The arguments of the function call.
/// The value returned by the function call.
public JsValue Invoke(JsValue value, object? thisObj, object?[] arguments)
{
var callable = value as ICallable;
if (callable is null)
{
ExceptionHelper.ThrowJavaScriptException(Realm.Intrinsics.TypeError, "Can only invoke functions");
}
JsValue DoInvoke()
{
var items = _jsValueArrayPool.RentArray(arguments.Length);
for (var i = 0; i < arguments.Length; ++i)
{
items[i] = JsValue.FromObject(this, arguments[i]);
}
// ensure logic is in sync between Call, Construct, engine.Invoke and JintCallExpression!
JsValue result;
var thisObject = JsValue.FromObject(this, thisObj);
if (callable is Function functionInstance)
{
var callStack = CallStack;
callStack.Push(functionInstance, expression: null, ExecutionContext);
try
{
result = functionInstance.Call(thisObject, items);
}
finally
{
// if call stack was reset due to recursive call to engine or similar, we might not have it anymore
if (callStack.Count > 0)
{
callStack.Pop();
}
}
}
else
{
result = callable.Call(thisObject, items);
}
_jsValueArrayPool.ReturnArray(items);
return result;
}
return ExecuteWithConstraints(Options.Strict, DoInvoke);
}
internal T ExecuteWithConstraints(bool strict, Func callback)
{
ResetConstraints();
var ownsContext = _activeEvaluationContext is null;
_activeEvaluationContext ??= new EvaluationContext(this);
try
{
using (new StrictModeScope(strict))
{
return callback();
}
}
finally
{
if (ownsContext)
{
_activeEvaluationContext = null!;
}
ResetConstraints();
_agent.ClearKeptObjects();
}
}
///
/// https://tc39.es/ecma262/#sec-invoke
///
internal JsValue Invoke(JsValue v, JsValue p, JsValue[] arguments)
{
var ownsContext = _activeEvaluationContext is null;
_activeEvaluationContext ??= new EvaluationContext(this);
try
{
var func = GetV(v, p);
var callable = func as ICallable;
if (callable is null)
{
ExceptionHelper.ThrowTypeErrorNoEngine("Can only invoke functions");
}
return callable.Call(v, arguments);
}
finally
{
if (ownsContext)
{
_activeEvaluationContext = null!;
}
}
}
///
/// https://tc39.es/ecma262/#sec-getv
///
private JsValue GetV(JsValue v, JsValue p)
{
var o = Runtime.TypeConverter.ToObject(Realm, v);
return o.Get(p);
}
///
/// Gets a named value from the Global scope.
///
/// The name of the property to return.
public JsValue GetValue(string propertyName)
{
return GetValue(Realm.GlobalObject, new JsString(propertyName));
}
///
/// Gets the last evaluated .
///
internal SyntaxElement GetLastSyntaxElement()
{
return _lastSyntaxElement!;
}
///
/// Gets a named value from the specified scope.
///
/// The scope to get the property from.
/// The name of the property to return.
public JsValue GetValue(JsValue scope, JsValue property)
{
var reference = _referencePool.Rent(scope, property, _isStrict, thisValue: null);
var jsValue = GetValue(reference, false);
_referencePool.Return(reference);
return jsValue;
}
///
/// https://tc39.es/ecma262/#sec-resolvebinding
///
internal Reference ResolveBinding(string name, Environment? env = null)
{
env ??= ExecutionContext.LexicalEnvironment;
return GetIdentifierReference(env, name, StrictModeScope.IsStrictModeCode);
}
private static Reference GetIdentifierReference(Environment? env, string name, bool strict)
{
if (env is null)
{
return new Reference(JsValue.Undefined, name, strict);
}
var envRec = env;
if (envRec.HasBinding(name))
{
return new Reference(envRec, name, strict);
}
return GetIdentifierReference(env._outerEnv, name, strict);
}
///
/// https://tc39.es/ecma262/#sec-getnewtarget
///
internal JsValue GetNewTarget(Environment? thisEnvironment = null)
{
// we can take as argument if caller site has already determined the value, otherwise resolve
thisEnvironment ??= ExecutionContext.GetThisEnvironment();
return thisEnvironment.NewTarget ?? JsValue.Undefined;
}
///
/// https://tc39.es/ecma262/#sec-resolvethisbinding
///
internal JsValue ResolveThisBinding()
{
var envRec = ExecutionContext.GetThisEnvironment();
return envRec.GetThisBinding();
}
///
/// https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
///
private void GlobalDeclarationInstantiation(
Script script,
GlobalEnvironment env)
{
var hoistingScope = script.GetHoistingScope();
var functionDeclarations = hoistingScope._functionDeclarations;
var functionToInitialize = new List();
var declaredFunctionNames = new HashSet();
var declaredVarNames = new List();
var realm = Realm;
if (functionDeclarations != null)
{
for (var i = functionDeclarations.Count - 1; i >= 0; i--)
{
var d = functionDeclarations[i];
var fn = (Key) d.Id!.Name;
if (!declaredFunctionNames.Contains(fn))
{
var fnDefinable = env.CanDeclareGlobalFunction(fn);
if (!fnDefinable)
{
ExceptionHelper.ThrowTypeError(realm, "Cannot declare global function " + fn);
}
declaredFunctionNames.Add(fn);
functionToInitialize.Add(new JintFunctionDefinition(d));
}
}
}
var varNames = script.GetVarNames(hoistingScope);
for (var j = 0; j < varNames.Count; j++)
{
var vn = varNames[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);
}
declaredVarNames.Add(vn);
}
}
PrivateEnvironment? privateEnv = null;
var lexNames = script.GetLexNames(hoistingScope);
for (var i = 0; i < lexNames.Count; i++)
{
var (dn, constant) = lexNames[i];
if (env.HasVarDeclaration(dn) || env.HasLexicalDeclaration(dn) || env.HasRestrictedGlobalProperty(dn))
{
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared");
}
if (constant)
{
env.CreateImmutableBinding(dn, strict: true);
}
else
{
env.CreateMutableBinding(dn, canBeDeleted: false);
}
}
// 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];
Key fn = f.Name!;
if (env.HasLexicalDeclaration(fn))
{
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{fn}' has already been declared");
}
var fo = realm.Intrinsics.Function.InstantiateFunctionObject(f, env, privateEnv);
env.CreateGlobalFunctionBinding(fn, fo, canBeDeleted: false);
}
env.CreateGlobalVarBindings(declaredVarNames, canBeDeleted: false);
}
///
/// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
///
internal JsArguments? FunctionDeclarationInstantiation(
Function function,
JsValue[] argumentsList)
{
var calleeContext = ExecutionContext;
var func = function._functionDefinition;
var env = (FunctionEnvironment) ExecutionContext.LexicalEnvironment;
var strict = _isStrict || StrictModeScope.IsStrictModeCode;
var configuration = func.Initialize();
var parameterNames = configuration.ParameterNames;
var hasDuplicates = configuration.HasDuplicates;
var simpleParameterList = configuration.IsSimpleParameterList;
var hasParameterExpressions = configuration.HasParameterExpressions;
var canInitializeParametersOnDeclaration = simpleParameterList && !configuration.HasDuplicates;
var arguments = canInitializeParametersOnDeclaration ? argumentsList : null;
env.InitializeParameters(parameterNames, hasDuplicates, arguments);
JsArguments? ao = null;
if (configuration.ArgumentsObjectNeeded || _isDebugMode)
{
if (strict || !simpleParameterList)
{
ao = CreateUnmappedArgumentsObject(argumentsList);
}
else
{
// NOTE: mapped argument object is only provided for non-strict functions that don't have a rest parameter,
// any parameter default value initializers, or any destructured parameters.
ao = CreateMappedArgumentsObject(function, parameterNames, argumentsList, env, configuration.HasRestParameter);
}
if (strict)
{
env.CreateImmutableBindingAndInitialize(KnownKeys.Arguments, strict: false, ao);
}
else
{
env.CreateMutableBindingAndInitialize(KnownKeys.Arguments, canBeDeleted: false, ao);
}
}
if (!canInitializeParametersOnDeclaration)
{
// slower set
env.AddFunctionParameters(_activeEvaluationContext!, func.Function, argumentsList);
}
// Let iteratorRecord be CreateListIteratorRecord(argumentsList).
// If hasDuplicates is true, then
// Perform ? IteratorBindingInitialization for formals with iteratorRecord and undefined as arguments.
// Else,
// Perform ? IteratorBindingInitialization for formals with iteratorRecord and env as arguments.
Environment varEnv;
if (!hasParameterExpressions)
{
// NOTE: Only a single lexical environment is needed for the parameters and top-level vars.
var varsToInitialize = configuration.VarsToInitialize!;
for (var i = 0; i < varsToInitialize.Count; i++)
{
var pair = varsToInitialize[i];
env.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, JsValue.Undefined);
}
varEnv = env;
}
else
{
// NOTE: A separate Environment Record is needed to ensure that closures created by expressions
// in the formal parameter list do not have visibility of declarations in the function body.
var varEnvRec = JintEnvironment.NewDeclarativeEnvironment(this, env);
varEnv = varEnvRec;
UpdateVariableEnvironment(varEnv);
var varsToInitialize = configuration.VarsToInitialize!;
for (var i = 0; i < varsToInitialize.Count; i++)
{
var pair = varsToInitialize[i];
var initialValue = pair.InitialValue ?? env.GetBindingValue(pair.Name, strict: false);
varEnvRec.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, initialValue);
}
}
// NOTE: Annex B.3.3.1 adds additional steps at this point.
// A https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation
Environment lexEnv;
if (!strict)
{
lexEnv = JintEnvironment.NewDeclarativeEnvironment(this, varEnv);
// NOTE: Non-strict functions use a separate lexical Environment Record for top-level lexical declarations
// so that a direct eval can determine whether any var scoped declarations introduced by the eval code conflict
// with pre-existing top-level lexically scoped declarations. This is not needed for strict functions
// because a strict direct eval always places all declarations into a new Environment Record.
}
else
{
lexEnv = varEnv;
}
UpdateLexicalEnvironment(lexEnv);
if (configuration.LexicalDeclarations.Length > 0)
{
foreach (var d in configuration.LexicalDeclarations)
{
for (var j = 0; j < d.BoundNames.Count; j++)
{
var dn = d.BoundNames[j];
if (d.IsConstantDeclaration)
{
lexEnv.CreateImmutableBinding(dn, strict: true);
}
else
{
lexEnv.CreateMutableBinding(dn, canBeDeleted: false);
}
}
}
}
if (configuration.FunctionsToInitialize != null)
{
var privateEnv = calleeContext.PrivateEnvironment;
var realm = Realm;
foreach (var f in configuration.FunctionsToInitialize)
{
var jintFunctionDefinition = new JintFunctionDefinition(f);
var fn = jintFunctionDefinition.Name!;
var fo = realm.Intrinsics.Function.InstantiateFunctionObject(jintFunctionDefinition, lexEnv, privateEnv);
varEnv.SetMutableBinding(fn, fo, strict: false);
}
}
return ao;
}
private JsArguments CreateMappedArgumentsObject(
Function func,
Key[] formals,
JsValue[] argumentsList,
DeclarativeEnvironment envRec,
bool hasRestParameter)
{
return _argumentsInstancePool.Rent(func, formals, argumentsList, envRec, hasRestParameter);
}
private JsArguments CreateUnmappedArgumentsObject(JsValue[] argumentsList)
{
return _argumentsInstancePool.Rent(argumentsList);
}
///
/// https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
///
internal void EvalDeclarationInstantiation(
Script script,
Environment varEnv,
Environment lexEnv,
PrivateEnvironment? privateEnv,
bool strict)
{
var hoistingScope = HoistingScope.GetProgramLevelDeclarations(script);
var lexEnvRec = (DeclarativeEnvironment) lexEnv;
var varEnvRec = varEnv;
var realm = Realm;
if (!strict && hoistingScope._variablesDeclarations != null)
{
if (varEnvRec is GlobalEnvironment globalEnvironmentRecord)
{
ref readonly var nodes = ref hoistingScope._variablesDeclarations;
for (var i = 0; i < nodes.Count; i++)
{
var variablesDeclaration = nodes[i];
var identifier = (Identifier) variablesDeclaration.Declarations[0].Id;
if (globalEnvironmentRecord.HasLexicalDeclaration(identifier.Name))
{
ExceptionHelper.ThrowSyntaxError(realm, "Identifier '" + identifier.Name + "' has already been declared");
}
}
}
var thisLex = lexEnv;
while (!ReferenceEquals(thisLex, varEnv))
{
var thisEnvRec = thisLex;
if (thisEnvRec is not ObjectEnvironment)
{
ref readonly var nodes = ref hoistingScope._variablesDeclarations;
for (var i = 0; i < nodes.Count; i++)
{
var variablesDeclaration = nodes[i];
var identifier = (Identifier) variablesDeclaration.Declarations[0].Id;
if (thisEnvRec!.HasBinding(identifier.Name))
{
ExceptionHelper.ThrowSyntaxError(realm);
}
}
}
thisLex = thisLex!._outerEnv;
}
}
HashSet? privateIdentifiers = null;
var pointer = privateEnv;
while (pointer is not null)
{
foreach (var name in pointer.Names)
{
privateIdentifiers??= new HashSet(PrivateIdentifierNameComparer._instance);
privateIdentifiers.Add(name.Key);
}
pointer = pointer.OuterPrivateEnvironment;
}
script.AllPrivateIdentifiersValid(realm, privateIdentifiers);
var functionDeclarations = hoistingScope._functionDeclarations;
var functionsToInitialize = new LinkedList();
var declaredFunctionNames = new HashSet();
if (functionDeclarations != null)
{
for (var i = functionDeclarations.Count - 1; i >= 0; i--)
{
var d = functionDeclarations[i];
Key fn = d.Id!.Name;
if (!declaredFunctionNames.Contains(fn))
{
if (varEnvRec is GlobalEnvironment ger)
{
var fnDefinable = ger.CanDeclareGlobalFunction(fn);
if (!fnDefinable)
{
ExceptionHelper.ThrowTypeError(realm);
}
}
declaredFunctionNames.Add(fn);
functionsToInitialize.AddFirst(new JintFunctionDefinition(d));
}
}
}
var boundNames = new List();
var declaredVarNames = new List();
var variableDeclarations = hoistingScope._variablesDeclarations;
var variableDeclarationsCount = variableDeclarations?.Count;
for (var i = 0; i < variableDeclarationsCount; i++)
{
var variableDeclaration = variableDeclarations![i];
boundNames.Clear();
variableDeclaration.GetBoundNames(boundNames);
for (var j = 0; j < boundNames.Count; j++)
{
var vn = boundNames[j];
if (!declaredFunctionNames.Contains(vn))
{
if (varEnvRec is GlobalEnvironment ger)
{
var vnDefinable = ger.CanDeclareGlobalFunction(vn);
if (!vnDefinable)
{
ExceptionHelper.ThrowTypeError(realm);
}
}
declaredVarNames.Add(vn);
}
}
}
var lexicalDeclarations = hoistingScope._lexicalDeclarations;
var lexicalDeclarationsCount = lexicalDeclarations?.Count;
for (var i = 0; i < lexicalDeclarationsCount; i++)
{
boundNames.Clear();
var d = lexicalDeclarations![i];
d.GetBoundNames(boundNames);
for (var j = 0; j < boundNames.Count; j++)
{
Key dn = boundNames[j];
if (d.IsConstantDeclaration())
{
lexEnvRec.CreateImmutableBinding(dn, strict: true);
}
else
{
lexEnvRec.CreateMutableBinding(dn, canBeDeleted: false);
}
}
}
foreach (var f in functionsToInitialize)
{
var fo = realm.Intrinsics.Function.InstantiateFunctionObject(f, lexEnv, privateEnv);
if (varEnvRec is GlobalEnvironment ger)
{
ger.CreateGlobalFunctionBinding(f.Name!, fo, canBeDeleted: true);
}
else
{
Key fn = f.Name!;
var bindingExists = varEnvRec.HasBinding(fn);
if (!bindingExists)
{
varEnvRec.CreateMutableBinding(fn, canBeDeleted: true);
varEnvRec.InitializeBinding(fn, fo);
}
else
{
varEnvRec.SetMutableBinding(fn, fo, strict: false);
}
}
}
foreach (var vn in declaredVarNames)
{
if (varEnvRec is GlobalEnvironment ger)
{
ger.CreateGlobalVarBinding(vn, true);
}
else
{
var bindingExists = varEnvRec.HasBinding(vn);
if (!bindingExists)
{
varEnvRec.CreateMutableBinding(vn, canBeDeleted: true);
varEnvRec.InitializeBinding(vn, JsValue.Undefined);
}
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void UpdateLexicalEnvironment(Environment newEnv)
{
_executionContexts.ReplaceTopLexicalEnvironment(newEnv);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void UpdateVariableEnvironment(Environment newEnv)
{
_executionContexts.ReplaceTopVariableEnvironment(newEnv);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void UpdatePrivateEnvironment(PrivateEnvironment? newEnv)
{
_executionContexts.ReplaceTopPrivateEnvironment(newEnv);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref readonly ExecutionContext UpdateGenerator(GeneratorInstance generator)
{
return ref _executionContexts.ReplaceTopGenerator(generator);
}
///
/// Invokes the named callable and returns the resulting object.
///
/// The name of the callable.
/// The arguments of the call.
/// The value returned by the call.
public JsValue Call(string callableName, params JsValue[] arguments)
{
var callable = Evaluate(callableName);
return Call(callable, arguments);
}
///
/// Invokes the callable and returns the resulting object.
///
/// The callable.
/// The arguments of the call.
/// The value returned by the call.
public JsValue Call(JsValue callable, params JsValue[] arguments)
=> Call(callable, thisObject: JsValue.Undefined, arguments);
///
/// Invokes the callable and returns the resulting object.
///
/// The callable.
/// Value bound as this.
/// The arguments of the call.
/// The value returned by the call.
public JsValue Call(JsValue callable, JsValue thisObject, JsValue[] arguments)
{
JsValue Callback()
{
if (!callable.IsCallable)
{
ExceptionHelper.ThrowArgumentException(callable + " is not callable");
}
return Call((ICallable) callable, thisObject, arguments, null);
}
return ExecuteWithConstraints(Options.Strict, Callback);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal JsValue Call(ICallable callable, JsValue thisObject, JsValue[] arguments, JintExpression? expression)
{
if (callable is Function functionInstance)
{
return Call(functionInstance, thisObject, arguments, expression);
}
return callable.Call(thisObject, arguments);
}
///
/// Calls the named constructor and returns the resulting object.
///
/// The name of the constructor to call.
/// The arguments of the constructor call.
/// The value returned by the constructor call.
public ObjectInstance Construct(string constructorName, params JsValue[] arguments)
{
var constructor = Evaluate(constructorName);
return Construct(constructor, arguments);
}
///
/// Calls the constructor and returns the resulting object.
///
/// The name of the constructor to call.
/// The arguments of the constructor call.
/// The value returned by the constructor call.
public ObjectInstance Construct(JsValue constructor, params JsValue[] arguments)
{
ObjectInstance Callback()
{
if (!constructor.IsConstructor)
{
ExceptionHelper.ThrowArgumentException(constructor + " is not a constructor");
}
return Construct(constructor, arguments, constructor, null);
}
return ExecuteWithConstraints(Options.Strict, Callback);
}
internal ObjectInstance Construct(
JsValue constructor,
JsValue[] arguments,
JsValue newTarget,
JintExpression? expression)
{
if (constructor is Function functionInstance)
{
return Construct(functionInstance, arguments, newTarget, expression);
}
return ((IConstructor) constructor).Construct(arguments, newTarget);
}
internal JsValue Call(Function function, JsValue thisObject)
=> Call(function, thisObject, Arguments.Empty, null);
internal JsValue Call(
Function function,
JsValue thisObject,
JsValue[] arguments,
JintExpression? expression)
{
// ensure logic is in sync between Call, Construct, engine.Invoke and JintCallExpression!
var recursionDepth = CallStack.Push(function, expression, ExecutionContext);
if (recursionDepth > Options.Constraints.MaxRecursionDepth)
{
// automatically pops the current element as it was never reached
ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack);
}
JsValue result;
try
{
result = function.Call(thisObject, arguments);
}
finally
{
// if call stack was reset due to recursive call to engine or similar, we might not have it anymore
if (CallStack.Count > 0)
{
CallStack.Pop();
}
}
return result;
}
private ObjectInstance Construct(
Function function,
JsValue[] arguments,
JsValue newTarget,
JintExpression? expression)
{
// ensure logic is in sync between Call, Construct, engine.Invoke and JintCallExpression!
var recursionDepth = CallStack.Push(function, expression, ExecutionContext);
if (recursionDepth > Options.Constraints.MaxRecursionDepth)
{
// automatically pops the current element as it was never reached
ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack);
}
ObjectInstance result;
try
{
result = ((IConstructor) function).Construct(arguments, newTarget);
}
finally
{
CallStack.Pop();
}
return result;
}
internal void SignalError(ErrorDispatchInfo error)
{
_error = error;
}
internal void RegisterTypeReference(TypeReference reference)
{
_typeReferences ??= new Dictionary();
_typeReferences[reference.ReferenceType] = reference;
}
internal ref readonly ExecutionContext GetExecutionContext(int fromTop)
{
return ref _executionContexts.Peek(fromTop);
}
public void Dispose()
{
if (_objectWrapperCache is null)
{
return;
}
#if SUPPORTS_WEAK_TABLE_CLEAR
_objectWrapperCache.Clear();
#else
// we can expect that reflection is OK as we've been generating object wrappers already
var clearMethod = _objectWrapperCache.GetType().GetMethod("Clear", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
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 Environment VariableEnvironment => _engine.ExecutionContext.VariableEnvironment;
public Environment LexicalEnvironment => _engine.ExecutionContext.LexicalEnvironment;
}
}
}