Skip to content

Commit 859dc7b

Browse files
authored
Merge pull request github#11024 from asgerf/rb/data-flow-layer-capture2
Ruby: expand DataFlow API
2 parents a05706d + 271de66 commit 859dc7b

35 files changed

+1865
-416
lines changed

ruby/ql/lib/codeql/ruby/AST.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ private module Cached {
3636
not s instanceof ModuleBase and
3737
result = getEnclosingMethod(s.getOuterScope())
3838
}
39+
40+
cached
41+
Toplevel getEnclosingToplevel(Scope s) {
42+
result = s
43+
or
44+
result = getEnclosingToplevel(s.getOuterScope())
45+
}
3946
}
4047

4148
private import Cached
@@ -66,6 +73,9 @@ class AstNode extends TAstNode {
6673
/** Gets the enclosing method, if any. */
6774
final MethodBase getEnclosingMethod() { result = getEnclosingMethod(scopeOfInclSynth(this)) }
6875

76+
/** Gets the enclosing top-level. */
77+
final Toplevel getEnclosingToplevel() { result = getEnclosingToplevel(scopeOfInclSynth(this)) }
78+
6979
/** Gets a textual representation of this node. */
7080
cached
7181
string toString() { none() }

ruby/ql/lib/codeql/ruby/Concepts.qll

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,7 @@ module Http {
271271

272272
/** Gets the URL pattern for this route, if it can be statically determined. */
273273
string getUrlPattern() {
274-
exists(CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
275-
this.getUrlPatternArg().getALocalSource() = DataFlow::exprNode(strNode) and
276-
result = strNode.getExpr().getConstantValue().getStringlikeValue()
277-
)
274+
result = this.getUrlPatternArg().getALocalSource().getConstantValue().getStringlikeValue()
278275
}
279276

280277
/**
@@ -538,10 +535,12 @@ module Http {
538535

539536
/** Gets the mimetype of this HTTP response, if it can be statically determined. */
540537
string getMimetype() {
541-
exists(CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
542-
this.getMimetypeOrContentTypeArg().getALocalSource() = DataFlow::exprNode(strNode) and
543-
result = strNode.getExpr().getConstantValue().getStringlikeValue().splitAt(";", 0)
544-
)
538+
result =
539+
this.getMimetypeOrContentTypeArg()
540+
.getALocalSource()
541+
.getConstantValue()
542+
.getStringlikeValue()
543+
.splitAt(";", 0)
545544
or
546545
not exists(this.getMimetypeOrContentTypeArg()) and
547546
result = this.getMimetypeDefault()

ruby/ql/lib/codeql/ruby/ast/Constant.qll

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,24 @@ module ConstantValue {
170170

171171
/** A constant `nil` value. */
172172
class ConstantNilValue extends ConstantValue, TNil { }
173+
174+
/** Gets the integer constant `x`. */
175+
ConstantValue fromInt(int x) { result.getInt() = x }
176+
177+
/** Gets the float constant `x`. */
178+
ConstantValue fromFloat(float x) { result.getFloat() = x }
179+
180+
/** Gets the string constant `x`. */
181+
ConstantValue fromString(string x) { result.getString() = x }
182+
183+
/** Gets the symbol constant `x`. */
184+
ConstantValue fromSymbol(string x) { result.getSymbol() = x }
185+
186+
/** Gets the regexp constant `x`. */
187+
ConstantValue fromRegExp(string x) { result.getRegExp() = x }
188+
189+
/** Gets the string, symbol, or regexp constant `x`. */
190+
ConstantValue fromStringlikeValue(string x) { result.getStringlikeValue() = x }
173191
}
174192

175193
/** An access to a constant. */

ruby/ql/lib/codeql/ruby/ast/Module.qll

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ private import codeql.ruby.CFG
33
private import internal.AST
44
private import internal.Module
55
private import internal.TreeSitter
6+
private import internal.Scope
67

78
/**
89
* A representation of a run-time `module` or `class` value.
@@ -23,6 +24,22 @@ class Module extends TModule {
2324
/** Gets an `include`d module. */
2425
Module getAnIncludedModule() { result = getAnIncludedModule(this) }
2526

27+
/** Gets the super class or an included or prepended module. */
28+
Module getAnImmediateAncestor() {
29+
result = [this.getSuperClass(), this.getAPrependedModule(), this.getAnIncludedModule()]
30+
}
31+
32+
/** Gets a direct subclass or module including or prepending this one. */
33+
Module getAnImmediateDescendent() { this = result.getAnImmediateAncestor() }
34+
35+
/** Gets a module that is transitively subclassed, included, or prepended by this module. */
36+
pragma[inline]
37+
Module getAnAncestor() { result = this.getAnImmediateAncestor*() }
38+
39+
/** Gets a module that transitively subclasses, includes, or prepends this module. */
40+
pragma[inline]
41+
Module getADescendent() { result = this.getAnImmediateDescendent*() }
42+
2643
/** Holds if this module is a class. */
2744
pragma[noinline]
2845
predicate isClass() {
@@ -63,6 +80,99 @@ class Module extends TModule {
6380
loc.getStartColumn()
6481
)
6582
}
83+
84+
/** Gets a constant or `self` access that refers to this module. */
85+
private Expr getAnImmediateReferenceBase() {
86+
resolveConstantReadAccess(result) = this
87+
or
88+
result.(SelfVariableAccess).getVariable() = this.getADeclaration().getModuleSelfVariable()
89+
}
90+
91+
/** Gets a singleton class that augments this module object. */
92+
SingletonClass getASingletonClass() { result.getValue() = this.getAnImmediateReferenceBase() }
93+
94+
/**
95+
* Gets a singleton method on this module, either declared as a singleton method
96+
* or an instance method on a singleton class.
97+
*
98+
* Does not take inheritance into account.
99+
*/
100+
MethodBase getAnOwnSingletonMethod() {
101+
result.(SingletonMethod).getObject() = this.getAnImmediateReferenceBase()
102+
or
103+
result = this.getASingletonClass().getAMethod().(Method)
104+
}
105+
106+
/**
107+
* Gets an instance method named `name` declared in this module.
108+
*
109+
* Does not take inheritance into account.
110+
*/
111+
Method getOwnInstanceMethod(string name) { result = this.getADeclaration().getMethod(name) }
112+
113+
/**
114+
* Gets an instance method declared in this module.
115+
*
116+
* Does not take inheritance into account.
117+
*/
118+
Method getAnOwnInstanceMethod() { result = this.getADeclaration().getMethod(_) }
119+
120+
/**
121+
* Gets the instance method named `name` available in this module, including methods inherited
122+
* from ancestors.
123+
*/
124+
Method getInstanceMethod(string name) { result = lookupMethod(this, name) }
125+
126+
/**
127+
* Gets an instance method available in this module, including methods inherited
128+
* from ancestors.
129+
*/
130+
Method getAnInstanceMethod() { result = lookupMethod(this, _) }
131+
132+
/** Gets a constant or `self` access that refers to this module. */
133+
Expr getAnImmediateReference() {
134+
result = this.getAnImmediateReferenceBase()
135+
or
136+
result.(SelfVariableAccess).getVariable().getDeclaringScope() = this.getAnOwnSingletonMethod()
137+
}
138+
139+
pragma[nomagic]
140+
private string getEnclosingModuleName() {
141+
exists(string qname |
142+
qname = this.getQualifiedName() and
143+
result = qname.regexpReplaceAll("::[^:]*$", "") and
144+
qname != result
145+
)
146+
}
147+
148+
pragma[nomagic]
149+
private string getOwnModuleName() {
150+
result = this.getQualifiedName().regexpReplaceAll("^.*::", "")
151+
}
152+
153+
/**
154+
* Gets the enclosing module, as it appears in the qualified name of this module.
155+
*
156+
* For example, the parent module of `A::B` is `A`, and `A` itself has no parent module.
157+
*/
158+
pragma[nomagic]
159+
Module getParentModule() { result.getQualifiedName() = this.getEnclosingModuleName() }
160+
161+
/**
162+
* Gets a module named `name` declared inside this one (not aliased), provided
163+
* that such a module is defined or reopened in the current codebase.
164+
*
165+
* For example, for `A::B` the nested module named `C` would be `A::B::C`.
166+
*
167+
* Note that this is not the same as constant lookup. If `A::B::C` would resolve to a
168+
* module whose qualified name is not `A::B::C`, then it will not be found by
169+
* this predicate.
170+
*/
171+
pragma[nomagic]
172+
Module getNestedModule(string name) {
173+
result.getParentModule() = this and
174+
result.getOwnModuleName() = name
175+
}
66176
}
67177

68178
/**
@@ -141,6 +251,46 @@ class ModuleBase extends BodyStmt, Scope, TModuleBase {
141251

142252
/** Gets the representation of the run-time value of this module or class. */
143253
Module getModule() { none() }
254+
255+
/**
256+
* Gets the `self` variable in the module-level scope.
257+
*
258+
* Does not include the `self` variable from any of the methods in the module.
259+
*/
260+
SelfVariable getModuleSelfVariable() { result.getDeclaringScope() = this }
261+
262+
/** Gets the nearest enclosing `Namespace` or `Toplevel`, possibly this module itself. */
263+
Namespace getNamespaceOrToplevel() {
264+
result = this
265+
or
266+
not this instanceof Namespace and
267+
result = this.getEnclosingModule().getNamespaceOrToplevel()
268+
}
269+
270+
/**
271+
* Gets an expression denoting the super class or an included or prepended module.
272+
*
273+
* For example, `C` is an ancestor expression of `M` in each of the following examples:
274+
* ```rb
275+
* class M < C
276+
* end
277+
*
278+
* module M
279+
* include C
280+
* prepend C
281+
* end
282+
* ```
283+
*/
284+
Expr getAnAncestorExpr() {
285+
exists(MethodCall call |
286+
call.getReceiver().(SelfVariableAccess).getVariable() = this.getModuleSelfVariable() and
287+
call.getMethodName() = ["include", "prepend"] and
288+
result = call.getArgument(0) and
289+
scopeOfInclSynth(call) = this // only permit calls directly in the module scope, not in a block
290+
)
291+
or
292+
result = this.(ClassDeclaration).getSuperclassExpr()
293+
}
144294
}
145295

146296
/**

ruby/ql/lib/codeql/ruby/dataflow/SSA.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ module Ssa {
289289
)
290290
}
291291

292-
final override string toString() { result = "<captured>" }
292+
final override string toString() { result = "<captured> " + this.getSourceVariable() }
293293

294294
override Location getLocation() { result = this.getBasicBlock().getLocation() }
295295
}

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

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,27 @@ module LocalFlow {
119119
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
120120
*/
121121
predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
122-
nodeTo = getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
122+
nodeTo = getParameterDefNode(nodeFrom.(ParameterNodeImpl).getParameter())
123123
or
124124
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
125125
}
126126

