Skip to content

Commit e91586a

Browse files
committed
Ruby: move special treatment of Hash.[] into Hash.qll
1 parent befcc23 commit e91586a

File tree

2 files changed

+78
-30
lines changed

2 files changed

+78
-30
lines changed

ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
/** Provides flow summaries for the `Hash` class. */
22

33
private import codeql.ruby.AST
4+
private import codeql.ruby.CFG as Cfg
45
private import codeql.ruby.ApiGraphs
56
private import codeql.ruby.DataFlow
67
private import codeql.ruby.dataflow.FlowSummary
78
private import codeql.ruby.dataflow.internal.DataFlowDispatch
89
private import codeql.ruby.ast.internal.Module
10+
private import codeql.ruby.typetracking.TypeTrackerSpecific
911

1012
/**
1113
* Provides flow summaries for the `Hash` class.
@@ -68,6 +70,50 @@ module Hash {
6870
}
6971
}
7072

73+
/** Holds if `literal` is a call to `Hash.[]` and `argument` is one of its arguments. */
74+
private predicate hashLiteralStore(DataFlow::CallNode literal, DataFlow::Node argument) {
75+
literal.getExprNode().getExpr() = Hash::getAStaticHashCall("[]") and
76+
argument = literal.getArgument(_)
77+
}
78+
79+
/**
80+
* A set of type-tracking steps to replace the `Hash.[]` summary.
81+
*
82+
* The `Hash.[]` method tends to have a large number of summaries, which would result
83+
* in too many unnecessary type-tracking edges, so we specialize it here.
84+
*/
85+
private class HashLiteralTypeTracker extends TypeTrackingStep {
86+
override predicate suppressSummary(SummarizedCallable callable) {
87+
callable instanceof HashLiteralSummary
88+
}
89+
90+
override predicate storeStep(Node pred, TypeTrackingNode succ, TypeTrackerContent content) {
91+
// Store edge: `value -> { key: value }` with content derived from `key`
92+
exists(Cfg::CfgNodes::ExprNodes::PairCfgNode pair |
93+
hashLiteralStore(succ, any(DataFlow::Node n | n.asExpr() = pair)) and
94+
pred.asExpr() = pair.getValue()
95+
|
96+
exists(ConstantValue constant |
97+
constant = pair.getKey().getConstantValue() and
98+
content.isSingleton(DataFlow::Content::getElementContent(constant))
99+
)
100+
or
101+
not exists(pair.getKey().getConstantValue()) and
102+
content.isAnyElement()
103+
)
104+
}
105+
106+
override predicate withContentStep(Node pred, Node succ, ContentFilter filter) {
107+
// `WithContent[element]` edge: `args --> { **args }`.
108+
exists(DataFlow::Node node |
109+
hashLiteralStore(succ, node) and
110+
node.asExpr().getExpr() instanceof HashSplatExpr and
111+
pred.asExpr() = node.asExpr().(Cfg::CfgNodes::ExprNodes::UnaryOperationCfgNode).getOperand() and
112+
filter = ContentFilter::hasElements()
113+
)
114+
}
115+
}
116+
71117
/**
72118
* `Hash[]` called on an existing hash, e.g.
73119
*

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

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ private import codeql.ruby.dataflow.internal.SsaImpl as SsaImpl
1010
private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
1111
private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
1212
private import codeql.ruby.dataflow.internal.AccessPathSyntax
13-
private import codeql.ruby.frameworks.core.Hash
1413

1514
class Node = DataFlowPublic::Node;
1615

@@ -60,6 +59,15 @@ class ContentFilter extends TContentFilter {
6059
}
6160
}
6261

62+
/** Module for getting `ContentFilter` values. */
63+
module ContentFilter {
64+
/** Gets the filter that only allow element contents. */
65+
ContentFilter hasElements() { result = MkElementFilter() }
66+
67+
/** Gets the filter that only allow pair-value contents. */
68+
ContentFilter hasPairValue() { result = MkPairValueFilter() }
69+
}
70+
6371
/**
6472
* Holds if a value stored with `storeContents` can be read back with `loadContents`.
6573
*/
@@ -225,28 +233,9 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet conten
225233
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
226234
)
227235
or
228-
// Hash literals
229-
exists(Cfg::CfgNodes::ExprNodes::PairCfgNode pair |
230-
hashLiteralStore(nodeTo, any(DataFlow::Node n | n.asExpr() = pair)) and
231-
nodeFrom.asExpr() = pair.getValue()
232-
|
233-
exists(ConstantValue constant |
234-
constant = pair.getKey().getConstantValue() and
235-
contents.isSingleton(DataFlow::Content::getElementContent(constant))
236-
)
237-
or
238-
not exists(pair.getKey().getConstantValue()) and
239-
contents.isAnyElement()
240-
)
241-
or
242236
TypeTrackingStep::storeStep(nodeFrom, nodeTo, contents)
243237
}
244238

