Skip to content

Commit 7197cc5

Browse files
authored
Merge pull request #22014 from github/copilot/update-rescue-clause-exception-handling
Ruby AST: preserve ExceptionList node in RescueClause for 2+ exceptions
2 parents 48b0cbc + 7d66ec0 commit 7197cc5

9 files changed

Lines changed: 1676 additions & 1579 deletions

File tree

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

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,36 @@ class Pair extends Expr instanceof PairImpl {
280280
final override string getAPrimaryQlClass() { result = "Pair" }
281281
}
282282

283+
/**
284+
* A disjunctive exception pattern match in a rescue clause. For example, the exception list
285+
* `FirstError, SecondError` in:
286+
* ```rb
287+
* begin
288+
* do_something
289+
* rescue FirstError, SecondError => e
290+
* handle_error(e)
291+
* end
292+
* ```
293+
*/
294+
class ExceptionList extends Expr, TExceptionList {
295+
private Ruby::Exceptions g;
296+
297+
ExceptionList() { this = TExceptionList(g) }
298+
299+
final override string getAPrimaryQlClass() { result = "ExceptionList" }
300+
301+
/** Gets the `n`th exception in this list. */
302+
final Expr getException(int n) { toGenerated(result) = g.getChild(n) }
303+
304+
final override string toString() { result = "..., ..." }
305+
306+
final override AstNode getAChild(string pred) {
307+
result = super.getAChild(pred)
308+
or
309+
pred = "getException" and result = this.getException(_)
310+
}
311+
}
312+
283313
/**
284314
* A rescue clause. For example:
285315
* ```rb
@@ -296,36 +326,69 @@ class RescueClause extends Expr, TRescueClause {
296326

297327
final override string getAPrimaryQlClass() { result = "RescueClause" }
298328

329+
/** Gets the exception list pattern when there are multiple exception expressions. */
330+
private ExceptionList getExceptions() { result = TExceptionList(g.getExceptions()) }
331+
299332
/**
300333
* Gets the `n`th exception to match, if any. For example `FirstError` or `SecondError` in:
301334
* ```rb
302335
* begin
303-
* do_something
336+
* do_something
304337
* rescue FirstError, SecondError => e
305338
* handle_error(e)
306339
* end
307340
* ```
308341
*/
309-
final Expr getException(int n) { toGenerated(result) = g.getExceptions().getChild(n) }
342+
final Expr getException(int n) {
343+
// 0 or 1 exception: no ExceptionList node, access directly
344+
not exists(this.getExceptions()) and
345+
toGenerated(result) = g.getExceptions().getChild(n)
346+
or
347+
// 2+ exceptions: delegate through ExceptionList
348+
result = this.getExceptions().getException(n)
349+
}
310350

311351
/**
312352
* Gets an exception to match, if any. For example `FirstError` or `SecondError` in:
313353
* ```rb
314354
* begin
315-
* do_something
355+
* do_something
316356
* rescue FirstError, SecondError => e
317357
* handle_error(e)
318358
* end
319359
* ```
320360
*/
321361
final Expr getAnException() { result = this.getException(_) }
322362

363+
/**
364+
* Gets the exception pattern to match, if any.
365+
*
366+
* This is either a single exception expression, or an `ExceptionList`
367+
* representing a disjunctive match of multiple exceptions when there are two
368+
* or more exceptions expressions.
369+
*
370+
* For example, in the following code, the exception pattern is the
371+
* exception list `FirstError, SecondError`:
372+
* ```rb
373+
* begin
374+
* do_something
375+
* rescue FirstError, SecondError => e
376+
* handle_error(e)
377+
* end
378+
* ```
379+
*/
380+
final Expr getPattern() {
381+
result = this.getExceptions()
382+
or
383+
not exists(this.getExceptions()) and result = this.getAnException()
384+
}
385+
323386
/**
324387
* Gets the variable to which to assign the matched exception, if any.
325388
* For example `err` in:
326389
* ```rb
327390
* begin
328-
* do_something
391+
* do_something
329392
* rescue StandardError => err
330393
* handle_error(err)
331394
* end
@@ -343,7 +406,7 @@ class RescueClause extends Expr, TRescueClause {
343406
final override AstNode getAChild(string pred) {
344407
result = super.getAChild(pred)
345408
or
346-
pred = "getException" and result = this.getException(_)
409+
pred = "getPattern" and result = this.getPattern()
347410
or
348411
pred = "getVariableExpr" and result = this.getVariableExpr()
349412
or

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ private module Cached {
155155
TEndBlock(Ruby::EndBlock g) or
156156
TEnsure(Ruby::Ensure g) or
157157
TEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequal } or
158+
TExceptionList(Ruby::Exceptions g) { strictcount(g.getChild(_)) > 1 } or
158159
TExponentExprReal(Ruby::Binary g) { g instanceof @ruby_binary_starstar } or
159160
TExponentExprSynth(Ast::AstNode parent, int i) { mkSynthChild(ExponentExprKind(), parent, i) } or
160161
TFalseLiteral(Ruby::False g) or
@@ -375,8 +376,8 @@ private module Cached {
375376
TClassVariableAccessReal or TComplementExpr or TComplexLiteral or TDefinedExprReal or
376377
TDelimitedSymbolLiteral or TDestructuredLeftAssignment or TDestructuredParameter or
377378
TDivExprReal or TDo or TDoBlock or TElementReference or TElseReal or TElsif or TEmptyStmt or
378-
TEncoding or TEndBlock or TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or
379-
TFile or TFindPattern or TFloatLiteral or TForExpr or TForwardParameter or
379+
TEncoding or TEndBlock or TEnsure or TEqExpr or TExceptionList or TExponentExprReal or
380+
TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or TForwardParameter or
380381
TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
381382
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExprReal or
382383
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
@@ -475,6 +476,7 @@ private module Cached {
475476
n = TEndBlock(result) or
476477
n = TEnsure(result) or
477478
n = TEqExpr(result) or
479+
n = TExceptionList(result) or
478480
n = TExponentExprReal(result) or
479481
n = TFalseLiteral(result) or
480482
n = TFile(result) or
@@ -765,7 +767,7 @@ class TExpr =
765767
TSelf or TArgumentList or TRescueClause or TRescueModifierExpr or TPair or TStringConcatenation or
766768
TCall or TBlockArgument or TConstantAccess or TControlExpr or TLiteral or TCallable or
767769
TVariableAccess or TStmtSequence or TOperation or TForwardArgument or TDestructuredLhsExpr or
768-
TMatchPattern or TTestPattern;
770+
TMatchPattern or TTestPattern or TExceptionList;
769771

770772
class TSplatExpr = TSplatExprReal or TSplatExprSynth;
771773

0 commit comments

Comments
 (0)