Skip to content

Commit 25e26b9

Browse files
authored
Merge pull request github#5554 from asgerf/js/non-recursive-propref
Approved by esbena
2 parents 6cceb73 + faf07da commit 25e26b9

File tree

6 files changed

+76
-14
lines changed

6 files changed

+76
-14
lines changed

javascript/ql/src/semmle/javascript/NodeJS.qll

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,42 @@ private class RequireVariable extends Variable {
202202
*/
203203
private predicate moduleInFile(Module m, File f) { m.getFile() = f }
204204

205+
private predicate isModuleModule(DataFlow::Node nd) {
206+
exists(ImportDeclaration imp |
207+
imp.getImportedPath().getValue() = "module" and
208+
nd =
209+
[
210+
DataFlow::destructuredModuleImportNode(imp),
211+
DataFlow::valueNode(imp.getASpecifier().(ImportNamespaceSpecifier))
212+
]
213+
)
214+
or
215+
isModuleModule(nd.getAPredecessor())
216+
}
217+
218+
private predicate isCreateRequire(DataFlow::Node nd) {
219+
exists(PropAccess prop |
220+
isModuleModule(prop.getBase().flow()) and
221+
prop.getPropertyName() = "createRequire" and
222+
nd = prop.flow()
223+
)
224+
or
225+
exists(PropertyPattern prop |
226+
isModuleModule(prop.getObjectPattern().flow()) and
227+
prop.getName() = "createRequire" and
228+
nd = prop.getValuePattern().flow()
229+
)
230+
or
231+
exists(ImportDeclaration decl, NamedImportSpecifier spec |
232+
decl.getImportedPath().getValue() = "module" and
233+
spec = decl.getASpecifier() and
234+
spec.getImportedName() = "createRequire" and
235+
nd = spec.flow()
236+
)
237+
or
238+
isCreateRequire(nd.getAPredecessor())
239+
}
240+
205241
/**
206242
* Holds if `nd` may refer to `require`, either directly or modulo local data flow.
207243
*/
@@ -215,16 +251,11 @@ private predicate isRequire(DataFlow::Node nd) {
215251
or
216252
// `import { createRequire } from 'module';`.
217253
// specialized to ES2015 modules to avoid recursion in the `DataFlow::moduleImport()` predicate and to avoid
218-
// negative recursion between `Import.getImportedModuleNode()` and `Import.getImportedModule()`.
219-
exists(ImportDeclaration imp, DataFlow::SourceNode baseObj |
220-
imp.getImportedPath().getValue() = "module"
221-
|
222-
baseObj =
223-
[
224-
DataFlow::destructuredModuleImportNode(imp),
225-
DataFlow::valueNode(imp.getASpecifier().(ImportNamespaceSpecifier))
226-
] and
227-
nd = baseObj.getAPropertyRead("createRequire").getACall()
254+
// negative recursion between `Import.getImportedModuleNode()` and `Import.getImportedModule()`, and
255+
// to avoid depending on `SourceNode` as this would make `SourceNode::Range` recursive.
256+
exists(CallExpr call |
257+
isCreateRequire(call.getCallee().flow()) and
258+
nd = call.flow()
228259
)
229260
}
230261

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ module DataFlow {
492492
* Gets the data flow node corresponding to the base object
493493
* whose property is read from or written to.
494494
*/
495+
cached
495496
abstract Node getBase();
496497

497498
/**
@@ -595,7 +596,10 @@ module DataFlow {
595596

596597
PropLValueAsPropWrite() { astNode instanceof LValue }
597598

598-
override Node getBase() { result = valueNode(astNode.getBase()) }
599+
override Node getBase() {
600+
result = valueNode(astNode.getBase()) and
601+
Stages::DataFlowStage::ref()
602+
}
599603

600604
override Expr getPropertyNameExpr() { result = astNode.getPropertyNameExpr() }
601605

@@ -724,7 +728,7 @@ module DataFlow {
724728
override ParameterField prop;
725729

726730
override Node getBase() {
727-
result = thisNode(prop.getDeclaringClass().getConstructor().getBody())
731+
thisNode(result, prop.getDeclaringClass().getConstructor().getBody())
728732
}
729733

730734
override Expr getPropertyNameExpr() {
@@ -758,7 +762,7 @@ module DataFlow {
758762
}
759763

760764
override Node getBase() {
761-
result = thisNode(prop.getDeclaringClass().getConstructor().getBody())
765+
thisNode(result, prop.getDeclaringClass().getConstructor().getBody())
762766
}
763767

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

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ private import semmle.javascript.internal.CachedStages
3434
* ```
3535
*/
3636
class SourceNode extends DataFlow::Node {
37-
SourceNode() { this instanceof SourceNode::Range }
37+
SourceNode() {
38+
this instanceof SourceNode::Range
39+
or
40+
none() and this instanceof SourceNode::Internal::RecursionGuard
41+
}
3842

3943
/**
4044
* Holds if this node flows into `sink` in zero or more local (that is,
@@ -329,6 +333,12 @@ module SourceNode {
329333
DataFlow::functionReturnNode(this, _)
330334
}
331335
}
336+
337+
/** INTERNAL. DO NOT USE. */
338+
module Internal {
339+
/** An empty class that some tests are using to enforce that SourceNode is non-recursive. */
340+
abstract class RecursionGuard extends DataFlow::Node { }
341+
}
332342
}
333343

334344
deprecated class DefaultSourceNode extends SourceNode {

javascript/ql/src/semmle/javascript/internal/CachedStages.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ module Stages {
133133
exists(any(DataFlow::Node node).toString())
134134
or
135135
exists(any(AccessPath a).getAnInstanceIn(_))
136+
or
137+
exists(any(DataFlow::PropRef ref).getBase())
136138
}
137139
}
138140

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| Success |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Test that fails to compile if the ___domain of `SourceNode::Range` depends on `SourceNode` (recursively).
3+
*
4+
* This tests adds a negative dependency `SourceNode --!--> SourceNode::Range`
5+
* so that the undesired edge `SourceNode::Range --> SourceNode` completes a negative cycle.
6+
*/
7+
8+
import javascript
9+
10+
class BadSourceNodeRange extends DataFlow::SourceNode::Internal::RecursionGuard {
11+
BadSourceNodeRange() { not this instanceof DataFlow::SourceNode::Range }
12+
}
13+
14+
select "Success"

0 commit comments

Comments
 (0)