Skip to content

Commit 77fca27

Browse files
committed
Ruby: Improve desugaring of for loops
1 parent f377d25 commit 77fca27

File tree

16 files changed

+621
-270
lines changed

16 files changed

+621
-270
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class UnaryLogicalOperation extends UnaryOperation, TUnaryLogicalOperation { }
4848
* not params.empty?
4949
* ```
5050
*/
51-
class NotExpr extends UnaryLogicalOperation, TNotExpr {
51+
class NotExpr extends UnaryLogicalOperation instanceof NotExprImpl {
5252
final override string getAPrimaryQlClass() { result = "NotExpr" }
5353
}
5454

@@ -118,7 +118,7 @@ class ComplementExpr extends UnaryBitwiseOperation, TComplementExpr {
118118
* defined? some_method
119119
* ```
120120
*/
121-
class DefinedExpr extends UnaryOperation, TDefinedExpr {
121+
class DefinedExpr extends UnaryOperation instanceof DefinedExprImpl {
122122
final override string getAPrimaryQlClass() { result = "DefinedExpr" }
123123
}
124124

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

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ private module Cached {
123123
TConstantWriteAccessSynth(Ast::AstNode parent, int i, string value) {
124124
mkSynthChild(ConstantWriteAccessKind(value), parent, i)
125125
} or
126-
TDefinedExpr(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
126+
TDefinedExprReal(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
127+
TDefinedExprSynth(Ast::AstNode parent, int i) { mkSynthChild(DefinedExprKind(), parent, i) } or
127128
TDelimitedSymbolLiteral(Ruby::DelimitedSymbol g) or
128129
TDestructuredLeftAssignment(Ruby::DestructuredLeftAssignment g) {
129130
not strictcount(int i | exists(g.getParent().(Ruby::LeftAssignmentList).getChild(i))) = 1
@@ -228,7 +229,8 @@ private module Cached {
228229
TNilLiteralReal(Ruby::Nil g) or
229230
TNilLiteralSynth(Ast::AstNode parent, int i) { mkSynthChild(NilLiteralKind(), parent, i) } or
230231
TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or
231-
TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
232+
TNotExprReal(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
233+
TNotExprSynth(Ast::AstNode parent, int i) { mkSynthChild(NotExprKind(), parent, i) } or
232234
TOptionalParameter(Ruby::OptionalParameter g) or
233235
TPair(Ruby::Pair g) or
234236
TParenthesizedExpr(Ruby::ParenthesizedStatements g) or
@@ -354,21 +356,21 @@ private module Cached {
354356
TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or TBlockParameter or
355357
TBraceBlockReal or TBreakStmt or TCaseEqExpr or TCaseExpr or TCaseMatchReal or
356358
TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or
357-
TComplexLiteral or TDefinedExpr or TDelimitedSymbolLiteral or TDestructuredLeftAssignment or
358-
TDestructuredParameter or TDivExprReal or TDo or TDoBlock or TElementReference or
359-
TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or TEnsure or TEqExpr or
360-
TExponentExprReal or TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or
361-
TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
362-
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExpr or
363-
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
364-
TIfReal or TIfModifierExpr or TInClauseReal or TInstanceVariableAccessReal or
365-
TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or
366-
TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or
367-
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TMatchPattern or
368-
TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or
369-
TNilLiteralReal or TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or
370-
TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or
371-
TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
359+
TComplexLiteral or TDefinedExprReal or TDelimitedSymbolLiteral or
360+
TDestructuredLeftAssignment or TDestructuredParameter or TDivExprReal or TDo or TDoBlock or
361+
TElementReference or TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or
362+
TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFile or TFindPattern or
363+
TFloatLiteral or TForExpr or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
364+
TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashPattern or
365+
THashSplatExpr or THashSplatNilParameter or THashSplatParameter or THereDoc or
366+
TIdentifierMethodCall or TIfReal or TIfModifierExpr or TInClauseReal or
367+
TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or
368+
TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList or TLine or
369+
TLocalVariableAccessReal or TLogicalAndExprReal or TLogicalOrExprReal or TMethod or
370+
TMatchPattern or TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or
371+
TNextStmt or TNilLiteralReal or TNoRegExpMatchExpr or TNotExprReal or TOptionalParameter or
372+
TPair or TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or
373+
TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
372374
TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or
373375
TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
374376
TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or
@@ -438,7 +440,7 @@ private module Cached {
438440
n = TClassVariableAccessReal(result, _) or
439441
n = TComplementExpr(result) or
440442
n = TComplexLiteral(result) or
441-
n = TDefinedExpr(result) or
443+
n = TDefinedExprReal(result) or
442444
n = TDelimitedSymbolLiteral(result) or
443445
n = TDestructuredLeftAssignment(result) or
444446
n = TDivExprReal(result) or
@@ -495,7 +497,7 @@ private module Cached {
495497
n = TNextStmt(result) or
496498
n = TNilLiteralReal(result) or
497499
n = TNoRegExpMatchExpr(result) or
498-
n = TNotExpr(result) or
500+
n = TNotExprReal(result) or
499501
n = TOptionalParameter(result) or
500502
n = TPair(result) or
501503
n = TParenthesizedExpr(result) or
@@ -585,6 +587,8 @@ private module Cached {
585587
or
586588
result = TConstantWriteAccessSynth(parent, i, _)
587589
or
590+
result = TDefinedExprSynth(parent, i)
591+
or
588592
result = TDivExprSynth(parent, i)
589593
or
590594
result = TElseSynth(parent, i)
@@ -617,6 +621,8 @@ private module Cached {
617621
or
618622
result = TNilLiteralSynth(parent, i)
619623
or
624+
result = TNotExprSynth(parent, i)
625+
or
620626
result = TRangeLiteralSynth(parent, i, _)
621627
or
622628
result = TRShiftExprSynth(parent, i)
@@ -789,10 +795,14 @@ class TNamespace = TClassDeclaration or TModuleDeclaration;
789795

790796
class TOperation = TUnaryOperation or TBinaryOperation or TAssignment;
791797

798+
class TDefinedExpr = TDefinedExprReal or TDefinedExprSynth;
799+
792800
class TUnaryOperation =
793801
TUnaryLogicalOperation or TUnaryArithmeticOperation or TUnaryBitwiseOperation or TDefinedExpr or
794802
TSplatExpr or THashSplatExpr;
795803

804+
class TNotExpr = TNotExprReal or TNotExprSynth;
805+
796806
class TUnaryLogicalOperation = TNotExpr;
797807

798808
class TUnaryArithmeticOperation = TUnaryPlusExpr or TUnaryMinusExpr;

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ class UnaryOperationGenerated extends UnaryOperationImpl {
3535
final override string getOperatorImpl() { result = g.getOperator() }
3636
}
3737

38+
abstract class NotExprImpl extends UnaryOperationImpl, TNotExpr { }
39+
40+
class NotExprReal extends NotExprImpl, UnaryOperationGenerated, TNotExprReal { }
41+
42+
class NotExprSynth extends NotExprImpl, TNotExprSynth {
43+
final override string getOperatorImpl() { result = "!" }
44+
45+
final override Expr getOperandImpl() { synthChild(this, 0, result) }
46+
}
47+
3848
class SplatExprReal extends UnaryOperationImpl, TSplatExprReal {
3949
private Ruby::SplatArgument g;
4050

@@ -67,6 +77,16 @@ class HashSplatExprImpl extends UnaryOperationImpl, THashSplatExpr {
6777
final override string getOperatorImpl() { result = "**" }
6878
}
6979

80+
abstract class DefinedExprImpl extends UnaryOperationImpl, TDefinedExpr { }
81+
82+
class DefinedExprReal extends DefinedExprImpl, UnaryOperationGenerated, TDefinedExprReal { }
83+
84+
class DefinedExprSynth extends DefinedExprImpl, TDefinedExprSynth {
85+
final override string getOperatorImpl() { result = "defined?" }
86+
87+
final override Expr getOperandImpl() { synthChild(this, 0, result) }
88+
}
89+
7090
abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinaryOperation {
7191
abstract Stmt getLeftOperandImpl();
7292

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

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ newtype SynthKind =
2121
BraceBlockKind() or
2222
CaseMatchKind() or
2323
ClassVariableAccessKind(ClassVariable v) or
24+
DefinedExprKind() or
2425
DivExprKind() or
2526
ElseKind() or
2627
ExponentExprKind() or
@@ -40,6 +41,7 @@ newtype SynthKind =
4041
ModuloExprKind() or
4142
MulExprKind() or
4243
NilLiteralKind() or
44+
NotExprKind() or
4345
RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
4446
RShiftExprKind() or
4547
SimpleParameterKind() or
@@ -1258,6 +1260,7 @@ private module HashLiteralDesugar {
12581260
* ```
12591261
* desugars to, roughly,
12601262
* ```rb
1263+
* if not defined? x then x = nil end
12611264
* xs.each { |__synth__0| x = __synth__0; <loop_body> }
12621265
* ```
12631266
*
@@ -1267,53 +1270,137 @@ private module HashLiteralDesugar {
12671270
* scoped to the synthesized block.
12681271
*/
12691272
private module ForLoopDesugar {
1273+
private Ruby::AstNode getForLoopPatternChild(Ruby::For for) {
1274+
result = for.getPattern()
1275+
or
1276+
result.getParent() = getForLoopPatternChild(for)
1277+
}
1278+
1279+
/** Holds if `v` is the `i`th iteration variable of `for`. */
1280+
private predicate forLoopVariable(Ruby::For for, VariableReal v, int i) {
1281+
v =
1282+
rank[i + 1](VariableReal v0, Ruby::AstNode n, Location l |
1283+
n = getForLoopPatternChild(for) and
1284+
l = n.getLocation() and
1285+
access(n, v0)
1286+
|
1287+
v0 order by l.getStartLine(), l.getStartColumn()
1288+
)
1289+
}
1290+
1291+
/** Gets the number of iteration variables of `for`. */
1292+
private int forLoopVariableCount(Ruby::For for) {
1293+
result = count(int j | forLoopVariable(for, _, j))
1294+
}
1295+
1296+
private Ruby::For toTsFor(ForExpr for) { for = TForExpr(result) }
1297+
1298+
/**
1299+
* Synthesizes an assignment
1300+
* ```rb
1301+
* if not defined? v then v = nil end
1302+
* ```
1303+
* anchored at index `rootIndex` of `root`.
1304+
*/
1305+
bindingset[root, rootIndex, v]
1306+
private predicate nilAssignUndefined(
1307+
AstNode root, int rootIndex, AstNode parent, int i, Child child, VariableReal v
1308+
) {
1309+
parent = root and
1310+
i = rootIndex and
1311+
child = SynthChild(IfKind())
1312+
or
1313+
exists(AstNode if_ | if_ = TIfSynth(root, rootIndex) |
1314+
parent = if_ and
1315+
i = 0 and
1316+
child = SynthChild(NotExprKind())
1317+
or
1318+
exists(AstNode not_ | not_ = TNotExprSynth(if_, 0) |
1319+
parent = not_ and
1320+
i = 0 and
1321+
child = SynthChild(DefinedExprKind())
1322+
or
1323+
parent = TDefinedExprSynth(not_, 0) and
1324+
i = 0 and
1325+
child = SynthChild(LocalVariableAccessRealKind(v))
1326+
)
1327+
or
1328+
parent = if_ and
1329+
i = 1 and
1330+
child = SynthChild(AssignExprKind())
1331+
or
1332+
parent = TAssignExprSynth(if_, 1) and
1333+
(
1334+
i = 0 and
1335+
child = SynthChild(LocalVariableAccessRealKind(v))
1336+
or
1337+
i = 1 and
1338+
child = SynthChild(NilLiteralKind())
1339+
)
1340+
)
1341+
}
1342+
12701343
pragma[nomagic]
12711344
private predicate forLoopSynthesis(AstNode parent, int i, Child child) {
12721345
exists(ForExpr for |
1273-
// each call
12741346
parent = for and
12751347
i = -1 and
1276-
child = SynthChild(MethodCallKind("each", false, 0))
1348+
child = SynthChild(StmtSequenceKind())
12771349
or
1278-
exists(MethodCall eachCall | eachCall = TMethodCallSynth(for, -1, "each", false, 0) |
1279-
// receiver
1280-
parent = eachCall and
1281-
i = 0 and
1282-
child = childRef(for.getValue()) // value is the Enumerable
1283-
or
1284-
parent = eachCall and
1285-
i = 1 and
1286-
child = SynthChild(BraceBlockKind())
1350+
exists(AstNode seq | seq = TStmtSequenceSynth(for, -1) |
1351+
exists(VariableReal v, int j | forLoopVariable(toTsFor(for), v, j) |
1352+
nilAssignUndefined(seq, j, parent, i, child, v)
1353+
)
12871354
or
1288-
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
1289-
// block params
1290-
parent = block and
1291-
i = 0 and
1292-
child = SynthChild(SimpleParameterKind())
1355+
exists(int numberOfVars | numberOfVars = forLoopVariableCount(toTsFor(for)) |
1356+
// each call
1357+
parent = seq and
1358+
i = numberOfVars and
1359+
child = SynthChild(MethodCallKind("each", false, 0))
12931360
or
1294-
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
1295-
parent = param and
1361+
exists(MethodCall eachCall |
1362+
eachCall = TMethodCallSynth(seq, numberOfVars, "each", false, 0)
1363+
|
1364+
// receiver
1365+
parent = eachCall and
12961366
i = 0 and
1297-
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1367+
child = childRef(for.getValue()) // value is the Enumerable
12981368
or
1299-
// assignment to pattern from for loop to synth parameter
1300-
parent = block and
1369+
parent = eachCall and
13011370
i = 1 and
1302-
child = SynthChild(AssignExprKind())
1371+
child = SynthChild(BraceBlockKind())
13031372
or
1304-
parent = TAssignExprSynth(block, 1) and
1305-
(
1373+
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
1374+
// block params
1375+
parent = block and
13061376
i = 0 and
1307-
child = childRef(for.getPattern())
1377+
child = SynthChild(SimpleParameterKind())
13081378
or
1309-
i = 1 and
1310-
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1379+
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
1380+
parent = param and
1381+
i = 0 and
1382+
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1383+
or
1384+
// assignment to pattern from for loop to synth parameter
1385+
parent = block and
1386+
i = 1 and
1387+
child = SynthChild(AssignExprKind())
1388+
or
1389+
parent = TAssignExprSynth(block, 1) and
1390+
(
1391+
i = 0 and
1392+
child = childRef(for.getPattern())
1393+
or
1394+
i = 1 and
1395+
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1396+
)
1397+
)
1398+
or
1399+
// rest of block body
1400+
parent = block and
1401+
child = childRef(for.getBody().(Do).getStmt(i - 2))
13111402
)
13121403
)
1313-
or
1314-
// rest of block body
1315-
parent = block and
1316-
child = childRef(for.getBody().(Do).getStmt(i - 2))
13171404
)
13181405
)
13191406
)

0 commit comments

Comments
 (0)