]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/require-atomic-updates.js
248b0eb11d00d59a1a49438e477a9a8bf2408721
2 * @fileoverview disallow assignments that can lead to race conditions due to usage of `await` or `yield`
4 * @author Toru Nagashima
9 * Make the map from identifiers to each reference.
10 * @param {escope.Scope} scope The scope to get references.
11 * @param {Map<Identifier, escope.Reference>} [outReferenceMap] The map from identifier nodes to each reference object.
12 * @returns {Map<Identifier, escope.Reference>} `referenceMap`.
14 function createReferenceMap(scope
, outReferenceMap
= new Map()) {
15 for (const reference
of scope
.references
) {
16 if (reference
.resolved
=== null) {
20 outReferenceMap
.set(reference
.identifier
, reference
);
22 for (const childScope
of scope
.childScopes
) {
23 if (childScope
.type
!== "function") {
24 createReferenceMap(childScope
, outReferenceMap
);
28 return outReferenceMap
;
32 * Get `reference.writeExpr` of a given reference.
33 * If it's the read reference of MemberExpression in LHS, returns RHS in order to address `a.b = await a`
34 * @param {escope.Reference} reference The reference to get.
35 * @returns {Expression|null} The `reference.writeExpr`.
37 function getWriteExpr(reference
) {
38 if (reference
.writeExpr
) {
39 return reference
.writeExpr
;
41 let node
= reference
.identifier
;
44 const t
= node
.parent
.type
;
46 if (t
=== "AssignmentExpression" && node
.parent
.left
=== node
) {
47 return node
.parent
.right
;
49 if (t
=== "MemberExpression" && node
.parent
.object
=== node
) {
61 * Checks if an expression is a variable that can only be observed within the given function.
62 * @param {Variable|null} variable The variable to check
63 * @param {boolean} isMemberAccess If `true` then this is a member access.
64 * @returns {boolean} `true` if the variable is local to the given function, and is never referenced in a closure.
66 function isLocalVariableWithoutEscape(variable
, isMemberAccess
) {
68 return false; // A global variable which was not defined.
71 // If the reference is a property access and the variable is a parameter, it handles the variable is not local.
72 if (isMemberAccess
&& variable
.defs
.some(d
=> d
.type
=== "Parameter")) {
76 const functionScope
= variable
.scope
.variableScope
;
78 return variable
.references
.every(reference
=>
79 reference
.from.variableScope
=== functionScope
);
83 * Represents segment information.
87 this.info
= new WeakMap();
91 * Initialize the segment information.
92 * @param {PathSegment} segment The segment to initialize.
96 const outdatedReadVariables
= new Set();
97 const freshReadVariables
= new Set();
99 for (const prevSegment
of segment
.prevSegments
) {
100 const info
= this.info
.get(prevSegment
);
103 info
.outdatedReadVariables
.forEach(Set
.prototype.add
, outdatedReadVariables
);
104 info
.freshReadVariables
.forEach(Set
.prototype.add
, freshReadVariables
);
108 this.info
.set(segment
, { outdatedReadVariables
, freshReadVariables
});
112 * Mark a given variable as read on given segments.
113 * @param {PathSegment[]} segments The segments that it read the variable on.
114 * @param {Variable} variable The variable to be read.
117 markAsRead(segments
, variable
) {
118 for (const segment
of segments
) {
119 const info
= this.info
.get(segment
);
122 info
.freshReadVariables
.add(variable
);
124 // If a variable is freshly read again, then it's no more out-dated.
125 info
.outdatedReadVariables
.delete(variable
);
131 * Move `freshReadVariables` to `outdatedReadVariables`.
132 * @param {PathSegment[]} segments The segments to process.
135 makeOutdated(segments
) {
136 for (const segment
of segments
) {
137 const info
= this.info
.get(segment
);
140 info
.freshReadVariables
.forEach(Set
.prototype.add
, info
.outdatedReadVariables
);
141 info
.freshReadVariables
.clear();
147 * Check if a given variable is outdated on the current segments.
148 * @param {PathSegment[]} segments The current segments.
149 * @param {Variable} variable The variable to check.
150 * @returns {boolean} `true` if the variable is outdated on the segments.
152 isOutdated(segments
, variable
) {
153 for (const segment
of segments
) {
154 const info
= this.info
.get(segment
);
156 if (info
&& info
.outdatedReadVariables
.has(variable
)) {
164 //------------------------------------------------------------------------------
166 //------------------------------------------------------------------------------
173 description
: "disallow assignments that can lead to race conditions due to usage of `await` or `yield`",
175 url
: "https://eslint.org/docs/rules/require-atomic-updates"
188 additionalProperties
: false
192 nonAtomicUpdate
: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`.",
193 nonAtomicObjectUpdate
: "Possible race condition: `{{value}}` might be assigned based on an outdated state of `{{object}}`."
198 const allowProperties
= !!context
.options
[0] && context
.options
[0].allowProperties
;
200 const sourceCode
= context
.getSourceCode();
201 const assignmentReferences
= new Map();
202 const segmentInfo
= new SegmentInfo();
206 onCodePathStart(codePath
) {
207 const scope
= context
.getScope();
209 scope
.type
=== "function" &&
210 (scope
.block
.async
|| scope
.block
.generator
);
215 referenceMap
: shouldVerify
? createReferenceMap(scope
) : null
222 // Initialize the segment information.
223 onCodePathSegmentStart(segment
) {
224 segmentInfo
.initialize(segment
);
227 // Handle references to prepare verification.
229 const { codePath
, referenceMap
} = stack
;
230 const reference
= referenceMap
&& referenceMap
.get(node
);
232 // Ignore if this is not a valid variable reference.
236 const variable
= reference
.resolved
;
237 const writeExpr
= getWriteExpr(reference
);
238 const isMemberAccess
= reference
.identifier
.parent
.type
=== "MemberExpression";
240 // Add a fresh read variable.
241 if (reference
.isRead() && !(writeExpr
&& writeExpr
.parent
.operator
=== "=")) {
242 segmentInfo
.markAsRead(codePath
.currentSegments
, variable
);
246 * Register the variable to verify after ESLint traversed the `writeExpr` node
247 * if this reference is an assignment to a variable which is referred from other closure.
250 writeExpr
.parent
.right
=== writeExpr
&& // ← exclude variable declarations.
251 !isLocalVariableWithoutEscape(variable
, isMemberAccess
)
253 let refs
= assignmentReferences
.get(writeExpr
);
257 assignmentReferences
.set(writeExpr
, refs
);
260 refs
.push(reference
);
265 * Verify assignments.
266 * If the reference exists in `outdatedReadVariables` list, report it.
268 ":expression:exit"(node
) {
269 const { codePath
, referenceMap
} = stack
;
271 // referenceMap exists if this is in a resumable function scope.
276 // Mark the read variables on this code path as outdated.
277 if (node
.type
=== "AwaitExpression" || node
.type
=== "YieldExpression") {
278 segmentInfo
.makeOutdated(codePath
.currentSegments
);
282 const references
= assignmentReferences
.get(node
);
285 assignmentReferences
.delete(node
);
287 for (const reference
of references
) {
288 const variable
= reference
.resolved
;
290 if (segmentInfo
.isOutdated(codePath
.currentSegments
, variable
)) {
291 if (node
.parent
.left
=== reference
.identifier
) {
294 messageId
: "nonAtomicUpdate",
299 } else if (!allowProperties
) {
302 messageId
: "nonAtomicObjectUpdate",
304 value
: sourceCode
.getText(node
.parent
.left
),
305 object
: variable
.name