Skip to content

Commit 32c93e7

Browse files
committed
Include simple interpolations in getValueText
When calculating `StringlikeLiteral.getValueText`, include results from interpolations where we can determine their string value. For example: b = "b" # local variable D = "d" # constant "a#{b}c" # getValueText() = "abc" "a#{b}c{D}" # getValueText() = "abcd" /#a#{b}c{D}/ # getValueText() = "abcd"
1 parent 3df3fb0 commit 32c93e7

File tree

10 files changed

+411
-131
lines changed

10 files changed

+411
-131
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ private import codeql.ruby.security.performance.RegExpTreeView as RETV
33
private import internal.AST
44
private import internal.Scope
55
private import internal.TreeSitter
6+
private import codeql.ruby.controlflow.CfgNodes
67

78
/**
89
* A literal.
@@ -411,14 +412,14 @@ class StringlikeLiteral extends Literal, TStringlikeLiteral {
411412
}
412413

413414
override string getValueText() {
414-
// 0 components should result in the empty string
415-
// if there are any interpolations, there should be no result
416-
// otherwise, concatenate all the components
417415
forall(StringComponent c | c = this.getComponent(_) |
418416
not c instanceof StringInterpolationComponent
419417
) and
420418
result =
421419
concat(StringComponent c, int i | c = this.getComponent(i) | c.getValueText() order by i)
420+
or
421+
exists(this.getComponent(_)) and
422+
result = this.getAControlFlowNode().(ExprNodes::StringlikeLiteralCfgNode).getValueText()
422423
}
423424

424425
override string toString() {

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ class ReturningCfgNode extends AstCfgNode {
128128
/** A control-flow node that wraps a `StringComponent` AST expression. */
129129
class StringComponentCfgNode extends AstCfgNode {
130130
StringComponentCfgNode() { this.getNode() instanceof StringComponent }
131+
132+
string getValueText() { result = this.getNode().(StringComponent).getValueText() }
131133
}
132134

