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