Skip to content

Commit deaa37d

Browse files
committed
Ruby: Include more (hash)splat flow in type tracking
1 parent da05e3e commit deaa37d

File tree

3 files changed

+257
-111
lines changed

3 files changed

+257
-111
lines changed

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

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,7 @@ private module Cached {
377377

378378
class TSourceParameterNode =
379379
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
380-
TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode or
381-
TSynthSplatParameterElementNode;
380+
TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode;
382381

383382
cached
384383
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
@@ -480,8 +479,11 @@ private module Cached {
480479
entrySsaDefinition(n) and
481480
not LocalFlow::localFlowSsaParamInput(_, n)
482481
or
483-
// Needed for stores in type tracking
484482
TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _)
483+
or
484+
TypeTrackerSpecific::readStepIntoSourceNode(_, n, _)
485+
or
486+
TypeTrackerSpecific::readStoreStepIntoSourceNode(_, n, _, _)
485487
}
486488

487489
cached
@@ -936,7 +938,7 @@ private module ParameterNodes {
936938
* A node that holds the content of a specific positional argument.
937939
* See `SynthSplatArgumentNode` for more information.
938940
*/
939-
class SynthSplatParameterElementNode extends ParameterNodeImpl, TSynthSplatParameterElementNode {
941+
class SynthSplatParameterElementNode extends NodeImpl, TSynthSplatParameterElementNode {
940942
private DataFlowCallable callable;
941943
private int pos;
942944

@@ -957,10 +959,6 @@ private module ParameterNodes {
957959
)
958960
}
959961

960-
final override Parameter getParameter() { none() }
961-
962-
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition p) { none() }
963-
964962
final override CfgScope getCfgScope() { result = callable.asCallable() }
965963

966964
final override DataFlowCallable getEnclosingCallable() { result = callable }
@@ -1369,7 +1367,7 @@ private ContentSet getKeywordContent(string name) {
13691367
)
13701368
}
13711369

1372-
private ContentSet getPositionalContent(int n) {
1370+
ContentSet getPositionalContent(int n) {
13731371
exists(ConstantValue::ConstantIntegerValue i |
13741372
result.isSingleton(TKnownElementContent(i)) and
13751373
i.isInt(n)
@@ -1400,18 +1398,13 @@ predicate storeStepCommon(Node node1, ContentSet c, Node node2) {
14001398
)
14011399
)
14021400
or
1401+
// Wrap all positional arguments in a synthesized splat argument node
14031402
exists(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos |
14041403
node2 = TSynthSplatArgumentNode(call) and
14051404
node1.asExpr().(Argument).isArgumentOf(call, pos)
14061405
|
14071406
exists(int n | pos.isPositional(n) and c = getPositionalContent(n))
14081407
)
1409-
or
1410-
node1 =
1411-
any(SynthSplatParameterElementNode elemNode |
1412-
node2 = elemNode.getSplatParameterNode(_) and
1413-
c = getPositionalContent(elemNode.getStorePosition())
1414-
)
14151408
}
14161409

14171410
/**
@@ -1445,9 +1438,24 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
14451438
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
14461439
node2.(FlowSummaryNode).getSummaryNode())
14471440
or
1441+
node1 =
1442+
any(SynthSplatParameterElementNode elemNode |
1443+
node2 = elemNode.getSplatParameterNode(_) and
1444+
c = getPositionalContent(elemNode.getStorePosition())
1445+
)
1446+
or
14481447
storeStepCommon(node1, c, node2)
14491448
}
14501449

1450+
/**
1451+
* Subset of `readStep` that should be shared with type-tracking.
1452+
*/
1453+
predicate readStepCommon(Node node1, ContentSet c, Node node2) {
1454+
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
1455+
or
1456+
node2 = node1.(SynthSplatParameterNode).getAParameter(c)
1457+
}
1458+
14511459
/**
14521460
* Holds if there is a read step of content `c` from `node1` to `node2`.
14531461
*/
@@ -1475,19 +1483,17 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
14751483
))
14761484
)
14771485
or
1478-
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
1479-
or
14801486
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
14811487
node2.(FlowSummaryNode).getSummaryNode())
14821488
or
1483-
node2 = node1.(SynthSplatParameterNode).getAParameter(c)
1484-
or
14851489
// Read from SynthSplatArgParameterNode into SynthSplatParameterElementNode
14861490
node2 =
14871491
any(SynthSplatParameterElementNode e |
14881492
node1.(SynthSplatArgParameterNode).isParameterOf(e.getEnclosingCallable(), _) and
14891493
c = getPositionalContent(e.getReadPosition())
14901494
)
1495+
or
1496+
readStepCommon(node1, c, node2)
14911497
}
14921498

14931499
/**

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ predicate storeStepIntoSourceNode(Node nodeFrom, Node nodeTo, DataFlow::ContentS
295295
* Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
296296
*/
297297
predicate basicLoadStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) {
298+
readStepIntoSourceNode(nodeFrom, nodeTo, contents)
299+
or
298300
exists(ExprNodes::MethodCallCfgNode call |
299301
call.getExpr().getNumberOfArguments() = 0 and
300302
contents.isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName())) and
@@ -305,15 +307,42 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet content
305307
TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, contents)
306308
}
307309

310+
/**
311+
* Holds if a read step `nodeFrom -> nodeTo` with `contents` exists, where the destination node
312+
* should be treated as a local source node.
313+
*/
314+
predicate readStepIntoSourceNode(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) {
315+
DataFlowPrivate::readStepCommon(nodeFrom, contents, nodeTo)
316+
}
317+
308318
/**
309319
* Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`.
310320
*/
311321
predicate basicLoadStoreStep(
312322
Node nodeFrom, Node nodeTo, DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent
313323
) {
324+
readStoreStepIntoSourceNode(nodeFrom, nodeTo, loadContent, storeContent)
325+
or
314326
TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent)
315327
}
316328

329+
/**
330+
* Holds if a read+store step `nodeFrom -> nodeTo`, where the destination node
331+
* should be treated as a local source node.
332+
*/
333+
predicate readStoreStepIntoSourceNode(
334+
Node nodeFrom, Node nodeTo, DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent
335+
) {
336+
exists(DataFlowPrivate::SynthSplatParameterElementNode mid |
337+
nodeFrom
338+
.(DataFlowPrivate::SynthSplatArgParameterNode)
339+
.isParameterOf(mid.getEnclosingCallable(), _) and
340+
loadContent = DataFlowPrivate::getPositionalContent(mid.getReadPosition()) and
341+
nodeTo = mid.getSplatParameterNode(_) and
342+
storeContent = DataFlowPrivate::getPositionalContent(mid.getStorePosition())
343+
)
344+
}
345+
317346
/**
318347
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here.
319348
*/

0 commit comments

Comments
 (0)