133135
private Expr desugar(Expr n) {
@@ -463,8 +465,13 @@ module ExprNodes {
463465
}
464466

465467
/** A control-flow node that wraps a `StringInterpolationComponent` AST expression. */
466-
class StringInterpolationComponentCfgNode extends StmtSequenceCfgNode {
468+
class StringInterpolationComponentCfgNode extends StringComponentCfgNode, StmtSequenceCfgNode {
467469
StringInterpolationComponentCfgNode() { this.getNode() instanceof StringInterpolationComponent }
470+
471+
// If last statement in the interpolation is a constant or local variable read,
472+
// we attempt to look up its string value.
473+
// If there's a result, we return that as the string value of the interpolation.
474+
final override string getValueText() { result = this.getLastStmt().getValueText() }
468475
}
469476

470477
private class StringlikeLiteralChildMapping extends ExprChildMapping, StringlikeLiteral {
@@ -477,8 +484,36 @@ module ExprNodes {
477484

478485
final override StringlikeLiteral getExpr() { result = super.getExpr() }
479486

487+
/** Gets the `n`th component of this `StringlikeLiteral` */
488+
StringComponentCfgNode getComponent(int n) { result.getNode() = e.getComponent(n) }
489+
480490
/** Gets a component of this `StringlikeLiteral` */
481-
StringComponentCfgNode getAComponent() { e.hasCfgChild(e.getComponent(_), this, result) }
491+
StringComponentCfgNode getAComponent() { result = this.getComponent(_) }
492+
493+
// 0 components results in the empty string
494+
// if all interpolations have a known string value, we will get a result
495+
language[monotonicAggregates]
496+
override string getValueText() {
497+
result = e.getValueText()
498+
or
499+
result =
500+
concat(StringComponent c, int i |
501+
c = e.getComponent(i)
502+
|
503+
getComponentValueText(c) order by i
504+
)
505+
}
506+
507+
/**
508+
* Get the `ValueText()` of a `StringComponent`.
509+
* If the component has a CFG node, defer to that (in order to resolve variables in interpolations).
510+
* Otherwise, defer to the AST node.
511+
*/
512+
private string getComponentValueText(StringComponent c) {
513+
exists(StringComponentCfgNode n | n.getNode() = c | result = n.getValueText())
514+
or
515+
not exists(StringComponentCfgNode n | n.getNode() = c) and result = c.getValueText()
516+
}
482517
}
483518

484519
/** A control-flow node that wraps a `StringLiteral` AST expression. */

ruby/ql/test/library-tests/ast/Ast.expected

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,22 @@ literals/literals.rb:
15151515
# 66| getStmt: [AddExpr] ... + ...
15161516
# 66| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1
15171517
# 66| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 9
1518+
# 67| getStmt: [AssignExpr] ... = ...
1519+
# 67| getAnOperand/getLeftOperand: [LocalVariableAccess] bar
1520+
# 67| getAnOperand/getRightOperand: [StringLiteral] "bar"
1521+
# 67| getComponent: [StringTextComponent] bar
1522+
# 68| getStmt: [AssignExpr] ... = ...
1523+
# 68| getAnOperand/getLeftOperand: [ConstantAssignment] BAR
1524+
# 68| getAnOperand/getRightOperand: [StringLiteral] "bar"
1525+
# 68| getComponent: [StringTextComponent] bar
1526+
# 69| getStmt: [StringLiteral] "foo #{...}"
1527+
# 69| getComponent: [StringTextComponent] foo
1528+
# 69| getComponent: [StringInterpolationComponent] #{...}
1529+
# 69| getStmt: [LocalVariableAccess] bar
1530+
# 70| getStmt: [StringLiteral] "foo #{...}"
1531+
# 70| getComponent: [StringTextComponent] foo
1532+
# 70| getComponent: [StringInterpolationComponent] #{...}
1533+
# 70| getStmt: [ConstantReadAccess] BAR
15181534
# 73| getStmt: [CharacterLiteral] ?x
15191535
# 74| getStmt: [CharacterLiteral] ?\n
15201536
# 75| getStmt: [CharacterLiteral] ?\s
@@ -1539,14 +1555,20 @@ literals/literals.rb:
15391555
# 89| getComponent: [StringTextComponent] wibble
15401556
# 90| getStmt: [SymbolLiteral] :"wibble wobble"
15411557
# 90| getComponent: [StringTextComponent] wibble wobble
1542-
# 91| getStmt: [SymbolLiteral] :"foo_#{...}"
1558+
# 91| getStmt: [SymbolLiteral] :"foo_#{...}_#{...}_#{...}"
15431559
# 91| getComponent: [StringTextComponent] foo_
15441560
# 91| getComponent: [StringInterpolationComponent] #{...}
15451561
# 91| getStmt: [AddExpr] ... + ...
15461562
# 91| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2
15471563
# 91| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2
1548-
# 92| getStmt: [SymbolLiteral] :"foo_#{ 1 + 1 }"
1549-
# 92| getComponent: [StringTextComponent] foo_#{ 1 + 1 }
1564+
# 91| getComponent: [StringTextComponent] _
1565+
# 91| getComponent: [StringInterpolationComponent] #{...}
1566+
# 91| getStmt: [LocalVariableAccess] bar
1567+
# 91| getComponent: [StringTextComponent] _
1568+
# 91| getComponent: [StringInterpolationComponent] #{...}
1569+
# 91| getStmt: [ConstantReadAccess] BAR
1570+
# 92| getStmt: [SymbolLiteral] :"foo_#{ 2 + 2}_#{bar}_#{BAR}"
1571+
# 92| getComponent: [StringTextComponent] foo_#{ 2 + 2}_#{bar}_#{BAR}
15501572
# 93| getStmt: [SymbolLiteral] :"foo_#{ 3 - 2 }"
15511573
# 93| getComponent: [StringTextComponent] foo_#{ 3 - 2 }
15521574
# 96| getStmt: [ArrayLiteral] [...]
@@ -1589,13 +1611,23 @@ literals/literals.rb:
15891611
# 105| getStmt: [AddExpr] ... + ...
15901612
# 105| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1
15911613
# 105| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
1614+
# 105| getElement: [StringLiteral] "#{...}"
1615+
# 105| getComponent: [StringInterpolationComponent] #{...}
1616+
# 105| getStmt: [LocalVariableAccess] bar
1617+
# 105| getElement: [StringLiteral] "#{...}"
1618+
# 105| getComponent: [StringInterpolationComponent] #{...}
1619+
# 105| getStmt: [ConstantReadAccess] BAR
15921620
# 105| getElement: [StringLiteral] "baz"
15931621
# 105| getComponent: [StringTextComponent] baz
15941622
# 106| getStmt: [ArrayLiteral] %w(...)
15951623
# 106| getElement: [StringLiteral] "foo"
15961624
# 106| getComponent: [StringTextComponent] foo
15971625
# 106| getElement: [StringLiteral] "bar#{1+1}"
15981626
# 106| getComponent: [StringTextComponent] bar#{1+1}
1627+
# 106| getElement: [StringLiteral] "#{bar}"
1628+
# 106| getComponent: [StringTextComponent] #{bar}
1629+
# 106| getElement: [StringLiteral] "#{BAR}"
1630+
# 106| getComponent: [StringTextComponent] #{BAR}
15991631
# 106| getElement: [StringLiteral] "baz"
16001632
# 106| getComponent: [StringTextComponent] baz
16011633
# 109| getStmt: [ArrayLiteral] %i(...)
@@ -1622,6 +1654,12 @@ literals/literals.rb:
16221654
# 112| getStmt: [AddExpr] ... + ...
16231655
# 112| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2
16241656
# 112| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4
1657+
# 112| getElement: [SymbolLiteral] :"#{...}"
1658+
# 112| getComponent: [StringInterpolationComponent] #{...}
1659+
# 112| getStmt: [LocalVariableAccess] bar
1660+
# 112| getElement: [SymbolLiteral] :"#{...}"
1661+
# 112| getComponent: [StringInterpolationComponent] #{...}
1662+
# 112| getStmt: [ConstantReadAccess] BAR
16251663
# 112| getElement: [SymbolLiteral] :"baz"
16261664
# 112| getComponent: [StringTextComponent] baz
16271665
# 113| getStmt: [ArrayLiteral] %i(...)
@@ -1637,6 +1675,10 @@ literals/literals.rb:
16371675
# 113| getComponent: [StringTextComponent] 4
16381676
# 113| getElement: [SymbolLiteral] :"}"
16391677
# 113| getComponent: [StringTextComponent] }
1678+
# 113| getElement: [SymbolLiteral] :"#{bar}"
1679+
# 113| getComponent: [StringTextComponent] #{bar}
1680+
# 113| getElement: [SymbolLiteral] :"#{BAR}"
1681+
# 113| getComponent: [StringTextComponent] #{BAR}
16401682
# 113| getElement: [SymbolLiteral] :"baz"
16411683
# 113| getComponent: [StringTextComponent] baz
16421684
# 116| getStmt: [HashLiteral] {...}
@@ -1656,7 +1698,7 @@ literals/literals.rb:
16561698
# 118| getKey: [SymbolLiteral] :foo
16571699
# 118| getValue: [IntegerLiteral] 7
16581700
# 118| getElement: [HashSplatExpr] ** ...
1659-
# 118| getAnOperand/getOperand/getReceiver: [MethodCall] call to bar
1701+
# 118| getAnOperand/getOperand/getReceiver: [MethodCall] call to baz
16601702
# 118| getReceiver: [Self, SelfVariableAccess] self
16611703
# 121| getStmt: [ParenthesizedExpr] ( ... )
16621704
# 121| getStmt: [RangeLiteral] _ .. _
@@ -1692,12 +1734,18 @@ literals/literals.rb:
16921734
# 130| getComponent: [StringTextComponent] ls -l
16931735
# 131| getStmt: [SubshellLiteral] `ls -l`
16941736
# 131| getComponent: [StringTextComponent] ls -l
1695-
# 132| getStmt: [SubshellLiteral] `du -d #{...}`
1737+
# 132| getStmt: [SubshellLiteral] `du -d #{...} #{...} #{...}`
16961738
# 132| getComponent: [StringTextComponent] du -d
16971739
# 132| getComponent: [StringInterpolationComponent] #{...}
16981740
# 132| getStmt: [AddExpr] ... + ...
16991741
# 132| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1
17001742
# 132| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
1743+
# 132| getComponent: [StringTextComponent]
1744+
# 132| getComponent: [StringInterpolationComponent] #{...}
1745+
# 132| getStmt: [LocalVariableAccess] bar
1746+
# 132| getComponent: [StringTextComponent]
1747+
# 132| getComponent: [StringInterpolationComponent] #{...}
1748+
# 132| getStmt: [ConstantReadAccess] BAR
17011749
# 133| getStmt: [SubshellLiteral] `du -d #{...}`
17021750
# 133| getComponent: [StringTextComponent] du -d
17031751
# 133| getComponent: [StringInterpolationComponent] #{...}
@@ -1732,13 +1780,31 @@ literals/literals.rb:
17321780
# 139| getComponent: [StringEscapeSequenceComponent] \s
17331781
# 139| getComponent: [StringTextComponent] bar
17341782
# 139| getComponent: [StringEscapeSequenceComponent] \S
1735-
# 140| getStmt: [RegExpLiteral] /foo#{...}bar/
1783+
# 140| getStmt: [RegExpLiteral] /foo#{...}bar#{...}#{...}/
1784+
# 140| getParsed: [RegExpSequence] foo2barbarbar
1785+
# 140| 0: [RegExpConstant, RegExpNormalChar] f
1786+
# 140| 1: [RegExpConstant, RegExpNormalChar] o
1787+
# 140| 2: [RegExpConstant, RegExpNormalChar] o
1788+
# 140| 3: [RegExpConstant, RegExpNormalChar] 2
1789+
# 140| 4: [RegExpConstant, RegExpNormalChar] b
1790+
# 140| 5: [RegExpConstant, RegExpNormalChar] a
1791+
# 140| 6: [RegExpConstant, RegExpNormalChar] r
1792+
# 140| 7: [RegExpConstant, RegExpNormalChar] b
1793+
# 140| 8: [RegExpConstant, RegExpNormalChar] a
1794+
# 140| 9: [RegExpConstant, RegExpNormalChar] r
1795+
# 140| 10: [RegExpConstant, RegExpNormalChar] b
1796+
# 140| 11: [RegExpConstant, RegExpNormalChar] a
1797+
# 140| 12: [RegExpConstant, RegExpNormalChar] r
17361798
# 140| getComponent: [StringTextComponent] foo
17371799
# 140| getComponent: [StringInterpolationComponent] #{...}
17381800
# 140| getStmt: [AddExpr] ... + ...
17391801
# 140| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1
17401802
# 140| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
17411803
# 140| getComponent: [StringTextComponent] bar
1804+
# 140| getComponent: [StringInterpolationComponent] #{...}
1805+
# 140| getStmt: [LocalVariableAccess] bar
1806+
# 140| getComponent: [StringInterpolationComponent] #{...}
1807+
# 140| getStmt: [ConstantReadAccess] BAR
17421808
# 141| getStmt: [RegExpLiteral] /foo/
17431809
# 141| getParsed: [RegExpSequence] foo
17441810
# 141| 0: [RegExpConstant, RegExpNormalChar] f
@@ -1773,13 +1839,31 @@ literals/literals.rb:
17731839
# 145| getComponent: [StringEscapeSequenceComponent] \s
17741840
# 145| getComponent: [StringTextComponent] bar
17751841
# 145| getComponent: [StringEscapeSequenceComponent] \S
1776-
# 146| getStmt: [RegExpLiteral] /foo#{...}bar/
1842+
# 146| getStmt: [RegExpLiteral] /foo#{...}bar#{...}#{...}/
1843+
# 146| getParsed: [RegExpSequence] foo2barbarbar
1844+
# 146| 0: [RegExpConstant, RegExpNormalChar] f
1845+
# 146| 1: [RegExpConstant, RegExpNormalChar] o
1846+
# 146| 2: [RegExpConstant, RegExpNormalChar] o
1847+
# 146| 3: [RegExpConstant, RegExpNormalChar] 2
1848+
# 146| 4: [RegExpConstant, RegExpNormalChar] b
1849+
# 146| 5: [RegExpConstant, RegExpNormalChar] a
1850+
# 146| 6: [RegExpConstant, RegExpNormalChar] r
1851+
# 146| 7: [RegExpConstant, RegExpNormalChar] b
1852+
# 146| 8: [RegExpConstant, RegExpNormalChar] a
1853+
# 146| 9: [RegExpConstant, RegExpNormalChar] r
1854+
# 146| 10: [RegExpConstant, RegExpNormalChar] b
1855+
# 146| 11: [RegExpConstant, RegExpNormalChar] a
1856+
# 146| 12: [RegExpConstant, RegExpNormalChar] r
17771857
# 146| getComponent: [StringTextComponent] foo
17781858
# 146| getComponent: [StringInterpolationComponent] #{...}
17791859
# 146| getStmt: [AddExpr] ... + ...
17801860
# 146| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1
17811861
# 146| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
17821862
# 146| getComponent: [StringTextComponent] bar
1863+
# 146| getComponent: [StringInterpolationComponent] #{...}
1864+
# 146| getStmt: [LocalVariableAccess] bar
1865+
# 146| getComponent: [StringInterpolationComponent] #{...}
1866+
# 146| getStmt: [ConstantReadAccess] BAR
17831867
# 147| getStmt: [RegExpLiteral] /foo/
17841868
# 147| getParsed: [RegExpSequence] foo
17851869
# 147| 0: [RegExpConstant, RegExpNormalChar] f

ruby/ql/test/library-tests/ast/AstDesugar.expected

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,12 @@ literals/literals.rb:
350350
# 105| getStmt: [AddExpr] ... + ...
351351
# 105| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1
352352
# 105| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
353+
# 105| getArgument: [StringLiteral] "#{...}"
354+
# 105| getComponent: [StringInterpolationComponent] #{...}
355+
# 105| getStmt: [LocalVariableAccess] bar
356+
# 105| getArgument: [StringLiteral] "#{...}"
357+
# 105| getComponent: [StringInterpolationComponent] #{...}
358+
# 105| getStmt: [ConstantReadAccess] BAR
353359
# 105| getArgument: [StringLiteral] "baz"
354360
# 105| getComponent: [StringTextComponent] baz
355361
# 106| [ArrayLiteral] %w(...)
@@ -359,6 +365,10 @@ literals/literals.rb:
359365
# 106| getComponent: [StringTextComponent] foo
360366
# 106| getArgument: [StringLiteral] "bar#{1+1}"
361367
# 106| getComponent: [StringTextComponent] bar#{1+1}
368+
# 106| getArgument: [StringLiteral] "#{bar}"
369+
# 106| getComponent: [StringTextComponent] #{bar}
370+
# 106| getArgument: [StringLiteral] "#{BAR}"
371+
# 106| getComponent: [StringTextComponent] #{BAR}
362372
# 106| getArgument: [StringLiteral] "baz"
363373
# 106| getComponent: [StringTextComponent] baz
364374
# 109| [ArrayLiteral] %i(...)
@@ -393,6 +403,12 @@ literals/literals.rb:
393403
# 112| getStmt: [AddExpr] ... + ...
394404
# 112| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2
395405
# 112| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4
406+
# 112| getArgument: [SymbolLiteral] :"#{...}"
407+
# 112| getComponent: [StringInterpolationComponent] #{...}
408+
# 112| getStmt: [LocalVariableAccess] bar
409+
# 112| getArgument: [SymbolLiteral] :"#{...}"
410+
# 112| getComponent: [StringInterpolationComponent] #{...}
411+
# 112| getStmt: [ConstantReadAccess] BAR
396412
# 112| getArgument: [SymbolLiteral] :"baz"
397413
# 112| getComponent: [StringTextComponent] baz
398414
# 113| [ArrayLiteral] %i(...)
@@ -410,6 +426,10 @@ literals/literals.rb:
410426
# 113| getComponent: [StringTextComponent] 4
411427
# 113| getArgument: [SymbolLiteral] :"}"
412428
# 113| getComponent: [StringTextComponent] }
429+
# 113| getArgument: [SymbolLiteral] :"#{bar}"
430+
# 113| getComponent: [StringTextComponent] #{bar}
431+
# 113| getArgument: [SymbolLiteral] :"#{BAR}"
432+
# 113| getComponent: [StringTextComponent] #{BAR}
413433
# 113| getArgument: [SymbolLiteral] :"baz"
414434
# 113| getComponent: [StringTextComponent] baz
415435
control/loops.rb:

0 commit comments

Comments
 (0)