Skip to content

Commit 51b83db

Browse files
authored
Merge pull request #19579 from Napalys/js/dom_property_access
JS: Improve `useless-expression` query to avoid duplicate alerts on compound expressions
2 parents 791369d + e465811 commit 51b83db

File tree

6 files changed

+47
-3
lines changed

6 files changed

+47
-3
lines changed

javascript/ql/lib/Expressions/ExprHasNoEffect.qll

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ predicate inVoidContext(Expr e) {
2222
)
2323
)
2424
or
25+
// propagate void context through parenthesized expressions
26+
inVoidContext(e.getParent().(ParExpr))
27+
or
2528
exists(SeqExpr seq, int i, int n |
2629
e = seq.getOperand(i) and
2730
n = seq.getNumOperands()
@@ -129,6 +132,19 @@ predicate noSideEffects(Expr e) {
129132
)
130133
}
131134

135+
/**
136+
* Holds if `e` is a compound expression that may contain sub-expressions with side effects.
137+
* We should not flag these directly as useless since we want to flag only the innermost
138+
* expressions that actually have no effect.
139+
*/
140+
predicate isCompoundExpression(Expr e) {
141+
e instanceof LogicalBinaryExpr
142+
or
143+
e instanceof SeqExpr
144+
or
145+
e instanceof ParExpr
146+
}
147+
132148
/**
133149
* Holds if the expression `e` should be reported as having no effect.
134150
*/
@@ -145,6 +161,7 @@ predicate hasNoEffect(Expr e) {
145161
not isDeclaration(e) and
146162
// exclude DOM properties, which sometimes have magical auto-update properties
147163
not isDomProperty(e.(PropAccess).getPropertyName()) and
164+
not isCompoundExpression(e) and
148165
// exclude xUnit.js annotations
149166
not e instanceof XUnitAnnotation and
150167
// exclude common patterns that are most likely intentional
@@ -157,7 +174,17 @@ predicate hasNoEffect(Expr e) {
157174
not exists(fe.getName())
158175
) and
159176
// exclude block-level flow type annotations. For example: `(name: empty)`.
160-
not e.(ParExpr).getExpression().getLastToken().getNextToken().getValue() = ":" and
177+
not exists(ParExpr parent |
178+
e.getParent() = parent and
179+
e.getLastToken().getNextToken().getValue() = ":"
180+
) and
181+
// exclude expressions that are part of a conditional expression
182+
not exists(ConditionalExpr cond | e = cond.getABranch() |
183+
e instanceof NullLiteral or
184+
e.(GlobalVarAccess).getName() = "undefined" or
185+
e.(NumberLiteral).getIntValue() = 0 or
186+
e instanceof VoidExpr
187+
) and
161188
// exclude the first statement of a try block
162189
not e = any(TryStmt stmt).getBody().getStmt(0).(ExprStmt).getExpr() and
163190
// exclude expressions that are alone in a file, and file doesn't contain a function.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `js/useless-expression` query now correctly flags only the innermost expressions with no effect, avoiding duplicate alerts on compound expressions.

javascript/ql/test/query-tests/Expressions/ExprHasNoEffect/ExprHasNoEffect.expected

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
| dom.js:2:33:2:50 | a.clientTop === !0 | This expression has no effect. |
12
| try.js:22:9:22:26 | x.ordinaryProperty | This expression has no effect. |
23
| tst2.js:2:4:2:4 | 0 | This expression has no effect. |
34
| tst.js:3:1:3:2 | 23 | This expression has no effect. |
@@ -11,4 +12,4 @@
1112
| tst.js:50:3:50:36 | new Err ... age(e)) | This expression has no effect. |
1213
| tst.js:61:2:61:20 | o.trivialNonGetter1 | This expression has no effect. |
1314
| tst.js:77:24:77:24 | o | This expression has no effect. |
14-
| uselessfn.js:1:1:1:26 | (functi ... .");\\n}) | This expression has no effect. |
15+
| uselessfn.js:1:2:1:26 | functio ... d.");\\n} | This expression has no effect. |
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function f(){
2+
a.clientTop && a.clientTop, a.clientTop === !0; //$Alert
3+
a && a.clientTop;
4+
a.clientTop, a.clientTop;
5+
if(a) return a.clientTop && a.clientTop, a.clientTop === !0;
6+
if(b) return b && (b.clientTop, b.clientTop && b.clientTop), null;
7+
}

javascript/ql/test/query-tests/Expressions/ExprHasNoEffect/tst.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,9 @@ function g() {
7979

8080
consume(testSomeCondition() ? o :
8181
doSomethingDangerous());
82+
83+
("release" === isRelease() ? warning() : null);
84+
"release" === isRelease() ? warning() : null;
85+
"release" === isRelease() ? warning() : 0;
86+
"release" === isRelease() ? warning() : undefined;
8287
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
(function f() { // $ Alert
22
console.log("I'm never called.");
3-
})
3+
})

0 commit comments

Comments
 (0)