Skip to content

Commit 76f8061

Browse files
committed
Ruby: desugar safe navigation calls
1 parent c9f7568 commit 76f8061

File tree

7 files changed

+203
-32
lines changed

7 files changed

+203
-32
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ class IfExpr extends ConditionalExpr, TIfExpr {
111111
}
112112
}
113113

114-
private class If extends IfExpr, TIf {
114+
private class IfReal extends IfExpr, TIfReal {
115115
private Ruby::If g;
116116

117-
If() { this = TIf(g) }
117+
IfReal() { this = TIfReal(g) }
118118

119119
final override Expr getCondition() { toGenerated(result) = g.getCondition() }
120120

@@ -125,6 +125,18 @@ private class If extends IfExpr, TIf {
125125
final override string toString() { result = "if ..." }
126126
}
127127

128+
private class IfSynth extends IfExpr, TIfSynth {
129+
IfSynth() { this = TIfSynth(_, _) }
130+
131+
final override Expr getCondition() { synthChild(this, 0, result) }
132+
133+
final override Stmt getThen() { synthChild(this, 1, result) }
134+
135+
final override Stmt getElse() { synthChild(this, 2, result) }
136+
137+
final override string toString() { result = "if ..." }
138+
}
139+
128140
private class Elsif extends IfExpr, TElsif {
129141
private Ruby::Elsif g;
130142

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

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ private module Cached {
162162
} or
163163
THereDoc(Ruby::HeredocBeginning g) or
164164
TIdentifierMethodCall(Ruby::Identifier g) { isIdentifierMethodCall(g) } or
165-
TIf(Ruby::If g) or
165+
TIfReal(Ruby::If g) or
166+
TIfSynth(AST::AstNode parent, int i) { mkSynthChild(IfKind(), parent, i) } or
166167
TIfModifierExpr(Ruby::IfModifier g) or
167168
TInClause(Ruby::InClause g) or
168169
TInstanceVariableAccessReal(Ruby::InstanceVariable g, AST::InstanceVariable v) {
@@ -214,7 +215,8 @@ private module Cached {
214215
TMulExprSynth(AST::AstNode parent, int i) { mkSynthChild(MulExprKind(), parent, i) } or
215216
TNEExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangequal } or
216217
TNextStmt(Ruby::Next g) or
217-
TNilLiteral(Ruby::Nil g) or
218+
TNilLiteralReal(Ruby::Nil g) or
219+
TNilLiteralSynth(AST::AstNode parent, int i) { mkSynthChild(NilLiteralKind(), parent, i) } or
218220
TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or
219221
TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
220222
TOptionalParameter(Ruby::OptionalParameter g) or
@@ -347,35 +349,36 @@ private module Cached {
347349
TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or TForwardParameter or
348350
TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
349351
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExpr or
350-
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or TIf or
351-
TIfModifierExpr or TInClause or TInstanceVariableAccessReal or TIntegerLiteralReal or
352-
TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or TLambda or
353-
TLeftAssignmentList or TLine or TLocalVariableAccessReal or TLogicalAndExprReal or
354-
TLogicalOrExprReal or TMethod or TModuleDeclaration or TModuloExprReal or TMulExprReal or
355-
TNEExpr or TNextStmt or TNilLiteral or TNoRegExpMatchExpr or TNotExpr or
356-
TOptionalParameter or TPair or TParenthesizedExpr or TParenthesizedPattern or
357-
TRShiftExprReal or TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or
358-
TRegExpMatchExpr or TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or
359-
TRegularSuperCall or TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
360-
TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or
361-
TSimpleSymbolLiteral or TSingletonClass or TSingletonMethod or TSpaceshipExpr or
362-
TSplatExprReal or TSplatParameter or TStringArrayLiteral or TStringConcatenation or
363-
TStringEscapeSequenceComponent or TStringInterpolationComponent or TStringTextComponent or
364-
TSubExprReal or TSubshellLiteral or TSymbolArrayLiteral or TTernaryIfExpr or TThen or
365-
TTokenConstantAccess or TTokenMethodName or TTokenSuperCall or TToplevel or TTrueLiteral or
366-
TUnaryMinusExpr or TUnaryPlusExpr or TUndefStmt or TUnlessExpr or TUnlessModifierExpr or
367-
TUntilExpr or TUntilModifierExpr or TReferencePattern or TWhenClause or TWhileExpr or
352+
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
353+
TIfReal or TIfModifierExpr or TInClause or TInstanceVariableAccessReal or
354+
TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or
355+
TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or
356+
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TModuleDeclaration or
357+
TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or TNilLiteralReal or
358+
TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or TParenthesizedExpr or
359+
TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or TRationalLiteral or
360+
TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or TRegularArrayLiteral or
361+
TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or TRescueClause or
362+
TRescueModifierExpr or TRetryStmt or TReturnStmt or TScopeResolutionConstantAccess or
363+
TSelfReal or TSimpleParameterReal or TSimpleSymbolLiteral or TSingletonClass or
364+
TSingletonMethod or TSpaceshipExpr or TSplatExprReal or TSplatParameter or
365+
TStringArrayLiteral or TStringConcatenation or TStringEscapeSequenceComponent or
366+
TStringInterpolationComponent or TStringTextComponent or TSubExprReal or TSubshellLiteral or
367+
TSymbolArrayLiteral or TTernaryIfExpr or TThen or TTokenConstantAccess or
368+
TTokenMethodName or TTokenSuperCall or TToplevel or TTrueLiteral or TUnaryMinusExpr or
369+
TUnaryPlusExpr or TUndefStmt or TUnlessExpr or TUnlessModifierExpr or TUntilExpr or
370+
TUntilModifierExpr or TReferencePattern or TWhenClause or TWhileExpr or
368371
TWhileModifierExpr or TYieldCall;
369372

370373
class TAstNodeSynth =
371374
TAddExprSynth or TAssignExprSynth or TBitwiseAndExprSynth or TBitwiseOrExprSynth or
372375
TBitwiseXorExprSynth or TBraceBlockSynth or TClassVariableAccessSynth or
373376
TConstantReadAccessSynth or TDivExprSynth or TExponentExprSynth or
374-
TGlobalVariableAccessSynth or TInstanceVariableAccessSynth or TIntegerLiteralSynth or
375-
TLShiftExprSynth or TLocalVariableAccessSynth or TLogicalAndExprSynth or
376-
TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or TMulExprSynth or
377-
TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or TSimpleParameterSynth or
378-
TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
377+
TGlobalVariableAccessSynth or TIfSynth or TInstanceVariableAccessSynth or
378+
TIntegerLiteralSynth or TLShiftExprSynth or TLocalVariableAccessSynth or
379+
TLogicalAndExprSynth or TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or
380+
TMulExprSynth or TNilLiteralSynth or TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or
381+
TSimpleParameterSynth or TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
379382

380383
/**
381384
* Gets the underlying TreeSitter entity for a given AST node. This does not
@@ -457,7 +460,7 @@ private module Cached {
457460
n = THereDoc(result) or
458461
n = TIdentifierMethodCall(result) or
459462
n = TIfModifierExpr(result) or
460-
n = TIf(result) or
463+
n = TIfReal(result) or
461464
n = TInClause(result) or
462465
n = TInstanceVariableAccessReal(result, _) or
463466
n = TIntegerLiteralReal(result) or
@@ -477,7 +480,7 @@ private module Cached {
477480
n = TMulExprReal(result) or
478481
n = TNEExpr(result) or
479482
n = TNextStmt(result) or
480-
n = TNilLiteral(result) or
483+
n = TNilLiteralReal(result) or
481484
n = TNoRegExpMatchExpr(result) or
482485
n = TNotExpr(result) or
483486
n = TOptionalParameter(result) or
@@ -568,6 +571,8 @@ private module Cached {
568571
or
569572
result = TGlobalVariableAccessSynth(parent, i, _)
570573
or
574+
result = TIfSynth(parent, i)
575+
or
571576
result = TInstanceVariableAccessSynth(parent, i, _)
572577
or
573578
result = TIntegerLiteralSynth(parent, i, _)
@@ -586,6 +591,8 @@ private module Cached {
586591
or
587592
result = TMulExprSynth(parent, i)
588593
or
594+
result = TNilLiteralSynth(parent, i)
595+
or
589596
result = TRangeLiteralSynth(parent, i, _)
590597
or
591598
result = TRShiftExprSynth(parent, i)
@@ -672,6 +679,8 @@ class TControlExpr = TConditionalExpr or TCaseExpr or TCaseMatch or TLoop;
672679
class TConditionalExpr =
673680
TIfExpr or TUnlessExpr or TIfModifierExpr or TUnlessModifierExpr or TTernaryIfExpr;
674681

682+
class TIf = TIfReal or TIfSynth;
683+
675684
class TIfExpr = TIf or TElsif;
676685

677686
class TConditionalLoop = TWhileExpr or TUntilExpr or TWhileModifierExpr or TUntilModifierExpr;
@@ -695,6 +704,8 @@ class TStmtSequence =
695704

696705
class TBodyStmt = TBeginExpr or TModuleBase or TMethod or TLambda or TDoBlock or TSingletonMethod;
697706

707+
class TNilLiteral = TNilLiteralReal or TNilLiteralSynth;
708+
698709
class TLiteral =
699710
TEncoding or TFile or TLine or TNumericLiteral or TNilLiteral or TBooleanLiteral or
700711
TStringlikeLiteral or TCharacterLiteral or TArrayLiteral or THashLiteral or TRangeLiteral or

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,18 @@ class ComplexLiteralImpl extends Expr, TComplexLiteral {
111111
}
112112
}
113113

114-
class NilLiteralImpl extends Expr, TNilLiteral {
114+
abstract class NilLiteralImpl extends Expr, TNilLiteral {
115+
final override string toString() { result = "nil" }
116+
}
117+
118+
class NilLiteralReal extends NilLiteralImpl, TNilLiteralReal {
115119
private Ruby::Nil g;
116120

117-
NilLiteralImpl() { this = TNilLiteral(g) }
121+
NilLiteralReal() { this = TNilLiteralReal(g) }
122+
}
118123

119-
final override string toString() { result = g.getValue() }
124+
class NilLiteralSynth extends NilLiteralImpl, TNilLiteralSynth {
125+
NilLiteralSynth() { this = TNilLiteralSynth(_, _) }
120126
}
121127

122128
abstract class BooleanLiteralImpl extends Expr, TBooleanLiteral {

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ newtype SynthKind =
2121
DivExprKind() or
2222
ExponentExprKind() or
2323
GlobalVariableAccessKind(GlobalVariable v) or
24+
IfKind() or
2425
InstanceVariableAccessKind(InstanceVariable v) or
2526
IntegerLiteralKind(int i) { i in [-1000 .. 1000] } or
2627
LShiftExprKind() or
@@ -33,6 +34,7 @@ newtype SynthKind =
3334
} or
3435
ModuloExprKind() or
3536
MulExprKind() or
37+
NilLiteralKind() or
3638
RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
3739
RShiftExprKind() or
3840
SimpleParameterKind() or
@@ -1083,3 +1085,123 @@ private module AnonymousBlockParameterSynth {
10831085
}
10841086
}
10851087
}
1088+
1089+
private module SafeNavigationCallDesugar {
1090+
/**
1091+
* ```rb
1092+
* receiver&.method(args) { ... }
1093+
* ```
1094+
* desugars to
1095+
*
1096+
* ```rb
1097+
* __synth__0 = receiver
1098+
* if nil == __synth__0 then nil else __synth__0.method(args) {...} end
1099+
* ```
1100+
*/
1101+
pragma[nomagic]
1102+
private predicate safeNavigationCallSynthesis(AstNode parent, int i, Child child) {
1103+
exists(RegularMethodCall call, LocalVariableAccessSynthKind local |
1104+
call.isSafeNavigationImpl() and
1105+
local = LocalVariableAccessSynthKind(TLocalVariableSynth(call.getReceiverImpl(), 0))
1106+
|
1107+
parent = call and
1108+
i = -1 and
1109+
child = SynthChild(StmtSequenceKind())
1110+
or
1111+
exists(TStmtSequenceSynth seq | seq = TStmtSequenceSynth(call, -1) |
1112+
parent = seq and
1113+
(
1114+
child = SynthChild(AssignExprKind()) and i = 0
1115+
or
1116+
child = SynthChild(IfKind()) and i = 1
1117+
)
1118+
or
1119+
parent = TAssignExprSynth(seq, 0) and
1120+
(
1121+
child = SynthChild(local) and
1122+
i = 0
1123+
or
1124+
child = childRef(call.getReceiverImpl()) and i = 1
1125+
)
1126+
or
1127+
exists(TIfSynth ifExpr | ifExpr = TIfSynth(seq, 1) |
1128+
parent = ifExpr and
1129+
(
1130+
child = SynthChild(MethodCallKind("==", false, 2)) and
1131+
i = 0
1132+
or
1133+
child = SynthChild(NilLiteralKind()) and i = 1
1134+
or
1135+
child =
1136+
SynthChild(MethodCallKind(call.getMethodNameImpl(), false,
1137+
call.getNumberOfArgumentsImpl())) and
1138+
i = 2
1139+
)
1140+
or
1141+
parent = TMethodCallSynth(ifExpr, 0, _, _, _) and
1142+
(
1143+
child = SynthChild(NilLiteralKind()) and i = 0
1144+
or
1145+
child = SynthChild(local) and
1146+
i = 1
1147+
)
1148+
or
1149+
parent = TMethodCallSynth(ifExpr, 2, _, _, _) and
1150+
(
1151+
i = 0 and
1152+
child = SynthChild(local)
1153+
or
1154+
child = childRef(call.getArgumentImpl(i - 1))
1155+
or
1156+
child = childRef(call.getBlockImpl()) and i = -2
1157+
)
1158+
)
1159+
)
1160+
)
1161+
}
1162+
1163+
private class SafeNavigationCallSynthesis extends Synthesis {
1164+
final override predicate child(AstNode parent, int i, Child child) {
1165+
safeNavigationCallSynthesis(parent, i, child)
1166+
}
1167+
1168+
final override predicate methodCall(string name, boolean setter, int arity) {
1169+
exists(RegularMethodCall call |
1170+
call.isSafeNavigationImpl() and
1171+
name = call.getMethodNameImpl() and
1172+
setter = false and
1173+
arity = call.getNumberOfArgumentsImpl()
1174+
)
1175+
or
1176+
name = "==" and setter = false and arity = 2
1177+
}
1178+
1179+
final override predicate localVariable(AstNode n, int i) {
1180+
i = 0 and n = any(RegularMethodCall c | c.isSafeNavigationImpl()).getReceiverImpl()
1181+
}
1182+
1183+
override predicate location(AstNode n, Location l) {
1184+
exists(RegularMethodCall call, StmtSequence seq |
1185+
call.isSafeNavigationImpl() and seq = call.getDesugared()
1186+
|
1187+
n = seq.getStmt(0) and
1188+
hasLocation(call.getReceiverImpl(), l)
1189+
or
1190+
n = seq.getStmt(1) and
1191+
l = toGenerated(call).(Ruby::Call).getOperator().getLocation()
1192+
or
1193+
n = seq.getStmt(1).(IfExpr).getCondition().(MethodCall).getArgument(0) and
1194+
hasLocation(call.getReceiverImpl(), l)
1195+
or
1196+
n = seq.getStmt(1).(IfExpr).getThen() and
1197+
hasLocation(call.getReceiverImpl(), l)
1198+
or
1199+
n = seq.getStmt(1).(IfExpr).getElse() and
1200+
hasLocation(call, l)
1201+
or
1202+
n = seq.getStmt(1).(IfExpr).getElse().(MethodCall).getReceiver() and
1203+
hasLocation(call.getReceiverImpl(), l)
1204+
)
1205+
}
1206+
}
1207+
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,19 @@ calls/calls.rb:
279279
# 340| getArgument: [IntegerLiteral] 4
280280
# 340| getArgument: [IntegerLiteral] 5
281281
# 340| getArgument: [IntegerLiteral] 6
282+
# 362| [MethodCall] call to empty?
283+
# 362| getDesugared: [StmtSequence] ...
284+
# 362| getStmt: [AssignExpr] ... = ...
285+
# 362| getAnOperand/getRightOperand: [MethodCall] call to list
286+
# 362| getReceiver: [SelfVariableAccess] self
287+
# 362| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
288+
# 362| getStmt: [IfExpr] if ...
289+
# 362| getBranch/getElse: [MethodCall] call to empty?
290+
# 362| getReceiver: [LocalVariableAccess] __synth__0__1
291+
# 362| getBranch/getThen: [NilLiteral] nil
292+
# 362| getCondition: [MethodCall] call to ==
293+
# 362| getArgument: [LocalVariableAccess] __synth__0__1
294+
# 362| getReceiver: [NilLiteral] nil
282295
control/cases.rb:
283296
# 90| [ArrayLiteral] %w(...)
284297
# 90| getDesugared: [MethodCall] call to []

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ exprValue
7474
| calls/calls.rb:346:8:346:9 | 42 | 42 | int |
7575
| calls/calls.rb:347:5:347:5 | :X | :X | symbol |
7676
| calls/calls.rb:350:5:350:5 | 1 | 1 | int |
77+
| calls/calls.rb:362:1:362:4 | nil | nil | nil |
78+
| calls/calls.rb:362:5:362:6 | nil | nil | nil |
7779
| constants/constants.rb:3:19:3:27 | "const_a" | const_a | string |
7880
| constants/constants.rb:6:15:6:23 | "const_b" | const_b | string |
7981
| constants/constants.rb:17:12:17:18 | "Hello" | Hello | string |
@@ -963,6 +965,8 @@ exprCfgNodeValue
963965
| calls/calls.rb:346:8:346:9 | 42 | 42 | int |
964966
| calls/calls.rb:347:5:347:5 | :X | :X | symbol |
965967
| calls/calls.rb:350:5:350:5 | 1 | 1 | int |
968+
| calls/calls.rb:362:1:362:4 | nil | nil | nil |
969+
| calls/calls.rb:362:5:362:6 | nil | nil | nil |
966970
| constants/constants.rb:3:19:3:27 | "const_a" | const_a | string |
967971
| constants/constants.rb:6:15:6:23 | "const_b" | const_b | string |
968972
| constants/constants.rb:17:12:17:18 | "Hello" | Hello | string |

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ callsWithArguments
114114
| calls.rb:346:1:346:10 | call to foo | foo | 0 | calls.rb:346:5:346:9 | Pair |
115115
| calls.rb:347:1:347:7 | call to foo | foo | 0 | calls.rb:347:5:347:6 | Pair |
116116
| calls.rb:352:13:352:17 | call to foo | foo | 0 | calls.rb:352:17:352:17 | x |
117+
| calls.rb:362:5:362:6 | call to == | == | 0 | calls.rb:362:1:362:4 | __synth__0__1 |
117118
callsWithReceiver
118119
| calls.rb:2:1:2:5 | call to foo | calls.rb:2:1:2:5 | self |
119120
| calls.rb:5:1:5:10 | call to bar | calls.rb:5:1:5:3 | Foo |
@@ -375,7 +376,9 @@ callsWithReceiver
375376
| calls.rb:361:1:361:4 | call to list | calls.rb:361:1:361:4 | self |
376377
| calls.rb:361:1:361:11 | call to empty? | calls.rb:361:1:361:4 | call to list |
377378
| calls.rb:362:1:362:4 | call to list | calls.rb:362:1:362:4 | self |
379+
| calls.rb:362:1:362:12 | call to empty? | calls.rb:362:1:362:4 | __synth__0__1 |
378380
| calls.rb:362:1:362:12 | call to empty? | calls.rb:362:1:362:4 | call to list |
381+
| calls.rb:362:5:362:6 | call to == | calls.rb:362:5:362:6 | nil |
379382
| calls.rb:363:1:363:4 | call to list | calls.rb:363:1:363:4 | self |
380383
| calls.rb:363:1:363:12 | call to empty? | calls.rb:363:1:363:4 | call to list |
381384
callsWithBlock

0 commit comments

Comments
 (0)