Skip to content

Commit 7661a98

Browse files
authored
Merge pull request #68 from esben-semmle/determinate-1-cfa-type-inference
Approved by xiemaisi
2 parents bcfd02f + 605695e commit 7661a98

29 files changed

+679
-104
lines changed

change-notes/1.18/analysis-javascript.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
* The taint tracking library now recognizes additional sanitization patterns. This may give fewer false-positive results for the security queries.
1818

19+
* Type inference for simple function calls has been improved. This may give additional results for queries that rely on type inference.
20+
1921
* Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following libraries:
2022
- [bluebird](http://bluebirdjs.com)
2123
- [browserid-crypto](https://github.com/mozilla/browserid-crypto)
@@ -92,6 +94,7 @@
9294
| **Query** | **Expected impact** | **Change** |
9395
|----------------------------|------------------------|------------------------------------------------------------------|
9496
| Arguments redefined | Fewer results | This rule previously also flagged redefinitions of `eval`. This was an oversight that is now fixed. |
97+
| Comparison between inconvertible types | Fewer results | This rule now flags fewer comparisons involving parameters. |
9598
| Comparison between inconvertible types | Lower severity | The severity of this rule has been revised to "warning". |
9699
| CORS misconfiguration for credentials transfer | More true-positive results | This rule now treats header names case-insensitively. |
97100
| Hard-coded credentials | More true-positive results | This rule now recognizes secret cryptographic keys. |
@@ -106,6 +109,8 @@
106109
| Unused variable | Fewer results | This rule no longer flags class expressions that could be made anonymous. While technically true, these results are not interesting. |
107110
| Unused variable | Renamed | This rule has been renamed to "Unused variable, import, function or class" to reflect the fact that it flags different kinds of unused program elements. |
108111
| Use of incompletely initialized object| Fewer results | This rule now flags the constructor instead its errorneous `this` or `super` expressions. |
112+
| Useless conditional | Fewer results | This rule no longer flags uses of boolean return values. |
113+
| Useless conditional | Fewer results | This rule now flags fewer comparisons involving parameters. |
109114

110115
## Changes to QL libraries
111116

javascript/ql/src/Expressions/HeterogeneousComparison.ql

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,36 @@ string getTypeDescription(string message1, string message2, int complexity1, int
170170
result = message1
171171
}
172172

173+
/**
174+
* Holds if `e` directly uses a parameter's initial value as passed in from the caller.
175+
*/
176+
predicate isInitialParameterUse(Expr e) {
177+
// unlike `SimpleParameter.getAnInitialUse` this will not include uses we have refinement information for
178+
exists (SimpleParameter p, SsaExplicitDefinition ssa |
179+
ssa.getDef() = p and
180+
ssa.getVariable().getAUse() = e and
181+
not p.isRestParameter()
182+
)
183+
}
184+
185+
/**
186+
* Holds if `e` is an expression that should not be considered in a heterogeneous comparison.
187+
*
188+
* We currently whitelist expressions that rely on inter-procedural parameter information.
189+
*/
190+
predicate whitelist(Expr e) {
191+
isInitialParameterUse(e)
192+
}
193+
173194
from ASTNode cmp,
174195
DataFlow::AnalyzedNode left, DataFlow::AnalyzedNode right,
175196
string leftTypes, string rightTypes,
176197
string leftExprDescription, string rightExprDescription,
177198
int leftTypeCount, int rightTypeCount ,
178199
string leftTypeDescription, string rightTypeDescription
179200
where isHeterogeneousComparison(cmp, left, right, leftTypes, rightTypes) and
201+
not whitelist(left.asExpr()) and
202+
not whitelist(right.asExpr()) and
180203
leftExprDescription = capitalize(getDescription(left.asExpr(), "this expression")) and
181204
rightExprDescription = getDescription(right.asExpr(), "an expression") and
182205
leftTypeCount = strictcount(left.getAType()) and

javascript/ql/src/Statements/UselessConditional.ql

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import javascript
1616
import semmle.javascript.RestrictedLocations
17+
import semmle.javascript.dataflow.Refinements
1718

1819
/**
1920
* Holds if `va` is a defensive truthiness check that may be worth keeping, even if it
@@ -62,19 +63,55 @@ predicate isConstant(Expr e) {
6263
isSymbolicConstant(e.(VarAccess).getVariable())
6364
}
6465

66+
/**
67+
* Holds if `e` directly uses a parameter's negated or initial value as passed in from the caller.
68+
*/
69+
predicate isInitialParameterUse(Expr e) {
70+
// unlike `SimpleParameter.getAnInitialUse` this will not include uses we have refinement information for
71+
exists (SimpleParameter p, SsaExplicitDefinition ssa |
72+
ssa.getDef() = p and
73+
ssa.getVariable().getAUse() = e and
74+
not p.isRestParameter()
75+
)
76+
or
77+
isInitialParameterUse(e.(LogNotExpr).getOperand())
78+
}
79+
80+
/**
81+
* Holds if `e` directly uses the returned value from a function call that returns a constant boolean value.
82+
*/
83+
predicate isConstantBooleanReturnValue(Expr e) {
84+
// unlike `SourceNode.flowsTo` this will not include uses we have refinement information for
85+
exists (DataFlow::CallNode call |
86+
exists (call.analyze().getTheBooleanValue()) |
87+
e = call.asExpr() or
88+
// also support return values that are assigned to variables
89+
exists (SsaExplicitDefinition ssa |
90+
ssa.getDef().getSource() = call.asExpr() and
91+
ssa.getVariable().getAUse() = e
92+
)
93+
)
94+
or
95+
isConstantBooleanReturnValue(e.(LogNotExpr).getOperand())
96+
}
97+
6598
/**
6699
* Holds if `e` is an expression that should not be flagged as a useless condition.
67100
*
68-
* We currently whitelist three kinds of expressions:
101+
* We currently whitelist these kinds of expressions:
69102
*
70103
* - constants (including references to literal constants);
71104
* - negations of constants;
72-
* - defensive checks.
105+
* - defensive checks;
106+
* - expressions that rely on inter-procedural parameter information;
107+
* - constant boolean returned values.
73108
*/
74109
predicate whitelist(Expr e) {
75110
isConstant(e) or
76111
isConstant(e.(LogNotExpr).getOperand()) or
77-
isDefensiveInit(e)
112+
isDefensiveInit(e) or
113+
isInitialParameterUse(e) or
114+
isConstantBooleanReturnValue(e)
78115
}
79116

80117
/**
@@ -107,4 +144,4 @@ where isConditional(cond, op.asExpr()) and
107144
msg = "This expression always evaluates to " + cv + "."
108145
)
109146

110-
select sel, msg
147+
select sel, msg

javascript/ql/src/semmle/javascript/dataflow/TypeInference.qll

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,52 @@ class AnalyzedModule extends TopLevel {
239239
)
240240
}
241241
}
242+
243+
/**
244+
* Flow analysis for functions.
245+
*/
246+
class AnalyzedFunction extends DataFlow::AnalyzedValueNode {
247+
override Function astNode;
248+
249+
override AbstractValue getALocalValue() { result = TAbstractFunction(astNode) }
250+
251+
/**
252+
* Gets a return value for a call to this function.
253+
*/
254+
AbstractValue getAReturnValue() {
255+
if astNode.isGenerator() or astNode.isAsync() then
256+
result = TAbstractOtherObject()
257+
else (
258+
// explicit return value
259+
result = astNode.getAReturnedExpr().analyze().getALocalValue()
260+
or
261+
// implicit return value
262+
(
263+
// either because execution of the function may terminate normally
264+
mayReturnImplicitly()
265+
or
266+
// or because there is a bare `return;` statement
267+
exists (ReturnStmt ret | ret = astNode.getAReturnStmt() | not exists(ret.getExpr()))
268+
) and
269+
result = TAbstractUndefined()
270+
)
271+
}
272+
273+
/**
274+
* Holds if the execution of this function may complete normally without
275+
* encountering a `return` or `throw` statement.
276+
*
277+
* Note that this is an overapproximation, that is, the predicate may hold
278+
* of functions that cannot actually complete normally, since it does not
279+
* account for `finally` blocks and does not check reachability.
280+
*/
281+
private predicate mayReturnImplicitly() {
282+
exists (ConcreteControlFlowNode final |
283+
final.getContainer() = astNode and
284+
final.isAFinalNode() and
285+
not final instanceof ReturnStmt and
286+
not final instanceof ThrowStmt
287+
)
288+
}
289+
290+
}

javascript/ql/src/semmle/javascript/dataflow/internal/BasicExprTypeInference.qll

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,6 @@ private class AnalyzedArrayComprehensionExpr extends DataFlow::AnalyzedValueNode
8989
override AbstractValue getALocalValue() { result = TAbstractOtherObject() }
9090
}
9191

92-
/**
93-
* Flow analysis for functions.
94-
*/
95-
private class AnalyzedFunction extends DataFlow::AnalyzedValueNode {
96-
override Function astNode;
97-
98-
override AbstractValue getALocalValue() { result = TAbstractFunction(astNode) }
99-
}
100-
10192
/**
10293
* Flow analysis for class declarations.
10394
*/

javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll

Lines changed: 76 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,69 +7,6 @@
77
import javascript
88
import AbstractValuesImpl
99

10-
/**
11-
* Flow analysis for immediately-invoked function expressions (IIFEs).
12-
*/
13-
private class IifeReturnFlow extends DataFlow::AnalyzedValueNode {
14-
ImmediatelyInvokedFunctionExpr iife;
15-
16-
IifeReturnFlow() {
17-
astNode = (CallExpr)iife.getInvocation()
18-
}
19-
20-
override AbstractValue getALocalValue() {
21-
result = getAReturnValue(iife)
22-
}
23-
}
24-
25-
/**
26-
* Gets a return value for the immediately-invoked function expression `f`.
27-
*/
28-
private AbstractValue getAReturnValue(ImmediatelyInvokedFunctionExpr f) {
29-
// explicit return value
30-
result = f.getAReturnedExpr().analyze().getALocalValue()
31-
or
32-
// implicit return value
33-
(
34-
// either because execution of the function may terminate normally
35-
mayReturnImplicitly(f)
36-
or
37-
// or because there is a bare `return;` statement
38-
exists (ReturnStmt ret | ret = f.getAReturnStmt() | not exists(ret.getExpr()))
39-
) and
40-
result = getDefaultReturnValue(f)
41-
}
42-
43-
44-
/**
45-
* Holds if the execution of function `f` may complete normally without
46-
* encountering a `return` or `throw` statement.
47-
*
48-
* Note that this is an overapproximation, that is, the predicate may hold
49-
* of functions that cannot actually complete normally, since it does not
50-
* account for `finally` blocks and does not check reachability.
51-
*/
52-
private predicate mayReturnImplicitly(Function f) {
53-
exists (ConcreteControlFlowNode final |
54-
final.getContainer() = f and
55-
final.isAFinalNode() and
56-
not final instanceof ReturnStmt and
57-
not final instanceof ThrowStmt
58-
)
59-
}
60-
61-
/**
62-
* Gets the default return value for immediately-invoked function expression `f`,
63-
* that is, the value that `f` returns if its execution terminates without
64-
* encountering an explicit `return` statement.
65-
*/
66-
private AbstractValue getDefaultReturnValue(ImmediatelyInvokedFunctionExpr f) {
67-
if f.isGenerator() or f.isAsync() then
68-
result = TAbstractOtherObject()
69-
else
70-
result = TAbstractUndefined()
71-
}
72-
7310
/**
7411
* Flow analysis for `this` expressions inside functions.
7512
*/
@@ -190,3 +127,79 @@ private class AnalyzedThisInPropertyFunction extends AnalyzedThisExpr {
190127
}
191128
}
192129

