Skip to content

Commit ca2ff9a

Browse files
authored
Merge pull request #305 from github/hvitved/desugar/array-literals
Desugar array literals to `::Array.[]`
2 parents 40f0112 + 888183f commit ca2ff9a

File tree

14 files changed

+378
-69
lines changed

14 files changed

+378
-69
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ private class ScopeResolutionConstantAccess extends ConstantAccess, TScopeResolu
7070
final override predicate hasGlobalScope() { not exists(g.getScope()) }
7171
}
7272

73+
private class ConstantReadAccessSynth extends ConstantAccess, TConstantReadAccessSynth {
74+
private string value;
75+
76+
ConstantReadAccessSynth() { this = TConstantReadAccessSynth(_, _, value) }
77+
78+
final override string getName() {
79+
if this.hasGlobalScope() then result = value.suffix(2) else result = value
80+
}
81+
82+
final override Expr getScopeExpr() { synthChild(this, 0, result) }
83+
84+
final override predicate hasGlobalScope() { value.matches("::%") }
85+
}
86+
7387
/**
7488
* A use (read) of a constant.
7589
*
@@ -92,6 +106,8 @@ class ConstantReadAccess extends ConstantAccess {
92106
or
93107
// `X` in `X ||= 10` is considered both a read and a write
94108
this = any(AssignOperation a).getLeftOperand()
109+
or
110+
this instanceof TConstantReadAccessSynth
95111
}
96112

97113
/**

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -678,13 +678,13 @@ class ArrayLiteral extends Literal, TArrayLiteral {
678678
final override string getAPrimaryQlClass() { result = "ArrayLiteral" }
679679

680680
/** Gets the `n`th element in this array literal. */
681-
Expr getElement(int n) { none() }
681+
final Expr getElement(int n) { result = this.(ArrayLiteralImpl).getElementImpl(n) }
682682

683683
/** Gets an element in this array literal. */
684684
final Expr getAnElement() { result = this.getElement(_) }
685685

686686
/** Gets the number of elements in this array literal. */
687-
final int getNumberOfElements() { result = count(this.getAnElement()) }
687+
final int getNumberOfElements() { result = this.(ArrayLiteralImpl).getNumberOfElementsImpl() }
688688

689689
final override AstNode getAChild(string pred) {
690690
result = super.getAChild(pred)
@@ -693,32 +693,44 @@ class ArrayLiteral extends Literal, TArrayLiteral {
693693
}
694694
}
695695

