using System.Diagnostics;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Jint.Collections;
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 Parser _defaultParser;
private ParserOptions? _defaultModuleParserOptions; // cache default ParserOptions for ModuleBuilder instances
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;
private readonly bool _customResolver;
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;
_customResolver = !ReferenceEquals(_referenceResolver, DefaultReferenceResolver.Instance);
_referencePool = new ReferencePool();
_argumentsInstancePool = new ArgumentsInstancePool(this);
_jsValueArrayPool = new JsValueArrayPool();
Options.Apply(this);
CallStack = new JintCallStack(Options.Constraints.MaxRecursionDepth >= 0);
_stackGuard = new StackGuard(this);
var defaultParserOptions = ScriptParsingOptions.Default.GetParserOptions(Options);
_defaultParser = new Parser(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 Node? _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 ParserOptions DefaultModuleParserOptions => _defaultModuleParserOptions ??= ModuleParsingOptions.Default.GetParserOptions(Options);
internal ParserOptions GetActiveParserOptions()
{
return _executionContexts?.GetActiveParserOptions() ?? _defaultParser.Options;
}
internal Parser GetParserFor(ScriptParsingOptions parsingOptions)
{
return ReferenceEquals(parsingOptions, ScriptParsingOptions.Default)
? _defaultParser
: new Parser(parsingOptions.GetParserOptions(Options));
}
internal Parser GetParserFor(ParserOptions parserOptions)
{
return ReferenceEquals(parserOptions, _defaultParser.Options) ? _defaultParser : new Parser(parserOptions);
}
internal void EnterExecutionContext(
Environment lexicalEnvironment,
Environment variableEnvironment,
Realm realm,
PrivateEnvironment? privateEnvironment)
{
var context = new ExecutionContext(
null,
lexicalEnvironment,
variableEnvironment,
privateEnvironment,
realm,
null);
_executionContexts.Push(context);
}
internal void EnterExecutionContext(in ExecutionContext context)
{
_executionContexts.Push(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, string? source = null)
{
var script = _defaultParser.ParseScriptGuarded(Realm, code, source ?? "", _isStrict);
return Evaluate(new Prepared