Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

require-atomic-updates.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /**
  2. * @fileoverview disallow assignments that can lead to race conditions due to usage of `await` or `yield`
  3. * @author Teddy Katz
  4. */
  5. "use strict";
  6. const astUtils = require("../util/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. module.exports = {
  11. meta: {
  12. type: "problem",
  13. docs: {
  14. description: "disallow assignments that can lead to race conditions due to usage of `await` or `yield`",
  15. category: "Possible Errors",
  16. recommended: false,
  17. url: "https://eslint.org/docs/rules/require-atomic-updates"
  18. },
  19. fixable: null,
  20. schema: [],
  21. messages: {
  22. nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`."
  23. }
  24. },
  25. create(context) {
  26. const sourceCode = context.getSourceCode();
  27. const identifierToSurroundingFunctionMap = new WeakMap();
  28. const expressionsByCodePathSegment = new Map();
  29. //----------------------------------------------------------------------
  30. // Helpers
  31. //----------------------------------------------------------------------
  32. const resolvedVariableCache = new WeakMap();
  33. /**
  34. * Gets the variable scope around this variable reference
  35. * @param {ASTNode} identifier An `Identifier` AST node
  36. * @returns {Scope|null} An escope Scope
  37. */
  38. function getScope(identifier) {
  39. for (let currentNode = identifier; currentNode; currentNode = currentNode.parent) {
  40. const scope = sourceCode.scopeManager.acquire(currentNode, true);
  41. if (scope) {
  42. return scope;
  43. }
  44. }
  45. return null;
  46. }
  47. /**
  48. * Resolves a given identifier to a given scope
  49. * @param {ASTNode} identifier An `Identifier` AST node
  50. * @param {Scope} scope An escope Scope
  51. * @returns {Variable|null} An escope Variable corresponding to the given identifier
  52. */
  53. function resolveVariableInScope(identifier, scope) {
  54. return scope.variables.find(variable => variable.name === identifier.name) ||
  55. (scope.upper ? resolveVariableInScope(identifier, scope.upper) : null);
  56. }
  57. /**
  58. * Resolves an identifier to a variable
  59. * @param {ASTNode} identifier An identifier node
  60. * @returns {Variable|null} The escope Variable that uses this identifier
  61. */
  62. function resolveVariable(identifier) {
  63. if (!resolvedVariableCache.has(identifier)) {
  64. const surroundingScope = getScope(identifier);
  65. if (surroundingScope) {
  66. resolvedVariableCache.set(identifier, resolveVariableInScope(identifier, surroundingScope));
  67. } else {
  68. resolvedVariableCache.set(identifier, null);
  69. }
  70. }
  71. return resolvedVariableCache.get(identifier);
  72. }
  73. /**
  74. * Checks if an expression is a variable that can only be observed within the given function.
  75. * @param {ASTNode} expression The expression to check
  76. * @param {ASTNode} surroundingFunction The function node
  77. * @returns {boolean} `true` if the expression is a variable which is local to the given function, and is never
  78. * referenced in a closure.
  79. */
  80. function isLocalVariableWithoutEscape(expression, surroundingFunction) {
  81. if (expression.type !== "Identifier") {
  82. return false;
  83. }
  84. const variable = resolveVariable(expression);
  85. if (!variable) {
  86. return false;
  87. }
  88. return variable.references.every(reference => identifierToSurroundingFunctionMap.get(reference.identifier) === surroundingFunction) &&
  89. variable.defs.every(def => identifierToSurroundingFunctionMap.get(def.name) === surroundingFunction);
  90. }
  91. /**
  92. * Reports an AssignmentExpression node that has a non-atomic update
  93. * @param {ASTNode} assignmentExpression The assignment that is potentially unsafe
  94. * @returns {void}
  95. */
  96. function reportAssignment(assignmentExpression) {
  97. context.report({
  98. node: assignmentExpression,
  99. messageId: "nonAtomicUpdate",
  100. data: {
  101. value: sourceCode.getText(assignmentExpression.left)
  102. }
  103. });
  104. }
  105. const alreadyReportedAssignments = new WeakSet();
  106. class AssignmentTrackerState {
  107. constructor({ openAssignmentsWithoutReads = new Set(), openAssignmentsWithReads = new Set() } = {}) {
  108. this.openAssignmentsWithoutReads = openAssignmentsWithoutReads;
  109. this.openAssignmentsWithReads = openAssignmentsWithReads;
  110. }
  111. copy() {
  112. return new AssignmentTrackerState({
  113. openAssignmentsWithoutReads: new Set(this.openAssignmentsWithoutReads),
  114. openAssignmentsWithReads: new Set(this.openAssignmentsWithReads)
  115. });
  116. }
  117. merge(other) {
  118. const initialAssignmentsWithoutReadsCount = this.openAssignmentsWithoutReads.size;
  119. const initialAssignmentsWithReadsCount = this.openAssignmentsWithReads.size;
  120. other.openAssignmentsWithoutReads.forEach(assignment => this.openAssignmentsWithoutReads.add(assignment));
  121. other.openAssignmentsWithReads.forEach(assignment => this.openAssignmentsWithReads.add(assignment));
  122. return this.openAssignmentsWithoutReads.size > initialAssignmentsWithoutReadsCount ||
  123. this.openAssignmentsWithReads.size > initialAssignmentsWithReadsCount;
  124. }
  125. enterAssignment(assignmentExpression) {
  126. (assignmentExpression.operator === "=" ? this.openAssignmentsWithoutReads : this.openAssignmentsWithReads).add(assignmentExpression);
  127. }
  128. exitAssignment(assignmentExpression) {
  129. this.openAssignmentsWithoutReads.delete(assignmentExpression);
  130. this.openAssignmentsWithReads.delete(assignmentExpression);
  131. }
  132. exitAwaitOrYield(node, surroundingFunction) {
  133. return [...this.openAssignmentsWithReads]
  134. .filter(assignment => !isLocalVariableWithoutEscape(assignment.left, surroundingFunction))
  135. .forEach(assignment => {
  136. if (!alreadyReportedAssignments.has(assignment)) {
  137. reportAssignment(assignment);
  138. alreadyReportedAssignments.add(assignment);
  139. }
  140. });
  141. }
  142. exitIdentifierOrMemberExpression(node) {
  143. [...this.openAssignmentsWithoutReads]
  144. .filter(assignment => (
  145. assignment.left !== node &&
  146. assignment.left.type === node.type &&
  147. astUtils.equalTokens(assignment.left, node, sourceCode)
  148. ))
  149. .forEach(assignment => {
  150. this.openAssignmentsWithoutReads.delete(assignment);
  151. this.openAssignmentsWithReads.add(assignment);
  152. });
  153. }
  154. }
  155. /**
  156. * If the control flow graph of a function enters an assignment expression, then does the
  157. * both of the following steps in order (possibly with other steps in between) before exiting the
  158. * assignment expression, then the assignment might be using an outdated value.
  159. * 1. Enters a read of the variable or property assigned in the expression (not necessary if operator assignment is used)
  160. * 2. Exits an `await` or `yield` expression
  161. *
  162. * This function checks for the outdated values and reports them.
  163. * @param {CodePathSegment} codePathSegment The current code path segment to traverse
  164. * @param {ASTNode} surroundingFunction The function node containing the code path segment
  165. * @returns {void}
  166. */
  167. function findOutdatedReads(
  168. codePathSegment,
  169. surroundingFunction,
  170. {
  171. stateBySegmentStart = new WeakMap(),
  172. stateBySegmentEnd = new WeakMap()
  173. } = {}
  174. ) {
  175. if (!stateBySegmentStart.has(codePathSegment)) {
  176. stateBySegmentStart.set(codePathSegment, new AssignmentTrackerState());
  177. }
  178. const currentState = stateBySegmentStart.get(codePathSegment).copy();
  179. expressionsByCodePathSegment.get(codePathSegment).forEach(({ entering, node }) => {
  180. if (node.type === "AssignmentExpression") {
  181. if (entering) {
  182. currentState.enterAssignment(node);
  183. } else {
  184. currentState.exitAssignment(node);
  185. }
  186. } else if (!entering && (node.type === "AwaitExpression" || node.type === "YieldExpression")) {
  187. currentState.exitAwaitOrYield(node, surroundingFunction);
  188. } else if (!entering && (node.type === "Identifier" || node.type === "MemberExpression")) {
  189. currentState.exitIdentifierOrMemberExpression(node);
  190. }
  191. });
  192. stateBySegmentEnd.set(codePathSegment, currentState);
  193. codePathSegment.nextSegments.forEach(nextSegment => {
  194. if (stateBySegmentStart.has(nextSegment)) {
  195. if (!stateBySegmentStart.get(nextSegment).merge(currentState)) {
  196. /*
  197. * This segment has already been processed with the given set of inputs;
  198. * no need to do it again. After no new state is available to process
  199. * for any control flow segment in the graph, the analysis reaches a fixpoint and
  200. * traversal stops.
  201. */
  202. return;
  203. }
  204. } else {
  205. stateBySegmentStart.set(nextSegment, currentState.copy());
  206. }
  207. findOutdatedReads(
  208. nextSegment,
  209. surroundingFunction,
  210. { stateBySegmentStart, stateBySegmentEnd }
  211. );
  212. });
  213. }
  214. //----------------------------------------------------------------------
  215. // Public
  216. //----------------------------------------------------------------------
  217. const currentCodePathSegmentStack = [];
  218. let currentCodePathSegment = null;
  219. const functionStack = [];
  220. return {
  221. onCodePathStart() {
  222. currentCodePathSegmentStack.push(currentCodePathSegment);
  223. },
  224. onCodePathEnd(codePath, node) {
  225. currentCodePathSegment = currentCodePathSegmentStack.pop();
  226. if (astUtils.isFunction(node) && (node.async || node.generator)) {
  227. findOutdatedReads(codePath.initialSegment, node);
  228. }
  229. },
  230. onCodePathSegmentStart(segment) {
  231. currentCodePathSegment = segment;
  232. expressionsByCodePathSegment.set(segment, []);
  233. },
  234. "AssignmentExpression, Identifier, MemberExpression, AwaitExpression, YieldExpression"(node) {
  235. expressionsByCodePathSegment.get(currentCodePathSegment).push({ entering: true, node });
  236. },
  237. "AssignmentExpression, Identifier, MemberExpression, AwaitExpression, YieldExpression:exit"(node) {
  238. expressionsByCodePathSegment.get(currentCodePathSegment).push({ entering: false, node });
  239. },
  240. ":function"(node) {
  241. functionStack.push(node);
  242. },
  243. ":function:exit"() {
  244. functionStack.pop();
  245. },
  246. Identifier(node) {
  247. if (functionStack.length) {
  248. identifierToSurroundingFunctionMap.set(node, functionStack[functionStack.length - 1]);
  249. }
  250. }
  251. };
  252. }
  253. };