Skip to content

Commit 07c059c

Browse files
authored
Merge pull request #166 from github/type_tracking
Minimal implementation of shared type-tracking library
2 parents 73b5699 + 3a3586f commit 07c059c

9 files changed

Lines changed: 641 additions & 40 deletions

File tree

codeql

Submodule codeql updated 1111 files

ql/src/codeql_ruby/controlflow/CfgNodes.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ module ExprNodes {
247247
override predicate relevantChild(Expr e) { e = this.getValue() or e = this.getBranch(_) }
248248
}
249249

250+
/** A control-flow node that wraps a `MethodCall` AST expression. */
251+
class MethodCallCfgNode extends CallCfgNode {
252+
MethodCallCfgNode() { this.getExpr() instanceof MethodCall }
253+
}
254+
250255
/** A control-flow node that wraps a `CaseExpr` AST expression. */
251256
class CaseExprCfgNode extends ExprCfgNode {
252257
override CaseExprChildMapping e;

ql/src/codeql_ruby/dataflow/internal/DataFlowDispatch.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ class DataFlowCallable = CfgScope;
4040

4141
class DataFlowCall extends CfgNodes::ExprNodes::CallCfgNode {
4242
DataFlowCallable getEnclosingCallable() { result = this.getScope() }
43+
44+
DataFlowCallable getTarget() {
45+
// TODO: this is a placeholder that finds a method with the same name, iff it's uniquely named.
46+
result =
47+
unique(DataFlowCallable c | c.(Method).getName() = this.getNode().(MethodCall).getMethodName())
48+
}
4349
}
4450

4551
/** Gets a viable run-time target for the call `call`. */

ql/src/codeql_ruby/dataflow/internal/DataFlowImpl.qll

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,11 +2133,8 @@ private module Stage4 {
21332133

21342134
bindingset[node, cc, config]
21352135
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
2136-
exists(Cc cc0 |
2137-
cc = pragma[only_bind_into](cc0) and
2138-
localFlowEntry(node, config) and
2139-
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
2140-
)
2136+
localFlowEntry(node, config) and
2137+
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
21412138
}
21422139

21432140
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
31323129
conf = mid.getConfiguration() and
31333130
cc = mid.getCallContext() and
31343131
sc = mid.getSummaryCtx() and
3135-
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
3132+
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
31363133
ap0 = mid.getAp()
31373134
|
31383135
localFlowBigStep(midnode, node, true, _, conf, localCC) and

ql/src/codeql_ruby/dataflow/internal/DataFlowPublic.qll

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ private import ruby
22
private import DataFlowDispatch
33
private import DataFlowPrivate
44
private import codeql_ruby.CFG
5+
private import codeql_ruby.typetracking.TypeTracker
56

67
/**
78
* An element, viewed as a node in a data flow graph. Either an expression
@@ -73,6 +74,37 @@ class ParameterNode extends Node, TParameterNode {
7374
predicate isParameterOf(Callable c, int i) { p = c.getParameter(i) }
7475
}
7576

77+
/**
78+
* A data-flow node that is a source of local flow.
79+
*/
80+
class LocalSourceNode extends Node {
81+
LocalSourceNode() { not simpleLocalFlowStep+(any(ExprNode n), this) }
82+
83+
/** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
84+
pragma[inline]
85+
predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) }
86+
87+
/**
88+
* Gets a node that this node may flow to using one heap and/or interprocedural step.
89+
*
90+
* See `TypeTracker` for more details about how to use this.
91+
*/
92+
pragma[inline]
93+
LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
94+
}
95+
96+
predicate hasLocalSource(Node sink, Node source) {
97+
// Declaring `source` to be a `SourceNode` currently causes a redundant check in the
98+
// recursive case, so instead we check it explicitly here.
99+
source = sink and
100+
source instanceof LocalSourceNode
101+
or
102+
exists(Node mid |
103+
hasLocalSource(mid, source) and
104+
simpleLocalFlowStep(mid, sink)
105+
)
106+
}
107+
76108
/** Gets a node corresponding to expression `e`. */
77109
ExprNode exprNode(CfgNodes::ExprCfgNode e) { result.getExprNode() = e }
78110

ql/src/codeql_ruby/dataflow/internal/SsaImplCommon.qll

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Provides a language-independant implementation of static single assignment
2+
* Provides a language-independent implementation of static single assignment
33
* (SSA) form.
44
*/
55

@@ -316,15 +316,23 @@ private module SsaDefReaches {
316316
)
317317
}
318318

319+
/**
320+
* Holds if the reference to `def` at index `i` in basic block `bb` is the
321+
* last reference to `v` inside `bb`.
322+
*/
323+
pragma[noinline]
324+
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
325+
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
326+
}
327+
319328
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
320329
exists(ssaDefRank(def, v, bb, _, _))
321330
}
322331

323332
pragma[noinline]
324-
private BasicBlock getAMaybeLiveSuccessor(Definition def, BasicBlock bb) {
325-
result = getABasicBlockSuccessor(bb) and
326-
not defOccursInBlock(_, bb, def.getSourceVariable()) and
327-
ssaDefReachesEndOfBlock(bb, def, _)
333+
private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) {
334+
ssaDefReachesEndOfBlock(bb, def, _) and
335+
not defOccursInBlock(_, bb, def.getSourceVariable())
328336
}
329337

