Engine.Ast.cs 10 KB

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