Skip to content

Commit 9302271

Browse files
committed
Ruby: Hack special-casing of hash literals
1 parent bd11946 commit 9302271

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ module Hash {
4040
* Gets a call to the method `name` invoked on the `Hash` object
4141
* (not on a hash instance).
4242
*/
43-
private MethodCall getAStaticHashCall(string name) {
43+
MethodCall getAStaticHashCall(string name) {
4444
result.getMethodName() = name and
4545
resolveConstantReadAccess(result.getReceiver()) = TResolved("Hash")
4646
}

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ 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
1314

1415
class Node = DataFlowPublic::Node;
1516

@@ -220,6 +221,25 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet conten
220221
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
221222
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
222223
)
224+
or
225+
// Hash literals
226+
exists(Cfg::CfgNodes::ExprNodes::PairCfgNode pair |
227+
hashLiteralStore(nodeTo, any(DataFlow::Node n | n.asExpr() = pair)) and
228+
nodeFrom.asExpr() = pair.getValue()
229+
|
230+
exists(ConstantValue constant |
231+
constant = pair.getKey().getConstantValue() and
232+
contents.isSingleton(DataFlow::Content::getElementContent(constant))
233+
)
234+
or
235+
not exists(pair.getKey().getConstantValue()) and
236+
contents.isAnyElement()
237+
)
238+
}
239+
240+
private predicate hashLiteralStore(DataFlow::CallNode hashCreation, DataFlow::Node argument) {
241+
hashCreation.getExprNode().getExpr() = Hash::getAStaticHashCall("[]") and
242+
argument = hashCreation.getArgument(_)
223243
}
224244

225245
/**
@@ -310,6 +330,14 @@ predicate basicWithContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter)
310330
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
311331
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
312332
)
333+
or
334+
// Hash-splat in a hash literal
335+
exists(DataFlow::Node node |
336+
hashLiteralStore(nodeTo, node) and
337+
node.asExpr().getExpr() instanceof HashSplatExpr and
338+
nodeFrom.asExpr() = node.asExpr().(Cfg::CfgNodes::ExprNodes::UnaryOperationCfgNode).getOperand() and
339+
filter = MkElementFilter()
340+
)
313341
}
314342

315343
/**
@@ -344,7 +372,8 @@ private predicate hasLoadStoreSummary(
344372
) {
345373
callable
346374
.propagatesFlow(push(SummaryComponent::content(loadContents), input),
347-
push(SummaryComponent::content(storeContents), output), true)
375+
push(SummaryComponent::content(storeContents), output), true) and
376+
callable != "Hash.[]" // Special-cased due to having a huge number of summaries
348377
}
349378

350379
/**

0 commit comments

Comments
 (0)