Skip to content

Commit 29bfb4d

Browse files
committed
Ruby: Revert changes to isLocalSourceNode and localFlowStepTypeTracker
Instead, use small-step type tracking, as suggested by @RasmusWL offline.
1 parent ac4d4ff commit 29bfb4d

File tree

3 files changed

+47
-106
lines changed

3 files changed

+47
-106
lines changed

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll

Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@ private predicate superCall(CfgNodes::ExprNodes::CallCfgNode call, Module superC
186186

187187
pragma[nomagic]
188188
private predicate instanceMethodCall(CfgNodes::ExprNodes::CallCfgNode call, Module tp, string method) {
189-
exists(DataFlow::LocalSourceNode sourceNode, Module m, boolean exact |
190-
flowsToMethodCall(call, sourceNode, method) and
191-
sourceNode = trackInstance(m, exact)
189+
exists(DataFlow::Node receiver, Module m, boolean exact |
190+
methodCall(call, receiver, method) and
191+
receiver = trackInstance(m, exact)
192192
|
193193
tp = m
194194
or
@@ -239,7 +239,7 @@ private predicate selfInToplevel(SelfVariable self, Module m) {
239239
*
240240
* the SSA definition for `c` is introduced by matching on `C`.
241241
*/
242-
predicate asModulePattern(SsaDefinitionNode def, Module m) {
242+
private predicate asModulePattern(SsaDefinitionNode def, Module m) {
243243
exists(AsPattern ap |
244244
m = resolveConstantReadAccess(ap.getPattern()) and
245245
def.getDefinition().(Ssa::WriteDefinition).getWriteAccess() = ap.getVariableAccess()
@@ -258,7 +258,7 @@ predicate asModulePattern(SsaDefinitionNode def, Module m) {
258258
*
259259
* the two reads of `object` are adjacent, and the second is checked to have type `C`.
260260
*/
261-
predicate hasAdjacentTypeCheckedReads(
261+
private predicate hasAdjacentTypeCheckedReads(
262262
Ssa::Definition def, CfgNodes::ExprCfgNode read1, CfgNodes::ExprCfgNode read2, Module m
263263
) {
264264
exists(
@@ -322,11 +322,9 @@ private module Cached {
322322
// def c.singleton; end # <- result
323323
// c.singleton # <- call
324324
// ```
325-
exists(DataFlow::Node sourceNode |
326-
methodCall(call, sourceNode, method) or
327-
flowsToMethodCall(call, sourceNode, method)
328-
|
329-
sourceNode = trackSingletonMethodOnInstance(result, method)
325+
exists(DataFlow::Node receiver |
326+
methodCall(call, receiver, method) and
327+
receiver = trackSingletonMethodOnInstance(result, method)
330328
)
331329
or
332330
// singleton method defined on a module
@@ -445,7 +443,7 @@ private DataFlow::LocalSourceNode trackModuleAccess(Module m) {
445443
}
446444

447445
pragma[nomagic]
448-
private DataFlow::LocalSourceNode trackInstance(Module tp, boolean exact, TypeTracker t) {
446+
private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
449447
t.start() and
450448
(
451449
result.asExpr().getExpr() instanceof NilLiteral and
@@ -554,23 +552,30 @@ private DataFlow::LocalSourceNode trackInstance(Module tp, boolean exact, TypeTr
554552
)
555553
}
556554

555+
private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
556+
localFlowStepTypeTracker(nodeFrom, nodeTo) and
557+
summary.toString() = "level"
558+
}
559+
557560
/**
558561
* We exclude steps into `self` parameters and type checked variables. For those,
559562
* we instead rely on the type of the enclosing module resp. the type being checked
560563
* against, and apply an open-world assumption when determining possible dispatch
561564
* targets.
562565
*/
563566
pragma[nomagic]
564-
private DataFlow::LocalSourceNode trackInstanceRec(
565-
Module tp, TypeTracker t, boolean exact, StepSummary summary
566-
) {
567-
StepSummary::step(trackInstance(tp, exact, t), result, summary) and
567+
private DataFlow::Node trackInstanceRec(Module tp, TypeTracker t, boolean exact, StepSummary summary) {
568+
exists(DataFlow::Node mid | mid = trackInstance(tp, exact, t) |
569+
StepSummary::smallstep(mid, result, summary)
570+
or
571+
localFlowStep(mid, result, summary)
572+
) and
568573
not result instanceof SelfParameterNode and
569574
not hasAdjacentTypeCheckedReads(_, _, result.asExpr(), _)
570575
}
571576

572577
pragma[nomagic]
573-
private DataFlow::LocalSourceNode trackInstance(Module tp, boolean exact) {
578+
private DataFlow::Node trackInstance(Module tp, boolean exact) {
574579
result = trackInstance(tp, exact, TypeTracker::end())
575580
}
576581

@@ -683,16 +688,6 @@ predicate singletonMethodOnInstance(MethodBase method, string name, Expr object)
683688
not exists(resolveConstantReadAccess(object))
684689
}
685690

686-
/**
687-
* Same as `singletonMethodOnInstance`, but where `n` is the post-update node
688-
* of the object that is the target of the singleton method `method`.
689-
*/
690-
predicate singletonMethodOnInstancePostUpdate(
691-
MethodBase method, string name, DataFlow::PostUpdateNode n
692-
) {
693-
singletonMethodOnInstance(method, name, n.getPreUpdateNode().asExpr().getExpr())
694-
}
695-
696691
/**
697692
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
698693
*
@@ -723,44 +718,46 @@ predicate singletonMethodOnInstancePostUpdate(
723718
* ```
724719
*/
725720
pragma[nomagic]
726-
private predicate paramReturnFlow(DataFlow::PostUpdateNode nodeFrom, DataFlow::PostUpdateNode nodeTo) {
721+
private predicate paramReturnFlow(
722+
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
723+
) {
727724
exists(
728725
CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node arg, DataFlow::ParameterNode p,
729726
Expr nodeFromPreExpr
730727
|
731728
TypeTrackerSpecific::callStep(call, arg, p) and
732729
nodeTo.getPreUpdateNode() = arg and
733-
nodeFromPreExpr = nodeFrom.getPreUpdateNode().asExpr().getExpr()
730+
summary.toString() = "return" and
731+
(
732+
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
733+
or
734+
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
735+
singletonMethodOnInstance(_, _, nodeFromPreExpr)
736+
)
734737
|
735738
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
736739
or
737740
nodeFromPreExpr = p.(SelfParameterNode).getSelfVariable().getAnAccess()
738741
)
739742
}
740743

741-
// Since post-update nodes are not (and should not be) `LocalSourceNode`s in general,
742-
// we need to do local flow manually
743-
pragma[nomagic]
744-
private predicate argPostUpdateFlowsTo(DataFlow::PostUpdateNode arg, DataFlow::Node n) {
745-
paramReturnFlow(_, arg) and
746-
n = arg
747-
or
748-
exists(DataFlow::Node mid |
749-
argPostUpdateFlowsTo(arg, mid) and
750-
localFlowStepTypeTracker(mid, n)
751-
)
752-
}
753-
754744
pragma[nomagic]
755745
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name, TypeTracker t) {
756746
t.start() and
757-
singletonMethodOnInstancePostUpdate(method, name, result)
747+
singletonMethodOnInstance(method, name, result.asExpr().getExpr())
758748
or
759749
exists(TypeTracker t2, StepSummary summary |
760750
result = trackSingletonMethodOnInstanceRec(method, name, t2, summary) and
761751
t = t2.append(summary) and
762-
// do not step over redefinitions
763-
not singletonMethodOnInstancePostUpdate(_, name, result)
752+
// Stop flow at redefinitions.
753+
//
754+
// Example:
755+
// ```rb
756+
// def x.foo; end
757+
// def x.foo; end
758+
// x.foo # <- we want to resolve this call to the second definition only
759+
// ```
760+
not singletonMethodOnInstance(_, name, result.asExpr().getExpr())
764761
)
765762
}
766763

@@ -769,15 +766,11 @@ private DataFlow::Node trackSingletonMethodOnInstanceRec(
769766
MethodBase method, string name, TypeTracker t, StepSummary summary
770767
) {
771768
exists(DataFlow::Node mid | mid = trackSingletonMethodOnInstance(method, name, t) |
772-
StepSummary::step(mid, result, summary)
769+
StepSummary::smallstep(mid, result, summary)
773770
or
774-
// include flow out through parameters
775-
paramReturnFlow(mid, result) and
776-
summary.toString() = "return"
771+
paramReturnFlow(mid, result, summary)
777772
or
778-
// include flow starting from an output argument
779-
argPostUpdateFlowsTo(mid, result) and
780-
summary.toString() = "level"
773+
localFlowStep(mid, result, summary)
781774
)
782775
}
783776

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,7 @@ private module Cached {
242242
isNonConstantExpr(n) and
243243
(
244244
n instanceof Argument or
245-
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver() or
246-
singletonMethodOnInstance(_, _, n.getExpr())
245+
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
247246
)
248247
} or
249248
TSummaryNode(
@@ -315,40 +314,7 @@ private module Cached {
315314
nodeTo = LocalFlow::getParameterDefNode(p)
316315
)
317316
or
318-
exists(Ssa::Definition def |
319-
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
320-
// For nodes that are the target of a singleton method definition, such
321-
// as `x` in `def x.foo; end`, we disallow use-use flow out of `x`, and
322-
// instead add a type-tracker level-step from `x` to the post-update node
323-
// of `x` (which does allow further use-use flow).
324-
//
325-
// This enables us to stregthen call resolution for singleton methods, since
326-
// we can stop flow at redefinitions, which would otherwise not be possible,
327-
// as type-tracking would step over such redefinitions.
328-
//
329-
// Example:
330-
// ```rb
331-
// def x.foo; end
332-
// def x.foo; end
333-
// x.foo # <- we want to resolve this call to the second definition only
334-
// ```
335-
not singletonMethodOnInstance(_, _, nodeFrom.asExpr().getExpr()) and
336-
// We disallow adjacent use-use steps, where the target is type checked, and
337-
// instead add a type-tracker level-step.
338-
//
339-
// This enables us to strengthen call resolution for instance methods, since
340-
// we can use the extra type information on the target node.
341-
//
342-
// Example:
343-
// ```rb
344-
// case object
345-
// when C then object.foo # <- we want to resolve this call as if it was a call inside `C`
346-
// end
347-
// ```
348-
//
349-
// The second access to `object` is known to have type `C` (or a sub-type thereof).
350-
not hasAdjacentTypeCheckedReads(def, nodeFrom.asExpr(), nodeTo.asExpr(), _)
351-
)
317+
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
352318
}
353319

354320
private predicate entrySsaDefinition(SsaDefinitionNode n) {
@@ -393,15 +359,6 @@ private module Cached {
393359
or
394360
// Needed for stores in type tracking
395361
TypeTrackerSpecific::basicStoreStep(_, n, _)
396-
or
397-
// Needed to be able to track singleton methods defined on instances
398-
singletonMethodOnInstancePostUpdate(_, _, n)
399-
or
400-
// Needed to be able to track instance methods on variables introduced via pattern matching
401-
asModulePattern(n, _)
402-
or
403-
// Needed to be able to (better) track instance methods on variables that are type checked
404-
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), _)
405362
}
406363

407364
cached

ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,7 @@ private predicate summarizedLocalStep(Node nodeFrom, Node nodeTo) {
3535
}
3636

3737
/** Holds if there is a level step from `nodeFrom` to `nodeTo`. */
38-
predicate levelStep(Node nodeFrom, Node nodeTo) {
39-
summarizedLocalStep(nodeFrom, nodeTo)
40-
or
41-
// See comment in `localFlowStepTypeTracker/2`
42-
nodeTo.(DataFlowPublic::PostUpdateNode).getPreUpdateNode() = nodeFrom and
43-
DataFlowDispatch::singletonMethodOnInstance(_, _, nodeFrom.asExpr().getExpr())
44-
or
45-
// See comment in `localFlowStepTypeTracker/2`
46-
DataFlowDispatch::hasAdjacentTypeCheckedReads(_, nodeFrom.asExpr(), nodeTo.asExpr(), _)
47-
}
38+
predicate levelStep(Node nodeFrom, Node nodeTo) { summarizedLocalStep(nodeFrom, nodeTo) }
4839

4940
/**
5041
* Gets the name of a possible piece of content. This will usually include things like

0 commit comments

Comments
 (0)