Skip to content

Commit 061575f

Browse files
authored
Merge pull request #13937 from hvitved/ruby/for-loop-desugar
Ruby: Improve desugaring of `for` loops
2 parents 77db0cf + e96cbeb commit 061575f

File tree

16 files changed

+647
-270
lines changed

16 files changed

+647
-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: 144 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,58 +1270,160 @@ 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 `n` is an access to variable `v` in the pattern of `for`. */
1280+
pragma[nomagic]
1281+
private predicate forLoopVariableAccess(Ruby::For for, Ruby::AstNode n, VariableReal v) {
1282+
n = getForLoopPatternChild(for) and
1283+
access(n, v)
1284+
}
1285+
1286+
/** Holds if `v` is the `i`th iteration variable of `for`. */
1287+
private predicate forLoopVariable(Ruby::For for, VariableReal v, int i) {
1288+
v =
1289+
rank[i + 1](VariableReal v0, Ruby::AstNode n, Location l |
1290+
forLoopVariableAccess(for, n, v0) and
1291+
l = n.getLocation()
1292+
|
1293+
v0 order by l.getStartLine(), l.getStartColumn()
1294+
)
1295+
}
1296+
1297+
/** Gets the number of iteration variables of `for`. */
1298+
private int forLoopVariableCount(Ruby::For for) {
1299+
result = count(int j | forLoopVariable(for, _, j))
1300+
}
1301+
1302+
private Ruby::For toTsFor(ForExpr for) { for = TForExpr(result) }
1303+
1304+
/**
1305+
* Synthesizes an assignment
1306+
* ```rb
1307+
* if not defined? v then v = nil end
1308+
* ```
1309+
* anchored at index `rootIndex` of `root`.
1310+
*/
1311+
bindingset[root, rootIndex, v]
1312+
private predicate nilAssignUndefined(
1313+
AstNode root, int rootIndex, AstNode parent, int i, Child child, VariableReal v
1314+
) {
1315+
parent = root and
1316+
i = rootIndex and
1317+
child = SynthChild(IfKind())
1318+
or
1319+
exists(AstNode if_ | if_ = TIfSynth(root, rootIndex) |
1320+
parent = if_ and
1321+
i = 0 and
1322+
child = SynthChild(NotExprKind())
1323+
or
1324+
exists(AstNode not_ | not_ = TNotExprSynth(if_, 0) |
1325+
parent = not_ and
1326+
i = 0 and
1327+
child = SynthChild(DefinedExprKind())
1328+
or
1329+
parent = TDefinedExprSynth(not_, 0) and
1330+
i = 0 and
1331+
child = SynthChild(LocalVariableAccessRealKind(v))
1332+
)
1333+
or
1334+
parent = if_ and
1335+
i = 1 and
1336+
child = SynthChild(AssignExprKind())
1337+
or
1338+
parent = TAssignExprSynth(if_, 1) and
1339+
(
1340+
i = 0 and
1341+
child = SynthChild(LocalVariableAccessRealKind(v))
1342+
or
1343+
i = 1 and
1344+
child = SynthChild(NilLiteralKind())
1345+
)
1346+
)
1347+
}
1348+
12701349
pragma[nomagic]
12711350
private predicate forLoopSynthesis(AstNode parent, int i, Child child) {
12721351
exists(ForExpr for |
1273-
// each call
12741352
parent = for and
12751353
i = -1 and
1276-
child = SynthChild(MethodCallKind("each", false, 0))
1354+
child = SynthChild(StmtSequenceKind())
12771355
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())
1356+
exists(AstNode seq | seq = TStmtSequenceSynth(for, -1) |
1357+
exists(VariableReal v, int j | forLoopVariable(toTsFor(for), v, j) |
1358+
nilAssignUndefined(seq, j, parent, i, child, v)
1359+
)
12871360
or
1288-
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
1289-
// block params
1290-
parent = block and
1291-
i = 0 and
1292-
child = SynthChild(SimpleParameterKind())
1361+
exists(int numberOfVars | numberOfVars = forLoopVariableCount(toTsFor(for)) |
1362+
// each call
1363+
parent = seq and
1364+
i = numberOfVars and
1365+
child = SynthChild(MethodCallKind("each", false, 0))
12931366
or
1294-
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
1295-
parent = param and
1367+
exists(MethodCall eachCall |
1368+
eachCall = TMethodCallSynth(seq, numberOfVars, "each", false, 0)
1369+
|
1370+
// receiver
1371+
parent = eachCall and
12961372
i = 0 and
1297-
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1373+
child = childRef(for.getValue()) // value is the Enumerable
12981374
or
1299-
// assignment to pattern from for loop to synth parameter
1300-
parent = block and
1375+
parent = eachCall and
13011376
i = 1 and
1302-
child = SynthChild(AssignExprKind())
1377+
child = SynthChild(BraceBlockKind())
13031378
or
1304-
parent = TAssignExprSynth(block, 1) and
1305-
(
1379+
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
1380+
// block params
1381+
parent = block and
13061382
i = 0 and
1307-
child = childRef(for.getPattern())
1383+
child = SynthChild(SimpleParameterKind())
13081384
or
1309-
i = 1 and
1310-
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1385+
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
1386+
parent = param and
1387+
i = 0 and
1388+
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1389+
or
1390+
// assignment to pattern from for loop to synth parameter
1391+
parent = block and
1392+
i = 1 and
1393+
child = SynthChild(AssignExprKind())
1394+
or
1395+
parent = TAssignExprSynth(block, 1) and
1396+
(
1397+
i = 0 and
1398+
child = childRef(for.getPattern())
1399+
or
1400+
i = 1 and
1401+
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
1402+
)
1403+
)
1404+
or
1405+
// rest of block body
1406+
parent = block and
1407+
child = childRef(for.getBody().(Do).getStmt(i - 2))
13111408
)
13121409
)
1313-
or
1314-
// rest of block body
1315-
parent = block and
1316-
child = childRef(for.getBody().(Do).getStmt(i - 2))
13171410
)
13181411
)
13191412
)
13201413
}
13211414

1415+
pragma[nomagic]
1416+
private predicate isDesugaredInitNode(ForExpr for, Variable v, AstNode n) {
1417+
exists(StmtSequence seq, AssignExpr ae |
1418+
seq = for.getDesugared() and
1419+
n = seq.getStmt(_) and
1420+
ae = n.(IfExpr).getThen() and
1421+
v = ae.getLeftOperand().getAVariable()
1422+
)
1423+
or
1424+
isDesugaredInitNode(for, v, n.getParent())
1425+
}
1426+
13221427
private class ForLoopSynthesis extends Synthesis {
13231428
final override predicate child(AstNode parent, int i, Child child) {
13241429
forLoopSynthesis(parent, i, child)
@@ -1338,6 +1443,14 @@ private module ForLoopDesugar {
13381443
final override predicate excludeFromControlFlowTree(AstNode n) {
13391444
n = any(ForExpr for).getBody()
13401445
}
1446+
1447+
final override predicate location(AstNode n, Location l) {
1448+
exists(ForExpr for, Ruby::AstNode access, Variable v |
1449+
forLoopVariableAccess(toTsFor(for), access, v) and
1450+
isDesugaredInitNode(for, v, n) and
1451+
l = access.getLocation()
1452+
)
1453+
}
13411454
}
13421455
}
13431456

0 commit comments

Comments
 (0)