Skip to content

Commit 29b8a0d

Browse files
authored
Merge pull request github#3508 from asger-semmle/js/shared-data-flow-node
Approved by esbena
2 parents e983919 + 9d00632 commit 29b8a0d

40 files changed

+590
-201
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,7 @@ The following low-precision queries are no longer run by default on LGTM (their
7070

7171
* A library `semmle.javascript.explore.CallGraph` has been added to help write queries for exploring the call graph.
7272
* Added data flow for `Map` and `Set`, and added matching type-tracking steps that can accessed using the `CollectionsTypeTracking` module.
73+
* The data-flow node representing a parameter or destructuring pattern is now always the `ValueNode` corresponding to that AST node. This has a few consequences:
74+
- `Parameter.flow()` now gets the correct data flow node for a parameter. Previously this had a result, but the node was disconnected from the data flow graph.
75+
- `ParameterNode.asExpr()` and `.getAstNode()` now gets the parameter's AST node, whereas previously it had no result.
76+
- `Expr.flow()` now has a more meaningful result for destructuring patterns. Previously this node was disconnected from the data flow graph. Now it represents the values being destructured by the pattern.

javascript/ql/src/Statements/UselessConditional.ql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ predicate isInitialParameterUse(Expr e) {
6262
not p.isRestParameter()
6363
)
6464
or
65+
// same as above, but for captured variables
66+
exists(SimpleParameter p, LocalVariable var |
67+
var = p.getVariable() and
68+
var.isCaptured() and
69+
e = var.getAnAccess() and
70+
not p.isRestParameter()
71+
)
72+
or
6573
isInitialParameterUse(e.(LogNotExpr).getOperand())
6674
}
6775

