Skip to content

Commit 83e0f3b

Browse files
author
Max Schaefer
authored
Merge pull request #946 from esben-semmle/js/captured-nodes-query-and-type-inference-1
JS: Captured Nodes, type inference + a query
2 parents 6cafe22 + 9511bdf commit 83e0f3b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+742
-58
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
| Incomplete URL substring sanitization | correctness, security, external/cwe/cwe-020 | Highlights URL sanitizers that are likely to be incomplete, indicating a violation of [CWE-020](https://cwe.mitre.org/data/definitions/20.html). Results shown on LGTM by default. |
3232
| Incorrect suffix check (`js/incorrect-suffix-check`) | correctness, security, external/cwe/cwe-020 | Highlights error-prone suffix checks based on `indexOf`, indicating a potential violation of [CWE-20](https://cwe.mitre.org/data/definitions/20.html). Results are shown on LGTM by default. |
3333
| Loop iteration skipped due to shifting (`js/loop-iteration-skipped-due-to-shifting`) | correctness | Highlights code that removes an element from an array while iterating over it, causing the loop to skip over some elements. Results are shown on LGTM by default. |
34-
| Unbound event handler receiver (`js/unbound-event-handler-receiver`) | Fewer false positive results | Additional ways that class methods can be bound are recognized. |
34+
| Unused property (`js/unused-property`) | maintainability | Highlights properties that are unused. Results are shown on LGTM by default. |
3535
| Useless comparison test (`js/useless-comparison-test`) | correctness | Highlights code that is unreachable due to a numeric comparison that is always true or always false. Results are shown on LGTM by default. |
3636

3737
## Changes to existing queries
@@ -44,9 +44,10 @@
4444
| Insecure randomness | More results | This rule now flags insecure uses of `crypto.pseudoRandomBytes`. |
4545
| Reflected cross-site scripting | Fewer false-positive results. | This rule now recognizes custom sanitizers. |
4646
| Stored cross-site scripting | Fewer false-positive results. | This rule now recognizes custom sanitizers. |
47+
| Unbound event handler receiver (`js/unbound-event-handler-receiver`) | Fewer false positive results | Additional ways that class methods can be bound are recognized. |
4748
| Uncontrolled data used in network request | More results | This rule now recognizes host values that are vulnerable to injection. |
4849
| Unused parameter | Fewer false-positive results | This rule no longer flags parameters with leading underscore. |
49-
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements, and no longer flags variables with leading underscore. |
50+
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements, no longer flags variables with leading underscore, and no longer flags variables in dead code. |
5051
| Uncontrolled data used in path expression | Fewer false-positive results | This rule now recognizes the Express `root` option, which prevents path traversal. |
5152
| Unneeded defensive code | More true-positive results, fewer false-positive results. | This rule now recognizes additional defensive code patterns. |
5253
| Useless conditional | Fewer results | Additional defensive coding patterns are now ignored. |

javascript/config/suites/javascript/maintainability-more

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
+ semmlecode-javascript-queries/Declarations/DeadStoreOfProperty.ql: /Maintainability/Declarations
66
+ semmlecode-javascript-queries/Declarations/DuplicateVarDecl.ql: /Maintainability/Declarations
77
+ semmlecode-javascript-queries/Declarations/UnusedParameter.ql: /Maintainability/Declarations
8+
+ semmlecode-javascript-queries/Declarations/UnusedProperty.ql: /Maintainability/Declarations
89
+ semmlecode-javascript-queries/Declarations/UnusedVariable.ql: /Maintainability/Declarations
910
+ semmlecode-javascript-queries/Expressions/UnneededDefensiveProgramming.ql: /Maintainability/Expressions
1011
+ semmlecode-javascript-queries/LanguageFeatures/ArgumentsCallerCallee.ql: /Maintainability/Language Features

javascript/ql/src/Declarations/UnusedParameter.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* This library contains the majority of the 'js/unused-parameter' query implementation.
2+
* Provides classes and predicates for the 'js/unused-parameter' query.
33
*
44
* In order to suppress alerts that are similar to the 'js/unused-parameter' alerts,
55
* `isAnAccidentallyUnusedParameter` should be used since it holds iff that alert is active.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Unused object properties make code harder to maintain and use. Clients that are unaware that a
8+
property is unused may perform nontrivial computations to compute a value that is ultimately
9+
unused.
10+
</p>
11+
12+
</overview>
13+
<recommendation>
14+
<p>Remove the unused property.</p>
15+
16+
</recommendation>
17+
<example>
18+
19+
<p>
20+
In this code, the function <code>f</code> initializes a property <code>prop_a</code> with a
21+
call to the function <code>expensiveComputation</code>, but later on this property is never read.
22+
Removing <code>prop</code> would improve code quality and performance.
23+
</p>
24+
25+
<sample src="examples/UnusedProperty.js" />
26+
27+
</example>
28+
<references>
29+
30+
<li>Coding Horror: <a href="http://blog.codinghorror.com/code-smells/">Code Smells</a>.</li>
31+
32+
33+
</references>
34+
</qhelp>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* @name Unused property
3+
* @description Unused properties may be a symptom of a bug and should be examined carefully.
4+
* @kind problem
5+
* @problem.severity recommendation
6+
* @id js/unused-property
7+
* @tags maintainability
8+
* @precision high
9+
*/
10+
11+
import javascript
12+
import semmle.javascript.dataflow.LocalObjects
13+
import UnusedVariable
14+
import UnusedParameter
15+
import Expressions.ExprHasNoEffect
16+
17+
predicate hasUnknownPropertyRead(LocalObject obj) {
18+
// dynamic reads
19+
exists(DataFlow::PropRead r | obj.getAPropertyRead() = r | not exists(r.getPropertyName()))
20+
or
21+
// reflective reads
22+
obj.flowsToExpr(any(EnhancedForLoop l).getIterationDomain())
23+
or
24+
obj.flowsToExpr(any(InExpr l).getRightOperand())
25+
or
26+
obj.flowsToExpr(any(SpreadElement e).getOperand())
27+
or
28+
exists(obj.getAPropertyRead("hasOwnProperty"))
29+
or
30+
exists(obj.getAPropertyRead("propertyIsEnumerable"))
31+
}
32+
33+
/**
34+
* Holds if `obj` flows to an expression that must have a specific type.
35+
*/
36+
predicate flowsToTypeRestrictedExpression(LocalObject obj) {
37+
exists (Expr restricted, TypeExpr type |
38+
obj.flowsToExpr(restricted) and
39+
not type.isAny() |
40+
exists (TypeAssertion assertion |
41+
type = assertion.getTypeAnnotation() and
42+
restricted = assertion.getExpression()
43+
)
44+
or
45+
exists (BindingPattern v |
46+
type = v.getTypeAnnotation() and
47+
restricted = v.getAVariable().getAnAssignedExpr()
48+
)
49+
// no need to reason about writes to typed fields, captured nodes do not reach them
50+
)
51+
}
52+
53+
from DataFlow::PropWrite write, LocalObject obj, string name
54+
where
55+
write = obj.getAPropertyWrite(name) and
56+
not exists(obj.getAPropertyRead(name)) and
57+
// `obj` is the only base object for the write: it is not spurious
58+
not write.getBase().analyze().getAValue() != obj.analyze().getAValue() and
59+
not hasUnknownPropertyRead(obj) and
60+
// avoid reporting if the definition is unreachable
61+
write.getAstNode().getFirstControlFlowNode().getBasicBlock() instanceof ReachableBasicBlock and
62+
// avoid implicitly read properties
63+
not (
64+
name = "toString" or
65+
name = "valueOf" or
66+
name.matches("@@%") // @@iterator, for example
67+
) and
68+
// avoid flagging properties that a type system requires
69+
not flowsToTypeRestrictedExpression(obj) and
70+
// flagged by js/unused-local-variable
71+
not exists(UnusedLocal l | l.getAnAssignedExpr().getUnderlyingValue().flow() = obj) and
72+
// flagged by js/unused-parameter
73+
not exists(Parameter p | isAnAccidentallyUnusedParameter(p) |
74+
p.getDefault().getUnderlyingValue().flow() = obj
75+
) and
76+
// flagged by js/useless-expression
77+
not inVoidContext(obj.asExpr())
78+
select write, "Unused property " + name + "."

javascript/ql/src/Declarations/UnusedVariable.ql

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,7 @@
1010
*/
1111

1212
import javascript
13-
14-
/**
15-
* A local variable that is neither used nor exported, and is not a parameter
16-
* or a function name.
17-
*/
18-
class UnusedLocal extends LocalVariable {
19-
UnusedLocal() {
20-
not exists(getAnAccess()) and
21-
not exists(Parameter p | this = p.getAVariable()) and
22-
not exists(FunctionExpr fe | this = fe.getVariable()) and
23-
not exists(ClassExpr ce | this = ce.getVariable()) and
24-
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
25-
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
26-
// common convention: variables with leading underscore are intentionally unused
27-
getName().charAt(0) != "_"
28-
}
29-
}
13+
import UnusedVariable
3014

3115
/**
3216
* Holds if `v` is mentioned in a JSDoc comment in the same file, and that file
@@ -206,6 +190,10 @@ predicate unusedImports(ImportVarDeclProvider provider, string msg) {
206190

207191
from ASTNode sel, string msg
208192
where
209-
unusedNonImports(sel, msg) or
210-
unusedImports(sel, msg)
193+
(
194+
unusedNonImports(sel, msg) or
195+
unusedImports(sel, msg)
196+
) and
197+
// avoid reporting if the definition is unreachable
198+
sel.getFirstControlFlowNode().getBasicBlock() instanceof ReachableBasicBlock
211199
select sel, msg
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Provides classes and predicates for the 'js/unused-local-variable' query.
3+
*/
4+
5+
import javascript
6+
7+
/**
8+
* A local variable that is neither used nor exported, and is not a parameter
9+
* or a function name.
10+
*/
11+
class UnusedLocal extends LocalVariable {
12+
UnusedLocal() {
13+
not exists(getAnAccess()) and
14+
not exists(Parameter p | this = p.getAVariable()) and
15+
not exists(FunctionExpr fe | this = fe.getVariable()) and
16+
not exists(ClassExpr ce | this = ce.getVariable()) and
17+
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
18+
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
19+
// common convention: variables with leading underscore are intentionally unused
20+
getName().charAt(0) != "_"
21+
}
22+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function f() {
2+
var o = {
3+
prop_a: expensiveComputation(),
4+
prop_b: anotherComputation()
5+
};
6+
7+
return o.prop_b;
8+
}

javascript/ql/src/Expressions/ExprHasNoEffect.ql

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,7 @@ import javascript
1616
import DOMProperties
1717
import semmle.javascript.frameworks.xUnit
1818
import semmle.javascript.RestrictedLocations
19-
20-
/**
21-
* Holds if `e` appears in a syntactic context where its value is discarded.
22-
*/
23-
predicate inVoidContext(Expr e) {
24-
exists(ExprStmt parent |
25-
// e is a toplevel expression in an expression statement
26-
parent = e.getParent() and
27-
// but it isn't an HTML attribute or a configuration object
28-
not exists(TopLevel tl | tl = parent.getParent() |
29-
tl instanceof CodeInAttribute
30-
or
31-
// if the toplevel in its entirety is of the form `({ ... })`,
32-
// it is probably a configuration object (e.g., a require.js build configuration)
33-
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
34-
)
35-
)
36-
or
37-
exists(SeqExpr seq, int i, int n |
38-
e = seq.getOperand(i) and
39-
n = seq.getNumOperands()
40-
|
41-
i < n - 1 or inVoidContext(seq)
42-
)
43-
or
44-
exists(ForStmt stmt | e = stmt.getUpdate())
45-
or
46-
exists(ForStmt stmt | e = stmt.getInit() |
47-
// Allow the pattern `for(i; i < 10; i++)`
48-
not e instanceof VarAccess
49-
)
50-
or
51-
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
52-
}
19+
import ExprHasNoEffect
5320

5421
/**
5522
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Provides classes and predicates for the 'js/useless-expression' query.
3+
*/
4+
5+
import javascript
6+
7+
/**
8+
* Holds if `e` appears in a syntactic context where its value is discarded.
9+
*/
10+
predicate inVoidContext(Expr e) {
11+
exists(ExprStmt parent |
12+
// e is a toplevel expression in an expression statement
13+
parent = e.getParent() and
14+
// but it isn't an HTML attribute or a configuration object
15+
not exists(TopLevel tl | tl = parent.getParent() |
16+
tl instanceof CodeInAttribute
17+
or
18+
// if the toplevel in its entirety is of the form `({ ... })`,
19+
// it is probably a configuration object (e.g., a require.js build configuration)
20+
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
21+
)
22+
)
23+
or
24+
exists(SeqExpr seq, int i, int n |
25+
e = seq.getOperand(i) and
26+
n = seq.getNumOperands()
27+
|
28+
i < n - 1 or inVoidContext(seq)
29+
)
30+
or
31+
exists(ForStmt stmt | e = stmt.getUpdate())
32+
or
33+
exists(ForStmt stmt | e = stmt.getInit() |
34+
// Allow the pattern `for(i; i < 10; i++)`
35+
not e instanceof VarAccess
36+
)
37+
or
38+
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
39+
}

0 commit comments

Comments
 (0)