Engine.Ast.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. using System.Runtime.InteropServices;
  2. using System.Text.RegularExpressions;
  3. using Jint.Native;
  4. using Jint.Runtime;
  5. using Jint.Runtime.Interpreter;
  6. using Jint.Runtime.Interpreter.Expressions;
  7. using Jint.Runtime.Interpreter.Statements;
  8. using Environment = Jint.Runtime.Environments.Environment;
  9. namespace Jint;
  10. public partial class Engine
  11. {
  12. /// <summary>
  13. /// Prepares a script for the engine that includes static analysis data to speed up execution during run-time.
  14. /// </summary>
  15. /// <remarks>
  16. /// Returned instance is reusable and thread-safe. You should prepare scripts only once and then reuse them.
  17. /// </remarks>
  18. public static Prepared<Script> PrepareScript(string code, string? source = null, bool strict = false, ScriptPreparationOptions? options = null)
  19. {
  20. options ??= ScriptPreparationOptions.Default;
  21. var astAnalyzer = new AstAnalyzer(options);
  22. var parserOptions = options.GetParserOptions();
  23. var preparedScript = new Parser(parserOptions with { OnNodeCreated = astAnalyzer.NodeVisitor }).ParseScript(code, source, strict);
  24. return new Prepared<Script>(preparedScript, parserOptions);
  25. }
  26. /// <summary>
  27. /// Prepares a module for the engine that includes static analysis data to speed up execution during run-time.
  28. /// </summary>
  29. /// <remarks>
  30. /// Returned instance is reusable and thread-safe. You should prepare modules only once and then reuse them.
  31. /// </remarks>
  32. public static Prepared<Module> PrepareModule(string code, string? source = null, ModulePreparationOptions? options = null)
  33. {
  34. options ??= ModulePreparationOptions.Default;
  35. var astAnalyzer = new AstAnalyzer(options);
  36. var parserOptions = options.GetParserOptions();
  37. var preparedModule = new Parser(parserOptions with { OnNodeCreated = astAnalyzer.NodeVisitor }).ParseModule(code, source);
  38. return new Prepared<Module>(preparedModule, parserOptions);
  39. }
  40. private sealed class AstAnalyzer
  41. {
  42. private static readonly bool _canCompileNegativeLookaroundAssertions = typeof(Regex).Assembly.GetName().Version?.Major is not (null or 7 or 8);
  43. private readonly IPreparationOptions<IParsingOptions> _preparationOptions;
  44. private readonly Dictionary<string, Environment.BindingName> _bindingNames = new(StringComparer.Ordinal);
  45. private readonly Dictionary<string, Regex> _regexes = new(StringComparer.Ordinal);
  46. public AstAnalyzer(IPreparationOptions<IParsingOptions> preparationOptions)
  47. {
  48. _preparationOptions = preparationOptions;
  49. }
  50. public void NodeVisitor(Node node)
  51. {
  52. switch (node.Type)
  53. {
  54. case NodeType.Identifier:
  55. var identifier = (Identifier) node;
  56. var name = identifier.Name;
  57. if (!_bindingNames.TryGetValue(name, out var bindingName))
  58. {
  59. _bindingNames[name] = bindingName = new Environment.BindingName(JsString.CachedCreate(name));
  60. }
  61. node.AssociatedData = new JintIdentifierExpression(identifier, bindingName);
  62. break;
  63. case NodeType.Literal:
  64. var literal = (Literal) node;
  65. var constantValue = JintLiteralExpression.ConvertToJsValue(literal);
  66. node.AssociatedData = constantValue is not null ? new JintConstantExpression(literal, constantValue) : null;
  67. if (node.AssociatedData is null && literal.TokenType == TokenKind.RegularExpression
  68. && !_canCompileNegativeLookaroundAssertions && _preparationOptions.ParsingOptions.CompileRegex != false)
  69. {
  70. var regExpLiteral = (RegExpLiteral) literal;
  71. var regExpParseResult = regExpLiteral.ParseResult;
  72. // only compile if there's no negative lookahead, it works incorrectly under NET 7 and NET 8
  73. // https://github.com/dotnet/runtime/issues/97455
  74. if (regExpParseResult.Success && regExpLiteral.Raw.Contains("(?!"))
  75. {
  76. if (!_regexes.TryGetValue(regExpLiteral.Raw, out var regex))
  77. {
  78. regex = regExpParseResult.Regex!;
  79. if ((regex.Options & RegexOptions.Compiled) != RegexOptions.None)
  80. {
  81. regex = new Regex(regex.ToString(), regex.Options & ~RegexOptions.Compiled, regex.MatchTimeout);
  82. }
  83. _regexes[regExpLiteral.Raw] = regex;
  84. }
  85. regExpLiteral.AssociatedData = regex;
  86. }
  87. }
  88. break;
  89. case NodeType.MemberExpression:
  90. node.AssociatedData = JintMemberExpression.InitializeDeterminedProperty((MemberExpression) node, cache: true);
  91. break;
  92. case NodeType.ArrowFunctionExpression:
  93. case NodeType.FunctionDeclaration:
  94. case NodeType.FunctionExpression:
  95. var function = (IFunction) node;
  96. node.AssociatedData = JintFunctionDefinition.BuildState(function);
  97. break;
  98. case NodeType.Program:
  99. node.AssociatedData = new CachedHoistingScope((Program) node);
  100. break;
  101. case NodeType.UnaryExpression:
  102. node.AssociatedData = JintUnaryExpression.BuildConstantExpression((UnaryExpression) node);
  103. break;
  104. case NodeType.BinaryExpression:
  105. var binaryExpression = (BinaryExpression) node;
  106. if (_preparationOptions.FoldConstants
  107. && binaryExpression.Operator != BinaryOperator.InstanceOf
  108. && binaryExpression.Operator != BinaryOperator.In
  109. && binaryExpression is { Left: Literal leftLiteral, Right: Literal rightLiteral })
  110. {
  111. var left = JintLiteralExpression.ConvertToJsValue(leftLiteral);
  112. var right = JintLiteralExpression.ConvertToJsValue(rightLiteral);
  113. if (left is not null && right is not null)
  114. {
  115. // we have fixed result
  116. try
  117. {
  118. var result = JintBinaryExpression.Build(binaryExpression);
  119. var context = new EvaluationContext();
  120. node.AssociatedData = new JintConstantExpression(binaryExpression, (JsValue) result.EvaluateWithoutNodeTracking(context));
  121. }
  122. catch
  123. {
  124. // probably caused an error and error reporting doesn't work without engine
  125. }
  126. }
  127. }
  128. break;
  129. case NodeType.ReturnStatement:
  130. var returnStatement = (ReturnStatement) node;
  131. if (returnStatement.Argument is Literal returnedLiteral)
  132. {
  133. var returnValue = JintLiteralExpression.ConvertToJsValue(returnedLiteral);
  134. if (returnValue is not null)
  135. {
  136. node.AssociatedData = new ConstantStatement(returnStatement, CompletionType.Return, returnValue);
  137. }
  138. }
  139. break;
  140. }
  141. }
  142. }
  143. }
  144. internal sealed class CachedHoistingScope
  145. {
  146. public CachedHoistingScope(Program program)
  147. {
  148. Scope = HoistingScope.GetProgramLevelDeclarations(program);
  149. VarNames = new List<Key>();
  150. GatherVarNames(Scope, VarNames);
  151. LexNames = new List<CachedLexicalName>();
  152. GatherLexNames(Scope, LexNames);
  153. }
  154. internal static void GatherVarNames(HoistingScope scope, List<Key> boundNames)
  155. {
  156. var varDeclarations = scope._variablesDeclarations;
  157. if (varDeclarations != null)
  158. {
  159. for (var i = 0; i < varDeclarations.Count; i++)
  160. {
  161. var d = varDeclarations[i];
  162. d.GetBoundNames(boundNames);
  163. }
  164. }
  165. }
  166. internal static void GatherLexNames(HoistingScope scope, List<CachedLexicalName> boundNames)
  167. {
  168. var lexDeclarations = scope._lexicalDeclarations;
  169. if (lexDeclarations != null)
  170. {
  171. var temp = new List<Key>();
  172. for (var i = 0; i < lexDeclarations.Count; i++)
  173. {
  174. var d = lexDeclarations[i];
  175. temp.Clear();
  176. d.GetBoundNames(temp);
  177. for (var j = 0; j < temp.Count; j++)
  178. {
  179. boundNames.Add(new CachedLexicalName(temp[j], d.IsConstantDeclaration()));
  180. }
  181. }
  182. }
  183. }
  184. [StructLayout(LayoutKind.Auto)]
  185. internal readonly record struct CachedLexicalName(Key Name, bool Constant);
  186. public HoistingScope Scope { get; }
  187. public List<Key> VarNames { get; }
  188. public List<CachedLexicalName> LexNames { get; }
  189. }
  190. internal static class AstPreparationExtensions
  191. {
  192. internal static HoistingScope GetHoistingScope(this Program program)
  193. {
  194. return program.AssociatedData is CachedHoistingScope cached ? cached.Scope : HoistingScope.GetProgramLevelDeclarations(program);
  195. }
  196. internal static List<Key> GetVarNames(this Program program, HoistingScope hoistingScope)
  197. {
  198. List<Key> boundNames;
  199. if (program.AssociatedData is CachedHoistingScope cached)
  200. {
  201. boundNames = cached.VarNames;
  202. }
  203. else
  204. {
  205. boundNames = new List<Key>();
  206. CachedHoistingScope.GatherVarNames(hoistingScope, boundNames);
  207. }
  208. return boundNames;
  209. }
  210. internal static List<CachedHoistingScope.CachedLexicalName> GetLexNames(this Program program, HoistingScope hoistingScope)
  211. {
  212. List<CachedHoistingScope.CachedLexicalName> boundNames;
  213. if (program.AssociatedData is CachedHoistingScope cached)
  214. {
  215. boundNames = cached.LexNames;
  216. }
  217. else
  218. {
  219. boundNames = new List<CachedHoistingScope.CachedLexicalName>();
  220. CachedHoistingScope.GatherLexNames(hoistingScope, boundNames);
  221. }
  222. return boundNames;
  223. }
  224. }