130+
/**
131+
* A call with inter-procedural type inference for the return value.
132+
*/
133+
abstract class CallWithAnalyzedReturnFlow extends DataFlow::AnalyzedValueNode {
134+
135+
/**
136+
* Gets a called function.
137+
*/
138+
abstract AnalyzedFunction getACallee();
139+
140+
override AbstractValue getALocalValue() {
141+
result = getACallee().getAReturnValue() and
142+
not this instanceof DataFlow::NewNode
143+
}
144+
}
145+
146+
/**
147+
* Flow analysis for the return value of IIFEs.
148+
*/
149+
private class IIFEWithAnalyzedReturnFlow extends CallWithAnalyzedReturnFlow {
150+
151+
ImmediatelyInvokedFunctionExpr iife;
152+
153+
IIFEWithAnalyzedReturnFlow() {
154+
astNode = iife.getInvocation()
155+
}
156+
157+
override AnalyzedFunction getACallee() {
158+
result = iife.analyze()
159+
}
160+
161+
}
162+
163+
/** A function that only is used locally, making it amenable to type inference. */
164+
class LocalFunction extends Function {
165+
166+
DataFlow::Impl::ExplicitInvokeNode invk;
167+
168+
LocalFunction() {
169+
this instanceof FunctionDeclStmt and
170+
exists (LocalVariable v, Expr callee |
171+
callee = invk.getCalleeNode().asExpr() and
172+
v = getVariable() and
173+
v.getAnAccess() = callee and
174+
forall(VarAccess o | o = v.getAnAccess() | o = callee) and
175+
not exists(v.getAnAssignedExpr()) and
176+
not exists(ExportDeclaration export | export.exportsAs(v, _))
177+
) and
178+
// if the function is non-strict and its `arguments` object is accessed, we
179+
// also assume that there may be other calls (through `arguments.callee`)
180+
(isStrict() or not usesArgumentsObject())
181+
}
182+
183+
/** Gets an invocation of this function. */
184+
DataFlow::InvokeNode getAnInvocation() {
185+
result = invk
186+
}
187+
188+
}
189+
190+
/**
191+
* Enables inter-procedural type inference for a call to a `LocalFunction`.
192+
*/
193+
private class LocalFunctionCallWithAnalyzedReturnFlow extends CallWithAnalyzedReturnFlow {
194+
195+
LocalFunction f;
196+
197+
LocalFunctionCallWithAnalyzedReturnFlow() {
198+
this = f.getAnInvocation()
199+
}
200+
201+
override AnalyzedFunction getACallee() {
202+
result = f.analyze()
203+
}
204+
205+
}

0 commit comments

Comments
 (0)