Skip to content

Commit 62ea1f0

Browse files
committed
Ruby: Fix performance of string comparison guard
The `or` case ran extremely slowly before this change. Also exclude string interpolations from consideration, for correctness, and add some more tests.
1 parent e25e192 commit 62ea1f0

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

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

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ private import codeql.ruby.controlflow.CfgNodes
77
private import codeql.ruby.dataflow.SSA
88
private import codeql.ruby.ast.internal.Constant
99
private import codeql.ruby.InclusionTests
10+
private import codeql.ruby.ast.internal.Literal
1011

1112
private predicate stringConstCompare(CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch) {
1213
exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c |
1314
c = guard and
1415
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
15-
c.getExpr() instanceof EqExpr and branch = true
16+
// Only consider strings without any interpolations
17+
not exists(StringInterpolationComponent comp | comp = strLitNode.getExpr().getComponent(_)) and
18+
c.getExpr() instanceof EqExpr and
19+
branch = true
1620
or
1721
c.getExpr() instanceof CaseEqExpr and branch = true
1822
or
@@ -26,24 +30,43 @@ private predicate stringConstCompare(CfgNodes::AstCfgNode guard, CfgNode testedN
2630
or
2731
stringConstCaseCompare(guard, testedNode, branch)
2832
or
29-
stringConstCompareOr(guard, testedNode, branch)
33+
exists(Ssa::Definition def, CfgNodes::ExprNodes::BinaryOperationCfgNode g |
34+
g = guard and
35+
stringConstCompareOr(guard, def, branch) and
36+
stringConstCompare(g.getLeftOperand(), testedNode, _)
37+
)
3038
or
3139
stringConstCompareAnd(guard, testedNode, branch)
3240
}
3341

42+
/**
43+
* Holds if `guard` is an `or` expression whose operands are string comparison guards that test the same SSA variable.
44+
* `testedNode` is an arbitrary node that is tested by one of the guards.
45+
* For example:
46+
*
47+
* ```rb
48+
* x == "foo" or x == "bar"
49+
* ```
50+
*/
3451
private predicate stringConstCompareOr(
35-
CfgNodes::ExprNodes::BinaryOperationCfgNode guard, CfgNode testedNode, boolean branch
52+
CfgNodes::ExprNodes::BinaryOperationCfgNode guard, Ssa::Definition def, boolean branch
3653
) {
3754
guard.getExpr() instanceof LogicalOrExpr and
3855
branch = true and
39-
exists(Ssa::Definition def |
40-
forall(CfgNode innerGuard | innerGuard = guard.getAnOperand() |
41-
stringConstCompare(innerGuard, def.getARead(), branch)
42-
) and
43-
testedNode = any(CfgNode node | stringConstCompare(guard.getAnOperand(), node, _))
56+
forall(CfgNode innerGuard | innerGuard = guard.getAnOperand() |
57+
stringConstCompare(innerGuard, def.getARead(), branch)
4458
)
4559
}
4660

61+
/**
62+
* Holds if guard is an `and` expression containing a string comparison guard in either operand.
63+
* For example:
64+
*
65+
* ```rb
66+
* x == "foo" and other_condition()
67+
* other_condition() and x == "foo"
68+
* ```
69+
*/
4770
private predicate stringConstCompareAnd(
4871
CfgNodes::ExprNodes::BinaryOperationCfgNode guard, CfgNode testedNode, boolean branch
4972
) {

ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,20 @@
250250
in "foo"
251251
foo
252252
end
253+
254+
if foo == "#{some_method()}"
255+
foo
256+
end
257+
258+
F = "foo"
259+
if foo == "#{F}"
260+
foo # $ MISSING: guarded
261+
end
262+
263+
f = "foo"
264+
if foo == "#{f}"
265+
foo # $ MISSING: guarded
266+
end
267+
268+
foo == "foo" && foo # $ guarded
269+
foo && foo == "foo"

0 commit comments

Comments
 (0)