696-
private class RegularArrayLiteral extends ArrayLiteral, TRegularArrayLiteral {
696+
abstract private class ArrayLiteralImpl extends ArrayLiteral {
697+
abstract Expr getElementImpl(int n);
698+
699+
abstract int getNumberOfElementsImpl();
700+
}
701+
702+
private class RegularArrayLiteral extends ArrayLiteralImpl, TRegularArrayLiteral {
697703
private Ruby::Array g;
698704

699705
RegularArrayLiteral() { this = TRegularArrayLiteral(g) }
700706

701-
final override Expr getElement(int i) { toGenerated(result) = g.getChild(i) }
707+
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
708+
709+
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
702710

703711
final override string toString() { result = "[...]" }
704712
}
705713

706-
private class StringArrayLiteral extends ArrayLiteral, TStringArrayLiteral {
714+
private class StringArrayLiteral extends ArrayLiteralImpl, TStringArrayLiteral {
707715
private Ruby::StringArray g;
708716

709717
StringArrayLiteral() { this = TStringArrayLiteral(g) }
710718

711-
final override Expr getElement(int i) { toGenerated(result) = g.getChild(i) }
719+
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
720+
721+
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
712722

713723
final override string toString() { result = "%w(...)" }
714724
}
715725

716-
private class SymbolArrayLiteral extends ArrayLiteral, TSymbolArrayLiteral {
726+
private class SymbolArrayLiteral extends ArrayLiteralImpl, TSymbolArrayLiteral {
717727
private Ruby::SymbolArray g;
718728

719729
SymbolArrayLiteral() { this = TSymbolArrayLiteral(g) }
720730

721-
final override Expr getElement(int i) { toGenerated(result) = g.getChild(i) }
731+
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
732+
733+
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
722734

723735
final override string toString() { result = "%i(...)" }
724736
}

ql/lib/codeql/ruby/ast/internal/AST.qll

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ private module Cached {
107107
} or
108108
TComplementExpr(Ruby::Unary g) { g instanceof @ruby_unary_tilde } or
109109
TComplexLiteral(Ruby::Complex g) or
110+
TConstantReadAccessSynth(AST::AstNode parent, int i, string value) {
111+
mkSynthChild(ConstantReadAccessKind(value), parent, i)
112+
} or
110113
TDefinedExpr(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
111114
TDelimitedSymbolLiteral(Ruby::DelimitedSymbol g) or
112115
TDestructuredLeftAssignment(Ruby::DestructuredLeftAssignment g) {
@@ -439,6 +442,8 @@ private module Cached {
439442
or
440443
result = TClassVariableAccessSynth(parent, i, _)
441444
or
445+
result = TConstantReadAccessSynth(parent, i, _)
446+
or
442447
result = TDivExprSynth(parent, i)
443448
or
444449
result = TExponentExprSynth(parent, i)
@@ -526,7 +531,8 @@ class TMethodCall =
526531

527532
class TSuperCall = TTokenSuperCall or TRegularSuperCall;
528533

529-
class TConstantAccess = TTokenConstantAccess or TScopeResolutionConstantAccess or TNamespace;
534+
class TConstantAccess =
535+
TTokenConstantAccess or TScopeResolutionConstantAccess or TNamespace or TConstantReadAccessSynth;
530536

531537
class TControlExpr = TConditionalExpr or TCaseExpr or TLoop;
532538

ql/lib/codeql/ruby/ast/internal/Synthesis.qll

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ newtype SynthKind =
3535
SplatExprKind() or
3636
StmtSequenceKind() or
3737
SelfKind() or
38-
SubExprKind()
38+
SubExprKind() or
39+
ConstantReadAccessKind(string value) { any(Synthesis s).constantReadAccess(value) }
3940

4041
/**
4142
* An AST child.
@@ -77,6 +78,11 @@ class Synthesis extends TSynthesis {
7778
*/
7879
predicate methodCall(string name, boolean setter, int arity) { none() }
7980

81+
/**
82+
* Holds if a constant read access of `name` is needed.
83+
*/
84+
predicate constantReadAccess(string name) { none() }
85+
8086
/**
8187
* Holds if `n` should be excluded from `ControlFlowTree` in the CFG construction.
8288
*/
@@ -373,7 +379,7 @@ private module AssignOperationDesugar {
373379
* x += y
374380
* ```
375381
*
376-
* desguars to
382+
* desugars to
377383
*
378384
* ```rb
379385
* x = x + y
@@ -503,7 +509,7 @@ private module AssignOperationDesugar {
503509
* foo[bar] += y
504510
* ```
505511
*
506-
* desguars to
512+
* desugars to
507513
*
508514
* ```rb
509515
* __synth__0 = foo;
@@ -702,7 +708,7 @@ private module CompoundAssignDesugar {
702708
* ```rb
703709
* x, *y, z = w
704710
* ```
705-
* desguars to
711+
* desugars to
706712
*
707713
* ```rb
708714
* __synth__0 = *w;
@@ -745,3 +751,47 @@ private module CompoundAssignDesugar {
745751
}
746752
}
747753
}
754+
755+
private module ArrayLiteralDesugar {
756+
pragma[nomagic]
757+
private predicate arrayLiteralSynthesis(AstNode parent, int i, Child child) {
758+
exists(ArrayLiteral al |
759+
parent = al and
760+
i = -1 and
761+
child = SynthChild(MethodCallKind("[]", false, al.getNumberOfElements() + 1))
762+
or
763+
exists(AstNode mc | mc = TMethodCallSynth(al, -1, _, _, _) |
764+
parent = mc and
765+
i = 0 and
766+
child = SynthChild(ConstantReadAccessKind("::Array"))
767+
or
768+
parent = mc and
769+
child = RealChild(al.getElement(i - 1))
770+
)
771+
)
772+
}
773+
774+
/**
775+
* ```rb
776+
* [1, 2, 3]
777+
* ```
778+
* desugars to
779+
*
780+
* ```rb
781+
* ::Array.[](1, 2, 3)
782+
* ```
783+
*/
784+
private class CompoundAssignSynthesis extends Synthesis {
785+
final override predicate child(AstNode parent, int i, Child child) {
786+
arrayLiteralSynthesis(parent, i, child)
787+
}
788+
789+
final override predicate methodCall(string name, boolean setter, int arity) {
790+
name = "[]" and
791+
setter = false and
792+
arity = any(ArrayLiteral al).getNumberOfElements() + 1
793+
}
794+
795+
final override predicate constantReadAccess(string name) { name = "::Array" }
796+
}
797+
}

ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,6 @@ module Trees {
130130
}
131131
}
132132

133-
private class ArrayLiteralTree extends StandardPostOrderTree, ArrayLiteral {
134-
final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) }
135-
}
136-
137133
private class AssignExprTree extends StandardPostOrderTree, AssignExpr {
138134
AssignExprTree() {
139135
exists(Expr left | left = this.getLeftOperand() |

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,14 @@ private DataFlow::LocalSourceNode trackInstance(Module tp, TypeTracker t) {
236236
or
237237
result.asExpr().getExpr() instanceof StringlikeLiteral and tp = TResolved("String")
238238
or
239-
result.asExpr().getExpr() instanceof ArrayLiteral and tp = TResolved("Array")
239+
exists(ConstantReadAccess array, MethodCall mc |
240+
result.asExpr().getExpr() = mc and
241+
mc.getMethodName() = "[]" and
242+
mc.getReceiver() = array and
243+
array.getName() = "Array" and
244+
array.hasGlobalScope() and
245+
tp = TResolved("Array")
246+
)
240247
or
241248
result.asExpr().getExpr() instanceof HashLiteral and tp = TResolved("Hash")
242249
or

0 commit comments

Comments
 (0)