AstExtensions.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. using System.Runtime.CompilerServices;
  2. using Jint.Native;
  3. using Jint.Native.Function;
  4. using Jint.Native.Object;
  5. using Jint.Runtime;
  6. using Jint.Runtime.Environments;
  7. using Jint.Runtime.Interpreter;
  8. using Jint.Runtime.Interpreter.Expressions;
  9. using Jint.Runtime.Modules;
  10. using Environment = Jint.Runtime.Environments.Environment;
  11. namespace Jint
  12. {
  13. public static class AstExtensions
  14. {
  15. #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value
  16. internal static readonly SourceLocation DefaultLocation;
  17. #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value
  18. public static JsValue GetKey<T>(this T property, Engine engine) where T : IProperty => GetKey(property.Key, engine, property.Computed);
  19. public static JsValue GetKey(this Expression expression, Engine engine, bool resolveComputed = false)
  20. {
  21. var key = TryGetKey(expression, engine, resolveComputed);
  22. if (key is not null)
  23. {
  24. return TypeConverter.ToPropertyKey(key);
  25. }
  26. ExceptionHelper.ThrowArgumentException("Unable to extract correct key, node type: " + expression.Type);
  27. return JsValue.Undefined;
  28. }
  29. internal static JsValue TryGetKey<T>(this T property, Engine engine) where T : IProperty
  30. {
  31. return TryGetKey(property.Key, engine, property.Computed);
  32. }
  33. internal static JsValue TryGetKey<T>(this T expression, Engine engine, bool resolveComputed) where T : Expression
  34. {
  35. JsValue key;
  36. if (expression is Literal literal)
  37. {
  38. key = literal.Kind == TokenKind.NullLiteral ? JsValue.Null : LiteralKeyToString(literal);
  39. }
  40. else if (!resolveComputed && expression is Identifier identifier)
  41. {
  42. key = identifier.Name;
  43. }
  44. else if (expression is PrivateIdentifier privateIdentifier)
  45. {
  46. key = engine.ExecutionContext.PrivateEnvironment!.Names[privateIdentifier];
  47. }
  48. else if (resolveComputed)
  49. {
  50. return TryGetComputedPropertyKey(expression, engine);
  51. }
  52. else
  53. {
  54. key = JsValue.Undefined;
  55. }
  56. return key;
  57. }
  58. internal static JsValue TryGetComputedPropertyKey<T>(T expression, Engine engine)
  59. where T : Expression
  60. {
  61. if (expression.Type is NodeType.Identifier
  62. or NodeType.CallExpression
  63. or NodeType.BinaryExpression
  64. or NodeType.UpdateExpression
  65. or NodeType.AssignmentExpression
  66. or NodeType.UnaryExpression
  67. or NodeType.MemberExpression
  68. or NodeType.LogicalExpression
  69. or NodeType.ConditionalExpression
  70. or NodeType.ArrowFunctionExpression
  71. or NodeType.FunctionExpression
  72. or NodeType.YieldExpression
  73. or NodeType.TemplateLiteral)
  74. {
  75. var context = engine._activeEvaluationContext ?? new EvaluationContext(engine);
  76. return JintExpression.Build(expression).GetValue(context);
  77. }
  78. return JsValue.Undefined;
  79. }
  80. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  81. internal static bool IsFunctionDefinition<T>(this T node) where T : Node
  82. {
  83. var type = node.Type;
  84. return type
  85. is NodeType.FunctionExpression
  86. or NodeType.ArrowFunctionExpression
  87. or NodeType.ClassExpression;
  88. }
  89. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  90. internal static bool IsStrict(this IFunction function)
  91. {
  92. return function.Body is FunctionBody { Strict: true };
  93. }
  94. /// <summary>
  95. /// https://tc39.es/ecma262/#sec-static-semantics-isconstantdeclaration
  96. /// </summary>
  97. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  98. internal static bool IsConstantDeclaration(this Declaration d)
  99. {
  100. return d is VariableDeclaration { Kind: VariableDeclarationKind.Const };
  101. }
  102. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  103. internal static bool HasName<T>(this T node) where T : Node
  104. {
  105. if (!node.IsFunctionDefinition())
  106. {
  107. return false;
  108. }
  109. if (node is IFunction { Id: not null })
  110. {
  111. return true;
  112. }
  113. if (node is ClassExpression { Id: not null })
  114. {
  115. return true;
  116. }
  117. return false;
  118. }
  119. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  120. internal static bool IsAnonymousFunctionDefinition<T>(this T node) where T : Node
  121. {
  122. if (!node.IsFunctionDefinition())
  123. {
  124. return false;
  125. }
  126. if (node.HasName())
  127. {
  128. return false;
  129. }
  130. return true;
  131. }
  132. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  133. internal static bool IsOptional<T>(this T node) where T : Expression
  134. {
  135. return node is IChainElement { Optional: true };
  136. }
  137. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  138. internal static string LiteralKeyToString(Literal literal)
  139. {
  140. if (literal is StringLiteral stringLiteral)
  141. {
  142. return stringLiteral.Value;
  143. }
  144. // prevent conversion to scientific notation
  145. else if (literal is NumericLiteral numericLiteral)
  146. {
  147. return TypeConverter.ToString(numericLiteral.Value);
  148. }
  149. else if (literal is BigIntLiteral bigIntLiteral)
  150. {
  151. return bigIntLiteral.Value.ToString(provider: null);
  152. }
  153. else
  154. {
  155. // We shouldn't ever reach this line in the case of a literal property key.
  156. return Convert.ToString(literal.Value, provider: null) ?? "";
  157. }
  158. }
  159. internal static void GetBoundNames(this VariableDeclaration variableDeclaration, List<Key> target)
  160. {
  161. ref readonly var declarations = ref variableDeclaration.Declarations;
  162. for (var i = 0; i < declarations.Count; i++)
  163. {
  164. var declaration = declarations[i];
  165. GetBoundNames(declaration.Id, target);
  166. }
  167. }
  168. internal static void GetBoundNames(this Node? parameter, List<Key> target)
  169. {
  170. if (parameter is null || parameter.Type == NodeType.Literal)
  171. {
  172. return;
  173. }
  174. // try to get away without a loop
  175. if (parameter is Identifier id)
  176. {
  177. target.Add(id.Name);
  178. return;
  179. }
  180. if (parameter is VariableDeclaration variableDeclaration)
  181. {
  182. variableDeclaration.GetBoundNames(target);
  183. return;
  184. }
  185. while (true)
  186. {
  187. if (parameter is Identifier identifier)
  188. {
  189. target.Add(identifier.Name);
  190. return;
  191. }
  192. if (parameter is RestElement restElement)
  193. {
  194. parameter = restElement.Argument;
  195. continue;
  196. }
  197. if (parameter is ArrayPattern arrayPattern)
  198. {
  199. ref readonly var arrayPatternElements = ref arrayPattern.Elements;
  200. for (var i = 0; i < arrayPatternElements.Count; i++)
  201. {
  202. var expression = arrayPatternElements[i];
  203. GetBoundNames(expression, target);
  204. }
  205. }
  206. else if (parameter is ObjectPattern objectPattern)
  207. {
  208. ref readonly var objectPatternProperties = ref objectPattern.Properties;
  209. for (var i = 0; i < objectPatternProperties.Count; i++)
  210. {
  211. var property = objectPatternProperties[i];
  212. if (property is AssignmentProperty p)
  213. {
  214. GetBoundNames(p.Value, target);
  215. }
  216. else
  217. {
  218. GetBoundNames((RestElement) property, target);
  219. }
  220. }
  221. }
  222. else if (parameter is AssignmentPattern assignmentPattern)
  223. {
  224. parameter = assignmentPattern.Left;
  225. continue;
  226. }
  227. else if (parameter is ClassDeclaration classDeclaration)
  228. {
  229. var name = classDeclaration.Id?.Name;
  230. if (name != null)
  231. {
  232. target.Add(name);
  233. }
  234. }
  235. break;
  236. }
  237. }
  238. /// <summary>
  239. /// https://tc39.es/ecma262/#sec-static-semantics-privateboundidentifiers
  240. /// </summary>
  241. internal static void PrivateBoundIdentifiers(this Node parameter, HashSet<PrivateIdentifier> target)
  242. {
  243. if (parameter.Type == NodeType.PrivateIdentifier)
  244. {
  245. target.Add((PrivateIdentifier) parameter);
  246. }
  247. else if (parameter.Type is NodeType.AccessorProperty or NodeType.MethodDefinition or NodeType.PropertyDefinition)
  248. {
  249. if (((ClassProperty) parameter).Key is PrivateIdentifier privateKeyIdentifier)
  250. {
  251. target.Add(privateKeyIdentifier);
  252. }
  253. }
  254. else if (parameter.Type == NodeType.ClassBody)
  255. {
  256. ref readonly var elements = ref ((ClassBody) parameter).Body;
  257. for (var i = 0; i < elements.Count; i++)
  258. {
  259. var element = elements[i];
  260. PrivateBoundIdentifiers(element, target);
  261. }
  262. }
  263. }
  264. internal static void BindingInitialization(
  265. this Node? expression,
  266. EvaluationContext context,
  267. JsValue value,
  268. Environment env)
  269. {
  270. if (expression is Identifier identifier)
  271. {
  272. var catchEnvRecord = (DeclarativeEnvironment) env;
  273. catchEnvRecord.CreateMutableBindingAndInitialize(identifier.Name, canBeDeleted: false, value);
  274. }
  275. else if (expression is DestructuringPattern pattern)
  276. {
  277. DestructuringPatternAssignmentExpression.ProcessPatterns(context, pattern, value, env);
  278. }
  279. }
  280. /// <summary>
  281. /// https://tc39.es/ecma262/#sec-runtime-semantics-definemethod
  282. /// </summary>
  283. internal static Record DefineMethod<T>(this T m, ObjectInstance obj, ObjectInstance? functionPrototype = null) where T : IProperty
  284. {
  285. var engine = obj.Engine;
  286. var propKey = TypeConverter.ToPropertyKey(m.GetKey(engine));
  287. var intrinsics = engine.Realm.Intrinsics;
  288. var runningExecutionContext = engine.ExecutionContext;
  289. var env = runningExecutionContext.LexicalEnvironment;
  290. var privateEnv = runningExecutionContext.PrivateEnvironment;
  291. var prototype = functionPrototype ?? intrinsics.Function.PrototypeObject;
  292. var function = m.Value as IFunction;
  293. if (function is null)
  294. {
  295. ExceptionHelper.ThrowSyntaxError(engine.Realm);
  296. }
  297. var definition = new JintFunctionDefinition(function);
  298. var closure = intrinsics.Function.OrdinaryFunctionCreate(prototype, definition, definition.ThisMode, env, privateEnv);
  299. closure.MakeMethod(obj);
  300. return new Record(propKey, closure);
  301. }
  302. internal static void GetImportEntries(this ImportDeclaration import, List<ImportEntry> importEntries, HashSet<ModuleRequest> requestedModules)
  303. {
  304. var source = import.Source.Value;
  305. var specifiers = import.Specifiers;
  306. var attributes = GetAttributes(import.Attributes);
  307. requestedModules.Add(new ModuleRequest(source, attributes));
  308. foreach (var specifier in specifiers)
  309. {
  310. switch (specifier)
  311. {
  312. case ImportNamespaceSpecifier namespaceSpecifier:
  313. importEntries.Add(new ImportEntry(new ModuleRequest(source, attributes), "*", namespaceSpecifier.Local.GetModuleKey()));
  314. break;
  315. case ImportSpecifier importSpecifier:
  316. importEntries.Add(new ImportEntry(new ModuleRequest(source, attributes), importSpecifier.Imported.GetModuleKey(), importSpecifier.Local.GetModuleKey()!));
  317. break;
  318. case ImportDefaultSpecifier defaultSpecifier:
  319. importEntries.Add(new ImportEntry(new ModuleRequest(source, attributes), "default", defaultSpecifier.Local.GetModuleKey()));
  320. break;
  321. }
  322. }
  323. }
  324. private static ModuleImportAttribute[] GetAttributes(in NodeList<ImportAttribute> importAttributes)
  325. {
  326. if (importAttributes.Count == 0)
  327. {
  328. return Array.Empty<ModuleImportAttribute>();
  329. }
  330. var attributes = new ModuleImportAttribute[importAttributes.Count];
  331. for (var i = 0; i < importAttributes.Count; i++)
  332. {
  333. var attribute = importAttributes[i];
  334. var key = attribute.Key is Identifier identifier ? identifier.Name : ((StringLiteral) attribute.Key).Value;
  335. attributes[i] = new ModuleImportAttribute(key, attribute.Value.Value);
  336. }
  337. return attributes;
  338. }
  339. internal static void GetExportEntries(this ExportDeclaration export, List<ExportEntry> exportEntries, HashSet<ModuleRequest> requestedModules)
  340. {
  341. switch (export)
  342. {
  343. case ExportDefaultDeclaration defaultDeclaration:
  344. GetExportEntries(true, defaultDeclaration.Declaration, exportEntries);
  345. break;
  346. case ExportAllDeclaration allDeclaration:
  347. //Note: there is a pending PR for Esprima to support exporting an imported modules content as a namespace i.e. 'export * as ns from "mod"'
  348. requestedModules.Add(new ModuleRequest(allDeclaration.Source.Value, []));
  349. exportEntries.Add(new(allDeclaration.Exported?.GetModuleKey(), new ModuleRequest(allDeclaration.Source.Value, []), "*", null));
  350. break;
  351. case ExportNamedDeclaration namedDeclaration:
  352. ref readonly var specifiers = ref namedDeclaration.Specifiers;
  353. if (specifiers.Count == 0)
  354. {
  355. ModuleRequest? moduleRequest = namedDeclaration.Source != null
  356. ? new ModuleRequest(namedDeclaration.Source.Value, [])
  357. : null;
  358. GetExportEntries(false, namedDeclaration.Declaration!, exportEntries, moduleRequest);
  359. }
  360. else
  361. {
  362. for (var i = 0; i < specifiers.Count; i++)
  363. {
  364. var specifier = specifiers[i];
  365. if (namedDeclaration.Source != null)
  366. {
  367. exportEntries.Add(new(specifier.Exported.GetModuleKey(), new ModuleRequest(namedDeclaration.Source.Value, []), specifier.Local.GetModuleKey(), null));
  368. }
  369. else
  370. {
  371. exportEntries.Add(new(specifier.Exported.GetModuleKey(), null, null, specifier.Local.GetModuleKey()));
  372. }
  373. }
  374. }
  375. if (namedDeclaration.Source is not null)
  376. {
  377. requestedModules.Add(new ModuleRequest(namedDeclaration.Source.Value, []));
  378. }
  379. break;
  380. }
  381. }
  382. private static void GetExportEntries(bool defaultExport, StatementOrExpression declaration, List<ExportEntry> exportEntries, ModuleRequest? moduleRequest = null)
  383. {
  384. var names = GetExportNames(declaration);
  385. if (names.Count == 0)
  386. {
  387. if (defaultExport)
  388. {
  389. exportEntries.Add(new("default", null, null, "*default*"));
  390. }
  391. }
  392. else
  393. {
  394. for (var i = 0; i < names.Count; i++)
  395. {
  396. var name = names[i];
  397. var exportName = defaultExport ? "default" : name.Name;
  398. exportEntries.Add(new(exportName, moduleRequest, null, name));
  399. }
  400. }
  401. }
  402. private static List<Key> GetExportNames(StatementOrExpression declaration)
  403. {
  404. var result = new List<Key>();
  405. switch (declaration)
  406. {
  407. case FunctionDeclaration functionDeclaration:
  408. var funcName = functionDeclaration.Id?.Name;
  409. if (funcName is not null)
  410. {
  411. result.Add(funcName);
  412. }
  413. break;
  414. case ClassDeclaration classDeclaration:
  415. var className = classDeclaration.Id?.Name;
  416. if (className is not null)
  417. {
  418. result.Add(className);
  419. }
  420. break;
  421. case VariableDeclaration variableDeclaration:
  422. variableDeclaration.GetBoundNames(result);
  423. break;
  424. }
  425. return result;
  426. }
  427. private static string GetModuleKey(this Expression expression)
  428. {
  429. return (expression as Identifier)?.Name ?? ((StringLiteral) expression).Value;
  430. }
  431. internal readonly record struct Record(JsValue Key, ScriptFunction Closure);
  432. /// <summary>
  433. /// Creates a dummy node that can be used when only location available and node is required.
  434. /// </summary>
  435. internal static Node CreateLocationNode(in SourceLocation location)
  436. {
  437. return new MinimalSyntaxElement(location);
  438. }
  439. /// <summary>
  440. /// https://tc39.es/ecma262/#sec-static-semantics-allprivateidentifiersvalid
  441. /// </summary>
  442. internal static void AllPrivateIdentifiersValid(this Script script, Realm realm, HashSet<PrivateIdentifier>? privateIdentifiers)
  443. {
  444. var validator = new PrivateIdentifierValidator(realm, privateIdentifiers);
  445. validator.Visit(script);
  446. }
  447. private sealed class MinimalSyntaxElement : Node
  448. {
  449. public MinimalSyntaxElement(in SourceLocation location) : base(NodeType.Unknown)
  450. {
  451. Location = location;
  452. }
  453. protected override IEnumerator<Node>? GetChildNodes() => throw new NotImplementedException();
  454. protected override object? Accept(AstVisitor visitor) => throw new NotImplementedException();
  455. }
  456. private sealed class PrivateIdentifierValidator : AstVisitor
  457. {
  458. private readonly Realm _realm;
  459. private HashSet<PrivateIdentifier>? _privateNames;
  460. public PrivateIdentifierValidator(Realm realm, HashSet<PrivateIdentifier>? privateNames)
  461. {
  462. _realm = realm;
  463. _privateNames = privateNames;
  464. }
  465. protected override object VisitPrivateIdentifier(PrivateIdentifier privateIdentifier)
  466. {
  467. if (_privateNames is null || !_privateNames.Contains(privateIdentifier))
  468. {
  469. Throw(_realm, privateIdentifier);
  470. }
  471. return privateIdentifier;
  472. }
  473. protected override object VisitClassBody(ClassBody classBody)
  474. {
  475. var oldList = _privateNames;
  476. _privateNames = new HashSet<PrivateIdentifier>(PrivateIdentifierNameComparer._instance);
  477. classBody.PrivateBoundIdentifiers(_privateNames);
  478. base.VisitClassBody(classBody);
  479. _privateNames = oldList;
  480. return classBody;
  481. }
  482. [MethodImpl(MethodImplOptions.NoInlining)]
  483. private static void Throw(Realm r, PrivateIdentifier id)
  484. {
  485. ExceptionHelper.ThrowSyntaxError(r, $"Private field '#{id.Name}' must be declared in an enclosing class");
  486. }
  487. }
  488. }
  489. }