Skip to content

Commit 208851c

Browse files
authored
Merge pull request #7084 from hvitved/ruby/self-flow
Ruby: Cleanup flow through `self`
2 parents 5b97458 + 37f5db5 commit 208851c

25 files changed

+496
-422
lines changed

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

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,8 @@ class Expr extends Stmt, TExpr {
2424
}
2525
}
2626

27-
/**
28-
* A reference to the current object. For example:
29-
* - `self == other`
30-
* - `self.method_name`
31-
* - `def self.method_name ... end`
32-
*
33-
* This also includes implicit references to the current object in method
34-
* calls. For example, the method call `foo(123)` has an implicit `self`
35-
* receiver, and is equivalent to the explicit `self.foo(123)`.
36-
*/
37-
class Self extends Expr, TSelf {
38-
final override string getAPrimaryQlClass() { result = "Self" }
39-
40-
final override string toString() { result = "self" }
41-
}
27+
/** DEPRECATED: Use `SelfVariableAccess` instead. */
28+
deprecated class Self = SelfVariableAccess;
4229

4330
/**
4431
* A sequence of expressions in the right-hand side of an assignment or

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,16 @@ class ClassVariableWriteAccess extends ClassVariableAccess, VariableWriteAccess
201201
/** An access to a class variable where the value is read. */
202202
class ClassVariableReadAccess extends ClassVariableAccess, VariableReadAccess { }
203203