245-
private predicate hashLiteralStore(DataFlow::CallNode hashCreation, DataFlow::Node argument) {
246-
hashCreation.getExprNode().getExpr() = Hash::getAStaticHashCall("[]") and
247-
argument = hashCreation.getArgument(_)
248-
}
249-
250239
/**
251240
* Holds if a store step `nodeFrom -> nodeTo` with `contents` exists, where the destination node
252241
* is a post-update node that should be treated as a local source node.
@@ -336,14 +325,6 @@ predicate basicWithContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter)
336325
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
337326
)
338327
or
339-
// Hash-splat in a hash literal
340-
exists(DataFlow::Node node |
341-
hashLiteralStore(nodeTo, node) and
342-
node.asExpr().getExpr() instanceof HashSplatExpr and
343-
nodeFrom.asExpr() = node.asExpr().(Cfg::CfgNodes::ExprNodes::UnaryOperationCfgNode).getOperand() and
344-
filter = MkElementFilter()
345-
)
346-
or
347328
TypeTrackingStep::withContentStep(nodeFrom, nodeTo, filter)
348329
}
349330

@@ -361,6 +342,7 @@ private predicate hasStoreSummary(
361342
SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponentStack input,
362343
SummaryComponentStack output
363344
) {
345+
not TypeTrackingStep::suppressSummary(callable) and
364346
callable.propagatesFlow(input, push(SummaryComponent::content(contents), output), true) and
365347
not isNonLocal(input.head()) and
366348
not isNonLocal(output.head())
@@ -371,6 +353,7 @@ private predicate hasLoadSummary(
371353
SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponentStack input,
372354
SummaryComponentStack output
373355
) {
356+
not TypeTrackingStep::suppressSummary(callable) and
374357
callable.propagatesFlow(push(SummaryComponent::content(contents), input), output, true) and
375358
not isNonLocal(input.head()) and
376359
not isNonLocal(output.head())
@@ -381,12 +364,12 @@ private predicate hasLoadStoreSummary(
381364
SummarizedCallable callable, DataFlow::ContentSet loadContents,
382365
DataFlow::ContentSet storeContents, SummaryComponentStack input, SummaryComponentStack output
383366
) {
367+
not TypeTrackingStep::suppressSummary(callable) and
384368
callable
385369
.propagatesFlow(push(SummaryComponent::content(loadContents), input),
386370
push(SummaryComponent::content(storeContents), output), true) and
387371
not isNonLocal(input.head()) and
388-
not isNonLocal(output.head()) and
389-
callable != "Hash.[]" // Special-cased due to having a huge number of summaries
372+
not isNonLocal(output.head())
390373
}
391374

392375
/**
@@ -419,6 +402,7 @@ private predicate hasWithoutContentSummary(
419402
SummaryComponentStack output
420403
) {
421404
exists(DataFlow::ContentSet content |
405+
not TypeTrackingStep::suppressSummary(callable) and
422406
callable.propagatesFlow(push(SummaryComponent::withoutContent(content), input), output, true) and
423407
filter = getFilterFromWithoutContentStep(content) and
424408
not isNonLocal(input.head()) and
@@ -457,6 +441,7 @@ private predicate hasWithContentSummary(
457441
SummaryComponentStack output
458442
) {
459443
exists(DataFlow::ContentSet content |
444+
not TypeTrackingStep::suppressSummary(callable) and
460445
callable.propagatesFlow(push(SummaryComponent::withContent(content), input), output, true) and
461446
filter = getFilterFromWithContentStep(content) and
462447
not isNonLocal(input.head()) and
@@ -501,6 +486,7 @@ private predicate dependsOnSummaryComponentStack(
501486
SummarizedCallable callable, SummaryComponentStack stack
502487
) {
503488
exists(callable.getACallSimple()) and
489+
not TypeTrackingStep::suppressSummary(callable) and
504490
(
505491
callable.propagatesFlow(stack, _, true)
506492
or
@@ -585,6 +571,13 @@ class TypeTrackingStep extends TUnit {
585571
/** Gets the string `"unit"`. */
586572
string toString() { result = "unit" }
587573

574+
/**
575+
* Holds if type-tracking should not attempt to derive steps from (simple) calls to `callable`.
576+
*
577+
* This can be done to manually control how steps are generated from such calls.
578+
*/
579+
predicate suppressSummary(SummarizedCallable callable) { none() }
580+
588581
/**
589582
* Holds if type-tracking should step from `pred` to `succ`.
590583
*/
@@ -623,6 +616,15 @@ class TypeTrackingStep extends TUnit {
623616

624617
/** Provides access to the steps contributed by subclasses of `SharedTypeTrackingStep`. */
625618
module TypeTrackingStep {
619+
/**
620+
* Holds if type-tracking should not attempt to derive steps from (simple) calls to `callable`.
621+
*
622+
* This can be done to manually control how steps are generated from such calls.
623+
*/
624+
predicate suppressSummary(SummarizedCallable callable) {
625+
any(TypeTrackingStep st).suppressSummary(callable)
626+
}
627+
626628
/**
627629
* Holds if type-tracking should step from `pred` to `succ`.
628630
*/

0 commit comments

Comments
 (0)