330338
/**
@@ -337,7 +345,11 @@ private module SsaDefReaches {
337345
defOccursInBlock(def, bb1, _) and
338346
bb2 = getABasicBlockSuccessor(bb1)
339347
or
340-
exists(BasicBlock mid | varBlockReaches(def, bb1, mid) | bb2 = getAMaybeLiveSuccessor(def, mid))
348+
exists(BasicBlock mid |
349+
varBlockReaches(def, bb1, mid) and
350+
ssaDefReachesThroughBlock(def, mid) and
351+
bb2 = getABasicBlockSuccessor(mid)
352+
)
341353
}
342354

343355
/**
@@ -348,24 +360,16 @@ private module SsaDefReaches {
348360
*/
349361
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
350362
varBlockReaches(def, bb1, bb2) and
351-
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
352-
variableRead(bb2, i2, _, _)
363+
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
353364
}
354365
}
355366

356367
private import SsaDefReaches
357368

358-
pragma[noinline]
359-
private predicate ssaDefReachesEndOfBlockRec(BasicBlock bb, Definition def, SourceVariable v) {
360-
exists(BasicBlock idom | ssaDefReachesEndOfBlock(idom, def, v) |
361-
// The construction of SSA form ensures that each read of a variable is
362-
// dominated by its definition. An SSA definition therefore reaches a
363-
// control flow node if it is the _closest_ SSA definition that dominates
364-
// the node. If two definitions dominate a node then one must dominate the
365-
// other, so therefore the definition of _closest_ is given by the dominator
366-
// tree. Thus, reaching definitions can be calculated in terms of dominance.
367-
idom = getImmediateBasicBlockDominator(bb)
368-
)
369+
pragma[nomagic]
370+
predicate liveThrough(BasicBlock bb, SourceVariable v) {
371+
liveAtExit(bb, v) and
372+
not ssaRef(bb, _, v, SsaDef())
369373
}
370374

371375
/**
@@ -382,9 +386,14 @@ predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable
382386
liveAtExit(bb, v)
383387
)
384388
or
385-
ssaDefReachesEndOfBlockRec(bb, def, v) and
386-
liveAtExit(bb, v) and
387-
not ssaRef(bb, _, v, SsaDef())
389+
// The construction of SSA form ensures that each read of a variable is
390+
// dominated by its definition. An SSA definition therefore reaches a
391+
// control flow node if it is the _closest_ SSA definition that dominates
392+
// the node. If two definitions dominate a node then one must dominate the
393+
// other, so therefore the definition of _closest_ is given by the dominator
394+
// tree. Thus, reaching definitions can be calculated in terms of dominance.
395+
ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and
396+
liveThrough(bb, pragma[only_bind_into](v))
388397
}
389398

390399
/**
@@ -433,15 +442,22 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
433442
bb2 = bb1
434443
)
435444
or
436-
exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and
445+
lastSsaRef(def, _, bb1, i1) and
437446
defAdjacentRead(def, bb1, bb2, i2)
438447
}
439448

449+
pragma[noinline]
450+
private predicate adjacentDefRead(
451+
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
452+
) {
453+
adjacentDefRead(def, bb1, i1, bb2, i2) and
454+
v = def.getSourceVariable()
455+
}
456+
440457
private predicate adjacentDefReachesRead(
441458
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
442459
) {
443-
adjacentDefRead(def, bb1, i1, bb2, i2) and
444-
exists(SourceVariable v | v = def.getSourceVariable() |
460+
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
445461
ssaRef(bb1, i1, v, SsaDef())
446462
or
447463
variableRead(bb1, i1, v, true)
@@ -474,17 +490,19 @@ predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, Ba
474490
*/
475491
pragma[nomagic]
476492
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
477-
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
493+
exists(SourceVariable v |
478494
// Next reference to `v` inside `bb` is a write
479-
next.definesAt(v, bb, j) and
480-
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
495+
exists(int rnk, int j |
496+
rnk = ssaDefRank(def, v, bb, i, _) and
497+
next.definesAt(v, bb, j) and
498+
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
499+
)
481500
or
482501
// Can reach a write using one or more steps
483-
rnk = maxSsaRefRank(bb, v) and
502+
lastSsaRef(def, v, bb, i) and
484503
exists(BasicBlock bb2 |
485504
varBlockReaches(def, bb, bb2) and
486-
next.definesAt(v, bb2, j) and
487-
1 = ssaRefRank(bb2, j, v, SsaDef())
505+
1 = ssaDefRank(next, v, bb2, _, SsaDef())
488506
)
489507
)
490508
}
@@ -538,7 +556,8 @@ pragma[nomagic]
538556
predicate lastRef(Definition def, BasicBlock bb, int i) {
539557
lastRefRedef(def, bb, i, _)
540558
or
541-
exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) |
559+
lastSsaRef(def, _, bb, i) and
560+
(
542561
// Can reach exit directly
543562
bb instanceof ExitBasicBlock
544563
or

0 commit comments

Comments
 (0)