127+
/**
128+
* Holds if `nodeFrom -> nodeTo` is a step from a parameter to a capture entry node for
129+
* that parameter.
130+
*
131+
* This is intended to recover from flow not currently recognised by ordinary capture flow.
132+
*/
133+
predicate localFlowSsaParamCaptureInput(Node nodeFrom, Node nodeTo) {
134+
exists(Ssa::CapturedEntryDefinition def |
135+
nodeFrom.asParameter().(NamedParameter).getVariable() = def.getSourceVariable()
136+
or
137+
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
138+
|
139+
nodeTo.(SsaDefinitionNode).getDefinition() = def
140+
)
141+
}
142+
127143
/**
128144
* Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
129145
* involving SSA definition `def`.
@@ -309,7 +325,7 @@ private module Cached {
309325
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
310326
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
311327
or
312-
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
328+
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
313329
or
314330
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
315331
or
@@ -338,7 +354,7 @@ private module Cached {
338354
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
339355
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
340356
or
341-
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
357+
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
342358
or
343359
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
344360
or
@@ -349,7 +365,12 @@ private module Cached {
349365
FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _)
350366
}
351367

352-
/** This is the local flow predicate that is used in type tracking. */
368+
/**
369+
* This is the local flow predicate that is used in type tracking.
370+
*
371+
* This needs to exclude `localFlowSsaParamInput` due to a performance trick
372+
* in type tracking, where such steps are treated as call steps.
373+
*/
353374
cached
354375
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
355376
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
@@ -396,7 +417,7 @@ private module Cached {
396417

397418
cached
398419
predicate isLocalSourceNode(Node n) {
399-
n instanceof ParameterNode
420+
n instanceof TParameterNode
400421
or
401422
// Expressions that can't be reached from another entry definition or expression
402423
n instanceof ExprNode and
@@ -1272,7 +1293,7 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
12721293
creation.asExpr() =
12731294
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
12741295
c.asCallable() = mc.getBlock().getExpr() and
1275-
mc.getExpr().getMethodName() = "lambda"
1296+
mc.getExpr().getMethodName() = ["lambda", "proc"]
12761297
)
12771298
)
12781299
}

0 commit comments

Comments
 (0)