javascript/ql/src/semmle/javascript/AST.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,9 @@ class StmtContainer extends @stmt_container, ASTNode {
447447
*/
448448
module AST {
449449
/**
450-
* A program element that evaluates to a value at runtime. This includes expressions,
451-
* but also function and class declaration statements, as well as TypeScript
452-
* namespace and enum declarations.
450+
* A program element that evaluates to a value or destructures a value at runtime.
451+
* This includes expressions and destructuring patterns, but also function and
452+
* class declaration statements, as well as TypeScript namespace and enum declarations.
453453
*
454454
* Examples:
455455
*

javascript/ql/src/semmle/javascript/CFG.qll

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,19 @@ class ControlFlowNode extends @cfg_node, Locatable, NodeInStmtContainer {
299299
*/
300300
predicate isStart() { this = any(StmtContainer sc).getStart() }
301301

302+
/**
303+
* Holds if this is a final node of `container`, that is, a CFG node where execution
304+
* of that toplevel or function terminates.
305+
*/
306+
predicate isAFinalNodeOfContainer(StmtContainer container) {
307+
getASuccessor().(SyntheticControlFlowNode).isAFinalNodeOfContainer(container)
308+
}
309+
302310
/**
303311
* Holds if this is a final node, that is, a CFG node where execution of a
304312
* toplevel or function terminates.
305313
*/
306-
predicate isAFinalNode() { getASuccessor().(SyntheticControlFlowNode).isAFinalNode() }
314+
final predicate isAFinalNode() { isAFinalNodeOfContainer(_) }
307315

308316
/**
309317
* Holds if this node is unreachable, that is, it has no predecessors in the CFG.
@@ -361,7 +369,9 @@ class ControlFlowEntryNode extends SyntheticControlFlowNode, @entry_node {
361369

362370
/** A synthetic CFG node marking the exit of a function or toplevel script. */
363371
class ControlFlowExitNode extends SyntheticControlFlowNode, @exit_node {
364-
override predicate isAFinalNode() { any() }
372+
override predicate isAFinalNodeOfContainer(StmtContainer container) {
373+
exit_cfg_node(this, container)
374+
}
365375

366376
override string toString() { result = "exit node of " + getContainer().toString() }
367377
}

javascript/ql/src/semmle/javascript/DefUse.qll

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ private predicate defn(ControlFlowNode def, Expr lhs, AST::ValueNode rhs) {
4545
exists(EnumMember member | def = member.getIdentifier() |
4646
lhs = def and rhs = member.getInitializer()
4747
)
48-
or
49-
lhs = def and def.(Parameter).getDefault() = rhs
5048
}
5149

5250
/**

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,9 @@ class MidPathNode extends PathNode, MkMidNode {
16011601
nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition() instanceof
16021602
SsaImplicitDefinition
16031603
or
1604+
// Skip SSA definition of parameter as its location coincides with the parameter node
1605+
nd = DataFlow::ssaDefinitionNode(SSA::definition(any(SimpleParameter p)))
1606+
or
16041607
// Skip to the top of big left-leaning string concatenation trees.
16051608
nd = any(AddExpr add).flow() and
16061609
nd = any(AddExpr add).getAnOperand().flow()

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

Lines changed: 31 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,10 @@
2121
import javascript
2222
private import internal.CallGraphs
2323
private import internal.FlowSteps as FlowSteps
24+
private import internal.DataFlowNode
25+
private import internal.AnalyzedParameters
2426

2527
module DataFlow {
26-
cached
27-
private newtype TNode =
28-
TValueNode(AST::ValueNode nd) or
29-
TSsaDefNode(SsaDefinition d) or
30-
TCapturedVariableNode(LocalVariable v) { v.isCaptured() } or
31-
TPropNode(@property p) or
32-
TRestPatternNode(DestructuringPattern dp, Expr rest) { rest = dp.getRest() } or
33-
TDestructuringPatternNode(DestructuringPattern dp) or
34-
TElementPatternNode(ArrayPattern ap, Expr p) { p = ap.getElement(_) } or
35-
TElementNode(ArrayExpr arr, Expr e) { e = arr.getAnElement() } or
36-
TReflectiveCallNode(MethodCallExpr ce, string kind) {
37-
ce.getMethodName() = kind and
38-
(kind = "call" or kind = "apply")
39-
} or
40-
TThisNode(StmtContainer f) { f.(Function).getThisBinder() = f or f instanceof TopLevel } or
41-
TUnusedParameterNode(SimpleParameter p) { not exists(SSA::definition(p)) } or
42-
TDestructuredModuleImportNode(ImportDeclaration decl) {
43-
exists(decl.getASpecifier().getImportedName())
44-
} or
45-
THtmlAttributeNode(HTML::Attribute attr) or
46-
TExceptionalFunctionReturnNode(Function f) or
47-
TExceptionalInvocationReturnNode(InvokeExpr e) or
48-
TGlobalAccessPathRoot()
49-
5028
/**
5129
* A node in the data flow graph.
5230
*/
@@ -90,13 +68,11 @@ module DataFlow {
9068
/**
9169
* Gets the expression enclosing this data flow node.
9270
* In most cases the result is the same as `asExpr()`, however this method
93-
* additionally the `InvokeExpr` corresponding to reflective calls, and the `Parameter`
94-
* for a `DataFlow::ParameterNode`.
71+
* additionally includes the `InvokeExpr` corresponding to reflective calls.
9572
*/
9673
Expr getEnclosingExpr() {
9774
result = asExpr() or
98-
this = DataFlow::reflectiveCallNode(result) or
99-
result = this.(ParameterNode).getParameter()
75+
this = DataFlow::reflectiveCallNode(result)
10076
}
10177

10278
/** Gets the AST node corresponding to this data flow node, if any. */
@@ -251,7 +227,7 @@ module DataFlow {
251227
*/
252228
private JSDocTypeExpr getFallbackTypeAnnotation() {
253229
exists(BindingPattern pattern |
254-
this = lvalueNode(pattern) and
230+
this = valueNode(pattern) and
255231
not ast_node_type(pattern, _) and
256232
result = pattern.getTypeAnnotation()
257233
)
@@ -281,8 +257,8 @@ module DataFlow {
281257
}
282258

283259
/**
284-
* An expression or a declaration of a function, class, namespace or enum,
285-
* viewed as a node in the data flow graph.
260+
* A node in the data flow graph which corresponds to an expression,
261+
* destructuring pattern, or declaration of a function, class, namespace, or enum.
286262
*
287263
* Examples:
288264
* ```js
@@ -390,30 +366,6 @@ module DataFlow {
390366
override ASTNode getAstNode() { result = rest }
391367
}
392368

393-
/**
394-
* A node in the data flow graph which corresponds to the value destructured by an
395-
* object or array pattern.
396-
*/
397-
private class DestructuringPatternNode extends Node, TDestructuringPatternNode {
398-
DestructuringPattern pattern;
399-
400-
DestructuringPatternNode() { this = TDestructuringPatternNode(pattern) }
401-
402-
override BasicBlock getBasicBlock() { result = pattern.getBasicBlock() }
403-
404-
override predicate hasLocationInfo(
405-
string filepath, int startline, int startcolumn, int endline, int endcolumn
406-
) {
407-
pattern.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
408-
}
409-
410-
override string toString() { result = pattern.toString() }
411-
412-
override File getFile() { result = pattern.getFile() }
413-
414-
override ASTNode getAstNode() { result = pattern }
415-
}
416-
417369
/**
418370
* A node in the data flow graph which corresponds to an element pattern of an
419371
* array pattern.
@@ -759,10 +711,6 @@ module DataFlow {
759711
parameterNode(paramNode, param)
760712
|
761713
result = paramNode
762-
or
763-
// special case: there is no SSA flow step for unused parameters
764-
paramNode instanceof UnusedParameterNode and
765-
result = param.getDefault().flow()
766714
)
767715
}
768716

@@ -850,7 +798,7 @@ module DataFlow {
850798
/** Gets the value pattern of this property pattern. */
851799
Expr getValuePattern() { result = prop.getValuePattern() }
852800

853-
override Node getBase() { result = TDestructuringPatternNode(prop.getObjectPattern()) }
801+
override Node getBase() { result = TValueNode(prop.getObjectPattern()) }
854802

855803
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
856804

@@ -863,7 +811,7 @@ module DataFlow {
863811
* for `[ ...elts ] = arr`.
864812
*/
865813
private class RestPatternAsPropRead extends PropRead, RestPatternNode {
866-
override Node getBase() { result = TDestructuringPatternNode(pattern) }
814+
override Node getBase() { result = TValueNode(pattern) }
867815

868816
override Expr getPropertyNameExpr() { none() }
869817

@@ -876,7 +824,7 @@ module DataFlow {
876824
* for `y`.
877825
*/
878826
private class ElementPatternAsPropRead extends PropRead, ElementPatternNode {
879-
override Node getBase() { result = TDestructuringPatternNode(pattern) }
827+
override Node getBase() { result = TValueNode(pattern) }
880828

881829
override Expr getPropertyNameExpr() { none() }
882830

@@ -923,32 +871,6 @@ module DataFlow {
923871
override string getPropertyName() { none() }
924872
}
925873

926-
/**
927-
* A data flow node representing an unused parameter.
928-
*
929-
* This case exists to ensure all parameters have a corresponding data-flow node.
930-
* In most cases, parameters are represented by SSA definitions or destructuring pattern nodes.
931-
*/
932-
private class UnusedParameterNode extends DataFlow::Node, TUnusedParameterNode {
933-
SimpleParameter p;
934-
935-
UnusedParameterNode() { this = TUnusedParameterNode(p) }
936-
937-
override string toString() { result = p.toString() }
938-
939-
override ASTNode getAstNode() { result = p }
940-
941-
override BasicBlock getBasicBlock() { result = p.getBasicBlock() }
942-
943-
override predicate hasLocationInfo(
944-
string filepath, int startline, int startcolumn, int endline, int endcolumn
945-
) {
946-
p.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
947-
}
948-
949-
override File getFile() { result = p.getFile() }
950-
}
951-
952874
/**
953875
* A data flow node representing an HTML attribute.
954876
*/
@@ -1302,7 +1224,7 @@ module DataFlow {
13021224
/**
13031225
* INTERNAL: Use `parameterNode(Parameter)` instead.
13041226
*/
1305-
predicate parameterNode(DataFlow::Node nd, Parameter p) { nd = lvalueNode(p) }
1227+
predicate parameterNode(DataFlow::Node nd, Parameter p) { nd = valueNode(p) }
13061228

13071229
/**
13081230
* INTERNAL: Use `thisNode(StmtContainer container)` instead.
@@ -1354,9 +1276,7 @@ module DataFlow {
13541276
result = TSsaDefNode(ssa)
13551277
)
13561278
or
1357-
result = TDestructuringPatternNode(lvalue)
1358-
or
1359-
result = TUnusedParameterNode(lvalue)
1279+
result = TValueNode(lvalue.(DestructuringPattern))
13601280
}
13611281

13621282
/**
@@ -1411,6 +1331,17 @@ module DataFlow {
14111331
succ = lvalueNode(def.getTarget())
14121332
)
14131333
or
1334+
exists(SimpleParameter param |
1335+
pred = valueNode(param) and // The value node represents the incoming argument
1336+
succ = lvalueNode(param) // The SSA node represents the parameters's local variable
1337+
)
1338+
or
1339+
exists(Expr arg, Parameter param |
1340+
localArgumentPassing(arg, param) and
1341+
pred = valueNode(arg) and
1342+
succ = valueNode(param)
1343+
)
1344+
or
14141345
exists(PropertyPattern pattern |
14151346
pred = TPropNode(pattern) and
14161347
succ = lvalueNode(pattern.getValuePattern())
@@ -1546,8 +1477,7 @@ module DataFlow {
15461477
*/
15471478
private AST::ValueNode defSourceNode(VarDef def) {
15481479
result = def.getSource() or
1549-
result = def.getDestructuringSource() or
1550-
localArgumentPassing(result, def)
1480+
result = def.getDestructuringSource()
15511481
}
15521482

15531483
/**
@@ -1593,8 +1523,15 @@ module DataFlow {
15931523
e instanceof FunctionBindExpr
15941524
or
15951525
e instanceof TaggedTemplateExpr
1526+
or
1527+
e instanceof Parameter and
1528+
not localArgumentPassing(_, e) and
1529+
not isAnalyzedParameter(e) and
1530+
not e.(Parameter).isRestParameter()
15961531
)
15971532
or
1533+
nd.(AnalyzedNode).hasAdditionalIncompleteness(cause)
1534+
or
15981535
nd.asExpr() instanceof ExternalModuleReference and
15991536
cause = "import"
16001537
or
@@ -1622,18 +1559,12 @@ module DataFlow {
16221559
exists(PropertyPattern p | nd = TPropNode(p)) and cause = "heap"
16231560
or
16241561
nd instanceof TElementPatternNode and cause = "heap"
1625-
or
1626-
nd instanceof UnusedParameterNode and cause = "call"
16271562
}
16281563

16291564
/**
16301565
* Holds if definition `def` cannot be completely analyzed due to `cause`.
16311566
*/
16321567
private predicate defIsIncomplete(VarDef def, Incompleteness cause) {
1633-
def instanceof Parameter and
1634-
not localArgumentPassing(_, def) and
1635-
cause = "call"
1636-
or
16371568
def instanceof ImportSpecifier and
16381569
cause = "import"
16391570
or

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ class AnalyzedNode extends DataFlow::Node {
156156

157157
/** Holds if the flow analysis can infer at least one abstract value for this node. */
158158
predicate hasFlow() { exists(getAValue()) }
159+
160+
/**
161+
* INTERNAL. Use `isIncomplete()` instead.
162+
*
163+
* Subclasses may override this to contribute additional incompleteness to this node
164+
* without overriding `isIncomplete()`.
165+
*/
166+
predicate hasAdditionalIncompleteness(DataFlow::Incompleteness cause) { none() }
159167
}
160168

161169
/**
@@ -255,14 +263,14 @@ class AnalyzedFunction extends DataFlow::AnalyzedValueNode {
255263
* of functions that cannot actually complete normally, since it does not
256264
* account for `finally` blocks and does not check reachability.
257265
*/
258-
private predicate mayReturnImplicitly() {
259-
exists(ConcreteControlFlowNode final |
260-
final.getContainer() = astNode and
261-
final.isAFinalNode() and
262-
not final instanceof ReturnStmt and
263-
not final instanceof ThrowStmt
264-
)
265-
}
266+
private predicate mayReturnImplicitly() { terminalNode(astNode, any(ExprOrStmt st)) }
267+
}
268+
269+
pragma[noinline]
270+
private predicate terminalNode(Function f, ControlFlowNode final) {
271+
final.isAFinalNodeOfContainer(f) and
272+
not final instanceof ReturnStmt and
273+
not final instanceof ThrowStmt
266274
}
267275

268276
/**

0 commit comments

Comments
 (0)