Skip to content

Commit 9e5a814

Browse files
authored
Merge pull request #18315 from hvitved/ruby/dataflow-types
Ruby: Track types in data flow
2 parents bca5f4b + 37212cc commit 9e5a814

40 files changed

+9610
-8845
lines changed

ruby/ql/consistency-queries/DataFlowConsistency.ql

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ private module Input implements InputSig<Location, RubyDataFlow> {
4444
n.getASplit() instanceof Split::ConditionalCompletionSplit
4545
)
4646
}
47+
48+
predicate uniqueTypeExclude(Node n) {
49+
n =
50+
any(DataFlow::CallNode call |
51+
Private::isStandardNewCall(call.getExprNode(), _, _) and
52+
not call.getReceiver().asExpr().getExpr() instanceof ConstantReadAccess
53+
)
54+
}
4755
}
4856

4957
import MakeConsistency<Location, RubyDataFlow, RubyTaintTracking, Input>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Types are now being tracked in data flow, but only when the type of an object is obvious from the context. For example, `C.new` has guaranteed type `C`, while in `def add(x, y) { x + y }` we cannot assign a type to `x + y` (it could, for instance, be both `String` and `Integer`). Tracking types allows us to remove false-positive results when type incompatibility can be established.

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

+11-162
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ class NormalCall extends DataFlowCall, TNormalCall {
237237
* need to track the `View` instance (2) into the receiver of the adjusted method
238238
* call, in order to figure out that the call target is in fact `view.html.erb`.
239239
*/
240-
private module ViewComponentRenderModeling {
240+
module ViewComponentRenderModeling {
241241
private import codeql.ruby.frameworks.ViewComponent
242242

243243
private class RenderMethod extends SummarizedCallable, LibraryCallableToIncludeInTypeTracking {
@@ -333,7 +333,7 @@ private predicate selfInModule(SelfVariable self, Module m) {
333333

334334
/** Holds if `self` belongs to method `method` inside module `m`. */
335335
pragma[nomagic]
336-
private predicate selfInMethod(SelfVariable self, MethodBase method, Module m) {
336+
predicate selfInMethod(SelfVariable self, MethodBase method, Module m) {
337337
exists(ModuleBase encl |
338338
method = self.getDeclaringScope() and
339339
encl = method.getEnclosingModule() and
@@ -343,67 +343,6 @@ private predicate selfInMethod(SelfVariable self, MethodBase method, Module m) {
343343
)
344344
}
345345

346-
/** Holds if `self` belongs to the top-level. */
347-
pragma[nomagic]
348-
private predicate selfInToplevel(SelfVariable self, Module m) {
349-
ViewComponentRenderModeling::selfInErbToplevel(self, m)
350-
or
351-
not ViewComponentRenderModeling::selfInErbToplevel(self, _) and
352-
self.getDeclaringScope() instanceof Toplevel and
353-
m = Module::TResolved("Object")
354-
}
355-
356-
/**
357-
* Holds if SSA definition `def` belongs to a variable introduced via pattern
358-
* matching on type `m`. For example, in
359-
*
360-
* ```rb
361-
* case object
362-
* in C => c then c.foo
363-
* end
364-
* ```
365-
*
366-
* the SSA definition for `c` is introduced by matching on `C`.
367-
*/
368-
private predicate asModulePattern(SsaDefinitionExtNode def, Module m) {
369-
exists(AsPattern ap |
370-
m = Module::resolveConstantReadAccess(ap.getPattern()) and
371-
def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getAstNode() =
372-
ap.getVariableAccess()
373-
)
374-
}
375-
376-
/**
377-
* Holds if `read1` and `read2` are adjacent reads of SSA definition `def`,
378-
* and `read2` is checked to have type `m`. For example, in
379-
*
380-
* ```rb
381-
* case object
382-
* when C then object.foo
383-
* end
384-
* ```
385-
*
386-
* the two reads of `object` are adjacent, and the second is checked to have type `C`.
387-
*/
388-
private predicate hasAdjacentTypeCheckedReads(
389-
Ssa::Definition def, CfgNodes::ExprCfgNode read1, CfgNodes::ExprCfgNode read2, Module m
390-
) {
391-
exists(
392-
CfgNodes::ExprCfgNode pattern, ConditionBlock cb, CfgNodes::ExprNodes::CaseExprCfgNode case
393-
|
394-
m = Module::resolveConstantReadAccess(pattern.getExpr()) and
395-
cb.getLastNode() = pattern and
396-
cb.controls(read2.getBasicBlock(),
397-
any(SuccessorTypes::MatchingSuccessor match | match.getValue() = true)) and
398-
def.hasAdjacentReads(read1, read2) and
399-
case.getValue() = read1
400-
|
401-
pattern = case.getBranch(_).(CfgNodes::ExprNodes::WhenClauseCfgNode).getPattern(_)
402-
or
403-
pattern = case.getBranch(_).(CfgNodes::ExprNodes::InClauseCfgNode).getPattern()
404-
)
405-
}
406-
407346
/** Holds if `new` is a user-defined `self.new` method. */
408347
predicate isUserDefinedNew(SingletonMethod new) {
409348
exists(Expr object | singletonMethod(new, "new", object) |
@@ -638,7 +577,7 @@ private predicate hasUserDefinedNew(Module m) {
638577
* `self.new` on `m`.
639578
*/
640579
pragma[nomagic]
641-
private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) {
580+
predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) {
642581
exists(DataFlow::LocalSourceNode sourceNode |
643582
flowsToMethodCallReceiver(TNormalCall(new), sourceNode, "new") and
644583
// `m` should not have a user-defined `self.new` method
@@ -667,106 +606,11 @@ private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo,
667606
}
668607

669608
private module TrackInstanceInput implements CallGraphConstruction::InputSig {
670-
pragma[nomagic]
671-
private predicate isInstanceNoCall(DataFlow::Node n, Module tp, boolean exact) {
672-
n.asExpr().getExpr() instanceof NilLiteral and
673-
tp = Module::TResolved("NilClass") and
674-
exact = true
675-
or
676-
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
677-
tp = Module::TResolved("FalseClass") and
678-
exact = true
679-
or
680-
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
681-
tp = Module::TResolved("TrueClass") and
682-
exact = true
683-
or
684-
n.asExpr().getExpr() instanceof IntegerLiteral and
685-
tp = Module::TResolved("Integer") and
686-
exact = true
687-
or
688-
n.asExpr().getExpr() instanceof FloatLiteral and
689-
tp = Module::TResolved("Float") and
690-
exact = true
691-
or
692-
n.asExpr().getExpr() instanceof RationalLiteral and
693-
tp = Module::TResolved("Rational") and
694-
exact = true
695-
or
696-
n.asExpr().getExpr() instanceof ComplexLiteral and
697-
tp = Module::TResolved("Complex") and
698-
exact = true
699-
or
700-
n.asExpr().getExpr() instanceof StringlikeLiteral and
701-
tp = Module::TResolved("String") and
702-
exact = true
703-
or
704-
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
705-
tp = Module::TResolved("Array") and
706-
exact = true
707-
or
708-
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
709-
tp = Module::TResolved("Hash") and
710-
exact = true
711-
or
712-
n.asExpr().getExpr() instanceof MethodBase and
713-
tp = Module::TResolved("Symbol") and
714-
exact = true
715-
or
716-
n.asParameter() instanceof BlockParameter and
717-
tp = Module::TResolved("Proc") and
718-
exact = true
719-
or
720-
n.asExpr().getExpr() instanceof Lambda and
721-
tp = Module::TResolved("Proc") and
722-
exact = true
723-
or
724-
// `self` reference in method or top-level (but not in module or singleton method,
725-
// where instance methods cannot be called; only singleton methods)
726-
n =
727-
any(SelfLocalSourceNode self |
728-
exists(MethodBase m |
729-
selfInMethod(self.getVariable(), m, tp) and
730-
not m instanceof SingletonMethod and
731-
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
732-
)
733-
or
734-
selfInToplevel(self.getVariable(), tp) and
735-
exact = true
736-
)
737-
or
738-
// `in C => c then c.foo`
739-
asModulePattern(n, tp) and
740-
exact = false
741-
or
742-
// `case object when C then object.foo`
743-
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
744-
exact = false
745-
}
746-
747-
pragma[nomagic]
748-
private predicate isInstanceCall(DataFlow::Node n, Module tp, boolean exact) {
749-
isStandardNewCall(n.asExpr(), tp, exact)
750-
}
751-
752-
/** Holds if `n` is an instance of type `tp`. */
753-
pragma[inline]
754-
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
755-
isInstanceNoCall(n, tp, exact)
756-
or
757-
isInstanceCall(n, tp, exact)
758-
}
759-
760-
pragma[nomagic]
761-
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
762-
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
763-
}
764-
765609
newtype State = additional MkState(Module m, Boolean exact)
766610

767611
predicate start(DataFlow::Node start, State state) {
768612
exists(Module tp, boolean exact | state = MkState(tp, exact) |
769-
isInstance(start, tp, exact)
613+
TypeInference::hasType(start, tp, exact)
770614
or
771615
exists(Module m |
772616
(if m.isClass() then tp = Module::TResolved("Class") else tp = Module::TResolved("Module")) and
@@ -784,15 +628,20 @@ private module TrackInstanceInput implements CallGraphConstruction::InputSig {
784628
)
785629
}
786630

631+
pragma[nomagic]
632+
private predicate hasAdjacentTypeCheckedRead(DataFlow::Node node) {
633+
TypeInference::hasAdjacentTypeCheckedRead(node.asExpr(), _)
634+
}
635+
787636
pragma[nomagic]
788637
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
789638
smallStepNoCall(nodeFrom, nodeTo, summary)
790639
or
791640
// We exclude steps into type checked variables. For those, we instead rely on the
792641
// type being checked against
793642
localFlowStep(nodeFrom, nodeTo, summary) and
794-
not hasAdjacentTypeCheckedReads(nodeTo) and
795-
not asModulePattern(nodeTo, _)
643+
not hasAdjacentTypeCheckedRead(nodeTo) and
644+
not TypeInference::asModulePattern(nodeTo.(SsaDefinitionExtNode).getDefinitionExt(), _)
796645
}
797646

798647
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {

0 commit comments

Comments
 (0)