204-
/** An access to the `self` variable */
204+
/**
205+
* An access to the `self` variable. For example:
206+
* - `self == other`
207+
* - `self.method_name`
208+
* - `def self.method_name ... end`
209+
*
210+
* This also includes implicit references to the current object in method
211+
* calls. For example, the method call `foo(123)` has an implicit `self`
212+
* receiver, and is equivalent to the explicit `self.foo(123)`.
213+
*/
205214
class SelfVariableAccess extends LocalVariableAccess instanceof SelfVariableAccessImpl {
206215
final override string getAPrimaryQlClass() { result = "SelfVariableAccess" }
207216
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private module Cached {
7373
m = resolveConstantReadAccess(c.getReceiver())
7474
or
7575
m = enclosingModule(c).getModule() and
76-
c.getReceiver() instanceof Self
76+
c.getReceiver() instanceof SelfVariableAccess
7777
) and
7878
result = resolveConstantReadAccess(c.getAnArgument())
7979
}
@@ -437,7 +437,7 @@ private module ResolveImpl {
437437
encl = enclosingModule(this) and
438438
result = [qualifiedModuleNameNonRec(encl, _, _), qualifiedModuleNameRec(encl, _, _)]
439439
|
440-
this.getReceiver() instanceof Self
440+
this.getReceiver() instanceof SelfVariableAccess
441441
or
442442
not exists(this.getReceiver())
443443
)

ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,23 @@ private module Cached {
366366

367367
cached
368368
predicate isCapturedAccess(LocalVariableAccess access) {
369-
access.getVariable().getDeclaringScope() != access.getCfgScope()
369+
exists(Scope scope1, Scope scope2 |
370+
scope1 = access.getVariable().getDeclaringScope() and
371+
scope2 = access.getCfgScope() and
372+
scope1 != scope2
373+
|
374+
if access instanceof SelfVariableAccess
375+
then
376+
// ```
377+
// class C
378+
// def self.m // not a captured access
379+
// end
380+
// end
381+
// ```
382+
not scope2 instanceof Toplevel or
383+
not access = any(SingletonMethod m).getObject()
384+
else any()
385+
)
370386
}
371387

372388
cached

ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class EntryNode extends CfgNode, TEntryNode {
1414

1515
EntryNode() { this = TEntryNode(scope) }
1616

17-
final override EntryBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
17+
final override EntryBasicBlock getBasicBlock() { result = super.getBasicBlock() }
1818

1919
final override Location getLocation() { result = scope.getLocation() }
2020

@@ -31,7 +31,7 @@ class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
3131
/** Holds if this node represent a normal exit. */
3232
final predicate isNormal() { normal = true }
3333

34-
final override AnnotatedExitBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
34+
final override AnnotatedExitBasicBlock getBasicBlock() { result = super.getBasicBlock() }
3535

3636
final override Location getLocation() { result = scope.getLocation() }
3737

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ module Ssa {
233233
)
234234
}
235235

236+
override SelfVariable getSourceVariable() { result = v }
237+
236238
final override string toString() { result = "self (" + v.getDeclaringScope() + ")" }
237239

238240
final override Location getLocation() { result = this.getControlFlowNode().getLocation() }
@@ -314,7 +316,7 @@ module Ssa {
314316
CapturedCallDefinition() {
315317
exists(Variable v, BasicBlock bb, int i |
316318
this.definesAt(v, bb, i) and
317-
SsaImpl::capturedCallWrite(bb, i, v)
319+
SsaImpl::capturedCallWrite(_, bb, i, v)
318320
)
319321
}
320322

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

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ private module Cached {
203203
result = lookupMethod(tp, method) and
204204
if result.(Method).isPrivate()
205205
then
206-
exists(Self self |
206+
exists(SelfVariableAccess self |
207207
self = call.getReceiver().getExpr() and
208208
pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) =
209209
pragma[only_bind_out](result.getEnclosingModule().getModule())
@@ -232,6 +232,18 @@ private module Cached {
232232
)
233233
}
234234

235+
/** Gets a viable run-time target for the call `call`. */
236+
cached
237+
DataFlowCallable viableCallable(DataFlowCall call) {
238+
result = TCfgScope(getTarget(call.asCall())) and
239+
not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
240+
or
241+
exists(LibraryCallable callable |
242+
result = TLibraryCallable(callable) and
243+
call.asCall().getExpr() = callable.getACall()
244+
)
245+
}
246+
235247
cached
236248
newtype TArgumentPosition =
237249
TSelfArgumentPosition() or
@@ -300,28 +312,14 @@ private DataFlow::LocalSourceNode trackInstance(Module tp, TypeTracker t) {
300312
)
301313
or
302314
// `self` in method
303-
exists(Self self, Method enclosing |
304-
self = result.asExpr().getExpr() and
305-
enclosing = self.getEnclosingMethod() and
306-
tp = enclosing.getEnclosingModule().getModule() and
307-
not self.getEnclosingModule().getEnclosingMethod() = enclosing
308-
)
315+
tp = result.(SsaSelfDefinitionNode).getSelfScope().(Method).getEnclosingModule().getModule()
309316
or
310317
// `self` in singleton method
311-
exists(Self self, MethodBase enclosing |
312-
self = result.asExpr().getExpr() and
313-
flowsToSingletonMethodObject(trackInstance(tp), enclosing) and
314-
enclosing = self.getEnclosingMethod() and
315-
not self.getEnclosingModule().getEnclosingMethod() = enclosing
316-
)
318+
flowsToSingletonMethodObject(trackInstance(tp), result.(SsaSelfDefinitionNode).getSelfScope())
317319
or
318320
// `self` in top-level
319-
exists(Self self, Toplevel enclosing |
320-
self = result.asExpr().getExpr() and
321-
enclosing = self.getEnclosingModule() and
322-
tp = TResolved("Object") and
323-
not self.getEnclosingMethod().getEnclosingModule() = enclosing
324-
)
321+
result.(SsaSelfDefinitionNode).getSelfScope() instanceof Toplevel and
322+
tp = TResolved("Object")
325323
or
326324
// a module or class
327325
exists(Module m |
@@ -371,7 +369,7 @@ private predicate singletonMethod(MethodBase method, Expr object) {
371369

372370
pragma[nomagic]
373371
private predicate flowsToSingletonMethodObject(DataFlow::LocalSourceNode nodeFrom, MethodBase method) {
374-
exists(DataFlow::LocalSourceNode nodeTo |
372+
exists(DataFlow::Node nodeTo |
375373
nodeFrom.flowsTo(nodeTo) and
376374
singletonMethod(method, nodeTo.asExpr().getExpr())
377375
)
@@ -409,13 +407,8 @@ private DataFlow::LocalSourceNode trackSingletonMethod(MethodBase m, string name
409407
name = m.getName()
410408
}
411409

412-
private DataFlow::Node selfInModule(Module tp) {
413-
exists(Self self, ModuleBase enclosing |
414-
self = result.asExpr().getExpr() and
415-
enclosing = self.getEnclosingModule() and
416-
tp = enclosing.getModule() and
417-
not self.getEnclosingMethod().getEnclosingModule() = enclosing
418-
)
410+
private SsaSelfDefinitionNode selfInModule(Module tp) {
411+
tp = result.getSelfScope().(ModuleBase).getModule()
419412
}
420413

421414
private DataFlow::LocalSourceNode trackModule(Module tp, TypeTracker t) {
@@ -442,17 +435,6 @@ private DataFlow::LocalSourceNode trackModule(Module tp) {
442435
result = trackModule(tp, TypeTracker::end())
443436
}
444437

445-
/** Gets a viable run-time target for the call `call`. */
446-
DataFlowCallable viableCallable(DataFlowCall call) {
447-
result = TCfgScope(getTarget(call.asCall())) and
448-
not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
449-
or
450-
exists(LibraryCallable callable |
451-
result = TLibraryCallable(callable) and
452-
call.asCall().getExpr() = callable.getACall()
453-
)
454-
}
455-
456438
/**
457439
* Holds if the set of viable implementations that can be called by `call`
458440
* might be improved by knowing the call context. This is the case if the

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

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ module LocalFlow {
7070
)
7171
}
7272

73+
/** Gets the SSA definition node corresponding to the implicit `self` parameter for `m`. */
74+
private SsaDefinitionNode getSelfParameterDefNode(MethodBase m) {
75+
result.getDefinition().(Ssa::SelfDefinition).getSourceVariable().getDeclaringScope() = m
76+
}
77+
78+
/**
79+
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
80+
*/
81+
predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
82+
nodeTo = getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
83+
or
84+
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
85+
}
86+
7387
/**
7488
* Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
7589
* involving SSA definition `def`.
@@ -115,9 +129,6 @@ module LocalFlow {
115129
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
116130
localSsaFlowStep(nodeFrom, nodeTo)
117131
or
118-
nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and
119-
nodeTo.asExpr().getExpr() instanceof Self
120-
or
121132
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs()
122133
or
123134
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue()
@@ -236,7 +247,7 @@ private module Cached {
236247
or
237248
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
238249
or
239-
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
250+
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
240251
or
241252
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
242253
or
@@ -253,7 +264,7 @@ private module Cached {
253264
or
254265
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
255266
or
256-
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
267+
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
257268
or
258269
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
259270
or
@@ -275,27 +286,34 @@ private module Cached {
275286
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
276287
}
277288

289+
private predicate entrySsaDefinition(SsaDefinitionNode n) {
290+
n = LocalFlow::getParameterDefNode(_)
291+
or
292+
exists(Ssa::Definition def | def = n.getDefinition() |
293+
def instanceof Ssa::SelfDefinition
294+
or
295+
def instanceof Ssa::CapturedEntryDefinition
296+
)
297+
}
298+
278299
cached
279300
predicate isLocalSourceNode(Node n) {
280301
n instanceof ParameterNode
281302
or
282-
// This case should not be needed once we have proper use-use flow
283-
// for `self`. At that point, the `self`s returned by `trackInstance`
284-
// in `DataFlowDispatch.qll` should refer to the post-update node,
285-
// and we can remove this case.
286-
n.asExpr().getExpr() instanceof Self
303+
n instanceof PostUpdateNodes::ExprPostUpdateNode
287304
or
288-
// Nodes that can't be reached from another parameter or expression.
289-
not localFlowStepTypeTracker+(any(Node e |
290-
e instanceof ExprNode
305+
// Expressions that can't be reached from another entry definition or expression.
306+
not localFlowStepTypeTracker+(any(Node n0 |
307+
n0 instanceof ExprNode
291308
or
292-
e instanceof ParameterNode
293-
), n)
309+
entrySsaDefinition(n0)
310+
), n.(ExprNode))
294311
or
295-
// Ensure all parameter SSA nodes are local sources -- this is needed by type tracking.
296-
// Note that when the parameter has a default value, it will be reachable from an
297-
// expression (the default value) and therefore won't be caught by the rule above.
298-
n = LocalFlow::getParameterDefNode(_)
312+
// Ensure all entry SSA definitions are local sources -- for parameters, this
313+
// is needed by type tracking. Note that when the parameter has a default value,
314+
// it will be reachable from an expression (the default value) and therefore
315+
// won't be caught by the rule above.
316+
entrySsaDefinition(n)
299317
}
300318

301319
cached
@@ -358,6 +376,16 @@ class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
358376
override string toStringImpl() { result = def.toString() }
359377
}
360378

379+
/** An SSA definition for a `self` variable. */
380+
class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionNode {
381+
private SelfVariable self;
382+
383+
SsaSelfDefinitionNode() { self = def.getSourceVariable() }
384+
385+
/** Gets the scope in which the `self` variable is declared. */
386+
Scope getSelfScope() { result = self.getDeclaringScope() }
387+
}
388+
361389
/**
362390
* A value returning statement, viewed as a node in a data flow graph.
363391
*
@@ -745,13 +773,6 @@ predicate jumpStep(Node pred, Node succ) {
745773
SsaImpl::captureFlowOut(pred.(SsaDefinitionNode).getDefinition(),
746774
succ.(SsaDefinitionNode).getDefinition())
747775
or
748-
exists(Self s, Method m |
749-
s = succ.asExpr().getExpr() and
750-
pred.(SelfParameterNode).getMethod() = m and
751-
m = s.getEnclosingMethod() and
752-
m != s.getEnclosingCallable()
753-
)
754-
or
755776
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
756777
}
757778

0 commit comments

Comments
 (0)