From ca391eb4bc7d7e656c604735ff55ef0c9cb9d971 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 24 Jun 2026 09:39:56 +0200 Subject: [PATCH 1/2] Ruby: Cfg2 wip. --- .../ruby/controlflow/ControlFlowGraph2.qll | 496 ++++++++++++++++++ .../ql/lib/ide-contextual-queries/printCfg.ql | 10 +- 2 files changed, 503 insertions(+), 3 deletions(-) create mode 100644 ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll diff --git a/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll b/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll new file mode 100644 index 000000000000..e03a164e1b51 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll @@ -0,0 +1,496 @@ +/** + * Provides classes representing the control flow graph within callables. + */ + +private import ruby as R +private import codeql.Locations +private import codeql.controlflow.ControlFlowGraph +private import codeql.controlflow.SuccessorType +private import codeql.ruby.ast.internal.Synthesis +private import codeql.ruby.ast.internal.Scope + +private module Cfg0 = Make0; + +private module Cfg1 = Make1; + +private module Cfg2 = Make2; + +private import Cfg0 +private import Cfg1 +private import Cfg2 +import Public + +module Consistency = ControlFlow::Consistency; + +private Ast::AstNode desugar(R::Ast::AstNode n) { result = n or result = n.getDesugared() } + +/** Provides an implementation of the AST signature for Ruby. */ +private module Ast implements AstSig { + private import codeql.ruby.ast.internal.AST + + private predicate additionalExclude(R::Ast::AstNode n) { + n instanceof R::Ast::DestructuredLhsExpr or + exists(n.getDesugared()) + } + + class AstNode extends R::Ast::AstNode { + AstNode() { + not any(Synthesis s).excludeFromControlFlowTree(this) and not additionalExclude(this) + } + } + + private R::Ast::AstNode adjustedGetChild(R::Ast::AstNode parent, int index) { + exists(R::Ast::WhenClause when | parent = when | + when.getPattern(index) = result + or + when.getBody() = result and index = toGenerated(result).getParentIndex() + ) + or + exists(R::Ast::MethodCall call | parent = call and not call instanceof R::Ast::BinaryOperation | + index = -1 and call.getReceiver() = result + or + call.getArgument(index) = result + or + index = call.getNumberOfArguments() and + call.getBlock() = result + ) + or + exists(R::Ast::ClassDeclaration cls | parent = cls | + index = 0 and cls.getScopeExpr() = result + or + index = 1 and cls.getSuperclassExpr() = result + or + cls.getStmt(index - 2) = result + ) + or + exists(R::Ast::ModuleDeclaration mod | parent = mod | + index = 0 and mod.getScopeExpr() = result + or + mod.getStmt(index - 1) = result + ) + or + exists(R::Ast::SingletonClass cls | parent = cls | + index = 0 and cls.getValue() = result + or + cls.getStmt(index - 1) = result + ) + or + exists(R::Ast::SingletonMethod method | parent = method | + index = 0 and method.getObject() = result + ) + or + exists(R::Ast::Pair pair | parent = pair | + index = 0 and pair.getKey() = result + or + index = 1 and pair.getValue() = result + ) + or + exists(R::Ast::HashPattern hashpattern | parent = hashpattern | + index = -1 and hashpattern.getClass() = result + or + exists(int i | + hashpattern.getKey(i) = result and + index = 2 * i + or + hashpattern.getValue(i) = result and + index = 2 * i + 1 + ) + or + index = 2 * count(hashpattern.getValue(_)) and hashpattern.getRestVariableAccess() = result + ) + } + + private R::Ast::AstNode baseGetChild(R::Ast::AstNode parent, int index) { + result = adjustedGetChild(parent, index) + or + not exists(adjustedGetChild(parent, _)) and + not parent instanceof R::Ast::Callable and + ( + synthChild(parent, index, result) + or + result = parent.getAChild() and + not synthChild(parent, _, result) and + toGenerated(result).getParentIndex() = index + ) + } + + AstNode getChild(AstNode parent, int index) { result = desugar(baseGetChild(parent, index)) } + + private R::Ast::Scope parentScope(R::Ast::Scope n) { + result = n.getOuterScope() and + not n instanceof Callable + } + + cached + Callable getEnclosingCallable(AstNode node) { result = parentScope*(scopeOfInclSynth(node)) } + + class Callable extends AstNode { + Callable() { this instanceof R::Ast::Toplevel or this instanceof R::Ast::Callable } + } + + AstNode callableGetBody(Callable c) { + result = c.(R::Ast::Toplevel).getABeginBlock() or + result = c.(R::Ast::Toplevel).getAStmt() or + result = c.(R::Ast::Callable).getBody() + } + + class Parameter extends AstNode instanceof R::Ast::Parameter { + AstNode getPattern() { + this.(R::Ast::NamedParameter).getDefiningAccess() = result + or + not exists(this.(R::Ast::NamedParameter).getDefiningAccess()) and + this = result + } + + Expr getDefaultValue() { + result = desugar(this.(R::Ast::KeywordParameter).getDefaultValue()) or + result = desugar(this.(R::Ast::OptionalParameter).getDefaultValue()) + } + } + + Parameter callableGetParameter(Callable c, int index) { + c.(R::Ast::Callable).getParameter(index) = result + } + + class Stmt extends AstNode instanceof R::Ast::Stmt { } + + // Most ast nodes in Ruby are both statements and expressions with only a few + // that are only statements. Similarly, most nodes accept statement children, + // but a few only accept expression children. But since this really only + // matters for parsing, we can just treat expressions and statements as the + // same thing to avoid hard-to-find mistakes. + class Expr = Stmt; + + class BlockStmt extends Stmt instanceof R::Ast::StmtSequence { + BlockStmt() { not this instanceof TryStmt } + + Stmt getStmt(int n) { result = desugar(super.getStmt(n)) } + + Stmt getLastStmt() { result = desugar(super.getLastStmt()) } + } + + class ExprStmt extends Stmt { + ExprStmt() { none() } + + Expr getExpr() { none() } + } + + class IfStmt extends Stmt instanceof R::Ast::ConditionalExpr { + IfStmt() { + exists(Stmt branch | + branch = desugar(super.getBranch(_)) and not branch instanceof R::Ast::Expr + ) + } + + Expr getCondition() { result = desugar(super.getCondition()) } + + Stmt getThen() { result = desugar(super.getBranch(true)) } + + Stmt getElse() { result = desugar(super.getBranch(false)) } + } + + class LoopStmt extends Stmt instanceof R::Ast::Loop { + Stmt getBody() { result = desugar(super.getBody()) } + } + + class WhileStmt extends LoopStmt instanceof R::Ast::ConditionalLoop { + WhileStmt() { this instanceof R::Ast::WhileExpr or this instanceof R::Ast::WhileModifierExpr } + + Expr getCondition() { result = desugar(super.getCondition()) } + } + + class UntilStmt extends LoopStmt instanceof R::Ast::ConditionalLoop { + UntilStmt() { this instanceof R::Ast::UntilExpr or this instanceof R::Ast::UntilModifierExpr } + + Expr getCondition() { result = desugar(super.getCondition()) } + } + + class DoStmt extends LoopStmt { + DoStmt() { none() } + + Expr getCondition() { none() } + } + + class ForStmt extends LoopStmt { + ForStmt() { none() } + + AstNode getInit(int index) { none() } + + Expr getCondition() { none() } + + AstNode getUpdate(int index) { none() } + } + + // `ForExpr` would be a match for `ForeachStmt`, but it is desugared. + class ForeachStmt extends LoopStmt { + Expr getVariable() { none() } + + Expr getCollection() { none() } + } + + class BreakStmt extends Stmt instanceof R::Ast::BreakStmt { } + + class ContinueStmt extends Stmt instanceof R::Ast::NextStmt { } + + class GotoStmt extends Stmt { + GotoStmt() { none() } + } + + class ReturnStmt extends Stmt instanceof R::Ast::ReturnStmt { + Expr getExpr() { result = desugar(super.getValue()) } + } + + // raise is just a method call + class Throw extends AstNode { + Throw() { none() } + + Expr getExpr() { none() } + } + + class TryStmt extends Stmt instanceof R::Ast::BodyStmt { + TryStmt() { exists(this.getRescue(_)) or exists(this.getElse()) or this.hasEnsure() } + + AstNode getBody(int index) { result = desugar(super.getStmt(index)) } + + CatchClause getCatch(int index) { result = super.getRescue(index) } + + Stmt getFinally() { result = super.getEnsure() } + } + + AstNode getTryElse(TryStmt try) { result = try.(R::Ast::BodyStmt).getElse() } + + class CatchClause extends AstNode instanceof R::Ast::RescueClause { + AstNode getPattern() { result = desugar(super.getPattern()) } + + AstNode getVariable() { result = super.getVariableExpr() } + + Expr getCondition() { none() } + + Stmt getBody() { result = super.getBody() } + } + + class Switch extends AstNode instanceof R::Ast::CaseExpr { + Expr getExpr() { result = desugar(super.getValue()) } + + Case getCase(int index) { result = super.getBranch(index) } + + Stmt getStmt(int index) { none() } + } + + class Case extends AstNode { + private R::Ast::CaseExpr switch; + + Case() { this = switch.getABranch() } + + AstNode getPattern(int index) { + desugar(this.(R::Ast::WhenClause).getPattern(index)) = result + or + desugar(this.(R::Ast::InClause).getPattern()) = result and index = 0 + } + + Expr getGuard() { desugar(this.(R::Ast::InClause).getCondition()) = result } + + AstNode getBody() { + desugar(this.(R::Ast::WhenClause).getBody()) = result or + desugar(this.(R::Ast::InClause).getBody()) = result or + desugar(this.(R::Ast::CaseElseBranch).getBody()) = result + } + } + + class DefaultCase extends Case instanceof R::Ast::CaseElseBranch { } + + class ConditionalExpr extends Expr instanceof R::Ast::ConditionalExpr { + ConditionalExpr() { not this instanceof IfStmt } + + Expr getCondition() { result = desugar(super.getCondition()) } + + Expr getThen() { result = desugar(super.getBranch(true)) } + + Expr getElse() { result = desugar(super.getBranch(false)) } + } + + class BinaryExpr extends Expr { + BinaryExpr() { this instanceof R::Ast::BinaryOperation or this instanceof R::Ast::Assignment } + + Expr getLeftOperand() { + result = desugar(this.(R::Ast::BinaryOperation).getLeftOperand()) or + result = desugar(this.(R::Ast::Assignment).getLeftOperand()) + } + + Expr getRightOperand() { + result = desugar(this.(R::Ast::BinaryOperation).getRightOperand()) or + result = desugar(this.(R::Ast::Assignment).getRightOperand()) + } + } + + class LogicalAndExpr extends BinaryExpr instanceof R::Ast::LogicalAndExpr { } + + class LogicalOrExpr extends BinaryExpr instanceof R::Ast::LogicalOrExpr { } + + class NullCoalescingExpr extends BinaryExpr { + NullCoalescingExpr() { none() } + } + + class UnaryExpr extends Expr instanceof R::Ast::UnaryOperation { + Expr getOperand() { result = desugar(super.getOperand()) } + } + + class LogicalNotExpr extends UnaryExpr instanceof R::Ast::NotExpr { } + + class Assignment extends BinaryExpr instanceof R::Ast::Assignment { } + + class AssignExpr extends Assignment instanceof R::Ast::AssignExpr { } + + // Compound assignments are desugared. + class CompoundAssignment extends Assignment { + CompoundAssignment() { none() } + } + + class AssignLogicalAndExpr extends CompoundAssignment { } + + class AssignLogicalOrExpr extends CompoundAssignment { } + + class AssignNullCoalescingExpr extends CompoundAssignment { } + + class BooleanLiteral extends Expr instanceof R::Ast::BooleanLiteral { + boolean getValue() { result = super.getValue() } + } + + class PatternMatchExpr extends Expr { + PatternMatchExpr() { none() } + + Expr getExpr() { none() } + + AstNode getPattern() { none() } + } +} + +private predicate rescuable(R::Ast::AstNode n) { + exists(R::Ast::RescueModifierExpr rescueModifier | n = desugar(rescueModifier.getBody())) + or + exists(Ast::TryStmt try | n = try.getBody(_)) + or + exists(R::Ast::AstNode parent | n = desugar(parent.getAChild()) and rescuable(parent)) +} + +/** + * Holds if `c` happens in an exception-aware context, that is, it may be + * `rescue`d or `ensure`d. In such cases, we assume that the target of `c` + * may raise an exception (in addition to evaluating normally). + */ +private predicate mayRaise(R::Ast::Call c) { rescuable(c) } + +private module Input implements InputSig1, InputSig2 { + private import codeql.util.Void + private import codeql.util.Unit + private import internal.NonReturning + + predicate cfgCachedStageRef() { CfgCachedStage::ref() } + + class Label = Void; + + predicate preOrderExpr(Ast::Expr e) { + e instanceof R::Ast::StmtSequence or + e instanceof R::Ast::RescueClause or + e instanceof R::Ast::ConditionalExpr or + e instanceof R::Ast::Loop or + e instanceof R::Ast::RescueModifierExpr + } + + predicate postOrInOrder(Ast::AstNode n) { + n instanceof R::Ast::Call and + not n instanceof R::Ast::BinaryOperation and + not n instanceof R::Ast::UnaryOperation + or + n instanceof R::Ast::RedoStmt + or + n instanceof R::Ast::RetryStmt + } + + class CallableContext = Unit; + + Ast::AstNode callableGetBodyPart(Ast::Callable c, CallableContext ctx, int index) { + exists(R::Ast::Toplevel t | + c = t and + exists(ctx) + | + result = t.getBeginBlock(index) or + result = desugar(t.getStmt(index - count(t.getABeginBlock()))) + ) + } + + predicate catchAll(Ast::CatchClause catch) { + not exists(catch.(R::Ast::RescueClause).getAnException()) + } + + predicate beginAbruptCompletion( + Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always + ) { + ast instanceof NonReturningCall and + n.isIn(ast) and + // TODO: diff between raise and exit completions + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = true + or + mayRaise(ast) and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = false + or + ast instanceof R::Ast::RedoStmt and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof RedoSuccessor and + always = true + or + ast instanceof R::Ast::RetryStmt and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof RetrySuccessor and + always = true + } + + predicate endAbruptCompletion(Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c) { + exists(R::Ast::Callable callable | + callable.getBody() = ast and + n.(NormalExitNodeImpl).getEnclosingCallable() = callable + | + c.asSimpleAbruptCompletion() instanceof BreakSuccessor or + c.asSimpleAbruptCompletion() instanceof ContinueSuccessor or + c.asSimpleAbruptCompletion() instanceof RedoSuccessor + ) + or + exists(R::Ast::RescueModifierExpr rescueModifier | + ast = desugar(rescueModifier.getBody()) and + c.getSuccessorType() instanceof ExceptionSuccessor and + n.isBefore(desugar(rescueModifier.getHandler())) + ) + or + ast = any(Ast::LoopStmt loop).getBody() and + c.asSimpleAbruptCompletion() instanceof RedoSuccessor and + n.isBefore(ast) + or + exists(Ast::TryStmt try | + ast = try.getCatch(_).getBody() and + c.asSimpleAbruptCompletion() instanceof RetrySuccessor and + n.isBefore(try.getBody(0)) + ) + } + + predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Ast::ConditionalExpr ce | + n1.isAfterTrue(ce.getCondition()) and not exists(ce.getThen()) and n2.isAfter(ce) + or + n1.isAfterFalse(ce.getCondition()) and not exists(ce.getElse()) and n2.isAfter(ce) + ) + or + exists(R::Ast::RescueModifierExpr rescueModifier | + n1.isBefore(rescueModifier) and + n2.isBefore(desugar(rescueModifier.getBody())) + or + n1.isAfter(desugar(rescueModifier.getBody())) and + n2.isAfter(rescueModifier) + or + n1.isAfter(desugar(rescueModifier.getHandler())) and + n2.isAfter(rescueModifier) + ) + } +} diff --git a/ruby/ql/lib/ide-contextual-queries/printCfg.ql b/ruby/ql/lib/ide-contextual-queries/printCfg.ql index 1c4cec61a3ef..9730f330d8d6 100644 --- a/ruby/ql/lib/ide-contextual-queries/printCfg.ql +++ b/ruby/ql/lib/ide-contextual-queries/printCfg.ql @@ -10,6 +10,7 @@ private import codeql.Locations private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl private import codeql.ruby.controlflow.ControlFlowGraph +private import codeql.ruby.controlflow.ControlFlowGraph2 as Cfg2 external string selectedSourceFile(); @@ -23,7 +24,8 @@ external int selectedSourceColumn(); private predicate selectedSourceColumnAlias = selectedSourceColumn/0; -module ViewCfgQueryInput implements ViewCfgQueryInputSig { +// module ViewCfgQueryInput implements ViewCfgQueryInputSig { +module ViewCfgQueryInput implements Cfg2::ControlFlow::ViewCfgQueryInputSig { predicate selectedSourceFile = selectedSourceFileAlias/0; predicate selectedSourceLine = selectedSourceLineAlias/0; @@ -31,11 +33,13 @@ module ViewCfgQueryInput implements ViewCfgQueryInputSig { predicate selectedSourceColumn = selectedSourceColumnAlias/0; predicate cfgScopeSpan( - CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn + // CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn + Cfg2::Ast::Callable scope, File file, int startLine, int startColumn, int endLine, int endColumn ) { file = scope.getFile() and scope.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn) } } -import ViewCfgQuery +// import ViewCfgQuery +import Cfg2::ControlFlow::ViewCfgQuery From e8c7a7616c61719e9c6d86c7a282e05c2a22b9bb Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 10 Jul 2026 13:45:06 +0200 Subject: [PATCH 2/2] Ruby: Cfg replacement wip --- ruby/ql/consistency-queries/CfgConsistency.ql | 31 +- .../DataFlowConsistency.ql | 11 - ruby/ql/lib/codeql/ruby/CFG.qll | 1 - ruby/ql/lib/codeql/ruby/ast/Statement.qll | 9 +- .../codeql/ruby/controlflow/BasicBlocks.qll | 314 ---- .../lib/codeql/ruby/controlflow/CfgNodes.qll | 35 +- .../ruby/controlflow/ControlFlowGraph.qll | 24 +- .../ruby/controlflow/ControlFlowGraph2.qll | 17 +- .../ruby/controlflow/internal/Completion.qll | 565 ------ .../internal/ControlFlowGraphImpl.qll | 1510 ----------------- .../ruby/controlflow/internal/Guards.qll | 2 +- .../controlflow/internal/NonReturning.qll | 10 +- .../ruby/controlflow/internal/Splitting.qll | 348 ---- ruby/ql/lib/codeql/ruby/dataflow/SSA.qll | 15 +- .../dataflow/internal/DataFlowDispatch.qll | 4 +- .../dataflow/internal/DataFlowPrivate.qll | 27 +- .../codeql/ruby/dataflow/internal/SsaImpl.qll | 33 +- .../ql/lib/codeql/ruby/frameworks/Sinatra.qll | 2 +- .../ConditionalBypassCustomizations.qll | 4 +- .../ql/lib/ide-contextual-queries/printCfg.ql | 11 +- .../src/experimental/performance/UseDetect.ql | 12 +- .../performance/DatabaseQueryInLoop.ql | 2 +- .../src/queries/variables/DeadStoreOfLocal.ql | 7 +- .../controlflow/graph/BasicBlocks.ql | 9 +- .../library-tests/controlflow/graph/Cfg.ql | 2 +- .../dataflow/barrier-guards/barrier-guards.ql | 3 +- 26 files changed, 87 insertions(+), 2921 deletions(-) delete mode 100644 ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll delete mode 100644 ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll delete mode 100644 ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll delete mode 100644 ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll diff --git a/ruby/ql/consistency-queries/CfgConsistency.ql b/ruby/ql/consistency-queries/CfgConsistency.ql index 5af4f22fc26a..d67d67a8d3fd 100644 --- a/ruby/ql/consistency-queries/CfgConsistency.ql +++ b/ruby/ql/consistency-queries/CfgConsistency.ql @@ -1,31 +1,2 @@ -import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::Consistency as Consistency -import Consistency -import codeql.ruby.AST import codeql.ruby.CFG -import codeql.ruby.controlflow.internal.Completion -import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl - -/** - * All `Expr` nodes are `PostOrderTree`s - */ -query predicate nonPostOrderExpr(Expr e, string cls) { - cls = e.getPrimaryQlClasses() and - not exists(e.getDesugared()) and - not e instanceof BodyStmt and - exists(AstNode last, Completion c | - CfgImpl::last(e, last, c) and - last != e and - c instanceof NormalCompletion - ) -} - -query predicate scopeNoFirst(CfgScope scope) { - Consistency::scopeNoFirst(scope) and - not scope = any(StmtSequence seq | not exists(seq.getAStmt())) and - not scope = - any(Callable c | - not exists(c.getAParameter()) and - not c.getBody().hasEnsure() and - not exists(c.getBody().getARescue()) - ) -} +import ControlFlow::Consistency diff --git a/ruby/ql/consistency-queries/DataFlowConsistency.ql b/ruby/ql/consistency-queries/DataFlowConsistency.ql index 4e0588d5d4a1..de63a4309b5b 100644 --- a/ruby/ql/consistency-queries/DataFlowConsistency.ql +++ b/ruby/ql/consistency-queries/DataFlowConsistency.ql @@ -22,17 +22,6 @@ private module Input implements InputSig { not isNonConstantExpr(n.asExpr()) } - predicate multipleArgumentCallExclude(ArgumentNode arg, DataFlowCall call) { - // An argument such as `x` in `if not x then ...` has two successors (and hence - // two calls); one for each Boolean outcome of `x`. - exists(CfgNodes::ExprCfgNode n | - arg.argumentOf(call, _) and - n = call.asCall() and - arg.asExpr().getASuccessor(any(ConditionalSuccessor c)).getASuccessor*() = n and - n.getASplit() instanceof Split::ConditionalCompletionSplit - ) - } - predicate uniqueTypeExclude(Node n) { n = any(DataFlow::CallNode call | diff --git a/ruby/ql/lib/codeql/ruby/CFG.qll b/ruby/ql/lib/codeql/ruby/CFG.qll index d3401821f199..5fe7da86df05 100644 --- a/ruby/ql/lib/codeql/ruby/CFG.qll +++ b/ruby/ql/lib/codeql/ruby/CFG.qll @@ -3,4 +3,3 @@ import codeql.Locations import controlflow.ControlFlowGraph import controlflow.CfgNodes as CfgNodes -import controlflow.BasicBlocks diff --git a/ruby/ql/lib/codeql/ruby/ast/Statement.qll b/ruby/ql/lib/codeql/ruby/ast/Statement.qll index dcd51dbb3272..0081fd5d593e 100644 --- a/ruby/ql/lib/codeql/ruby/ast/Statement.qll +++ b/ruby/ql/lib/codeql/ruby/ast/Statement.qll @@ -6,7 +6,6 @@ private import codeql.ruby.CFG private import internal.AST private import internal.TreeSitter private import internal.Variable -private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl /** * A statement. @@ -14,14 +13,14 @@ private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl * This is the root QL class for all statements. */ class Stmt extends AstNode, TStmt { + /** Gets the control-flow node for this statement, if any. */ + ControlFlowNode getControlFlowNode() { result.injects(this) } + /** Gets a control-flow node for this statement, if any. */ CfgNodes::AstCfgNode getAControlFlowNode() { result.getAstNode() = this } - /** Gets a control-flow entry node for this statement, if any */ - AstNode getAControlFlowEntryNode() { result = CfgImpl::getAControlFlowEntryNode(this) } - /** Gets the control-flow scope of this statement, if any. */ - CfgScope getCfgScope() { result = CfgImpl::getCfgScope(this) } + CfgScope getCfgScope() { result = getEnclosingCallable(this) } /** Gets the enclosing callable, if any. */ Callable getEnclosingCallable() { result = this.getCfgScope() } diff --git a/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll b/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll deleted file mode 100644 index e085dbd90a38..000000000000 --- a/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll +++ /dev/null @@ -1,314 +0,0 @@ -/** Provides classes representing basic blocks. */ -overlay[local] -module; - -private import codeql.ruby.AST -private import codeql.ruby.ast.internal.AST -private import codeql.ruby.ast.internal.TreeSitter -private import codeql.ruby.controlflow.ControlFlowGraph -private import internal.ControlFlowGraphImpl as CfgImpl -private import CfgNodes -private import CfgImpl::BasicBlocks as BasicBlocksImpl -private import codeql.controlflow.BasicBlock as BB - -/** - * A basic block, that is, a maximal straight-line sequence of control flow nodes - * without branches or joins. - */ -final class BasicBlock extends BasicBlocksImpl::BasicBlock { - /** Gets an immediate successor of this basic block, if any. */ - BasicBlock getASuccessor() { result = super.getASuccessor() } - - /** Gets an immediate successor of this basic block of a given type, if any. */ - BasicBlock getASuccessor(SuccessorType t) { result = super.getASuccessor(t) } - - /** Gets an immediate predecessor of this basic block, if any. */ - BasicBlock getAPredecessor() { result = super.getAPredecessor() } - - /** Gets an immediate predecessor of this basic block of a given type, if any. */ - BasicBlock getAPredecessor(SuccessorType t) { result = super.getAPredecessor(t) } - - // The overrides below are to use `CfgNode` instead of `CfgImpl::Node` - CfgNode getNode(int pos) { result = super.getNode(pos) } - - CfgNode getANode() { result = super.getANode() } - - /** Gets the first control flow node in this basic block. */ - CfgNode getFirstNode() { result = super.getFirstNode() } - - /** Gets the last control flow node in this basic block. */ - CfgNode getLastNode() { result = super.getLastNode() } - - /** - * Holds if this basic block immediately dominates basic block `bb`. - * - * That is, this basic block is the unique basic block satisfying: - * 1. This basic block strictly dominates `bb` - * 2. There exists no other basic block that is strictly dominated by this - * basic block and which strictly dominates `bb`. - * - * All basic blocks, except entry basic blocks, have a unique immediate - * dominator. - * - * Example: - * - * ```rb - * def m b - * if b - * return 0 - * end - * return 1 - * end - * ``` - * - * The basic block starting on line 2 immediately dominates the - * basic block on line 5 (all paths from the entry point of `m` - * to `return 1` must go through the `if` block). - */ - predicate immediatelyDominates(BasicBlock bb) { super.immediatelyDominates(bb) } - - /** - * Holds if this basic block strictly dominates basic block `bb`. - * - * That is, all paths reaching basic block `bb` from some entry point - * basic block must go through this basic block (which must be different - * from `bb`). - * - * Example: - * - * ```rb - * def m b - * if b - * return 0 - * end - * return 1 - * end - * ``` - * - * The basic block starting on line 2 strictly dominates the - * basic block on line 5 (all paths from the entry point of `m` - * to `return 1` must go through the `if` block). - */ - predicate strictlyDominates(BasicBlock bb) { super.strictlyDominates(bb) } - - /** - * Holds if this basic block dominates basic block `bb`. - * - * That is, all paths reaching basic block `bb` from some entry point - * basic block must go through this basic block. - * - * Example: - * - * ```rb - * def m b - * if b - * return 0 - * end - * return 1 - * end - * ``` - * - * The basic block starting on line 2 dominates the basic - * basic block on line 5 (all paths from the entry point of `m` - * to `return 1` must go through the `if` block). - */ - predicate dominates(BasicBlock bb) { super.dominates(bb) } - - /** - * Holds if `df` is in the dominance frontier of this basic block. - * That is, this basic block dominates a predecessor of `df`, but - * does not dominate `df` itself. - * - * Example: - * - * ```rb - * def m x - * if x < 0 - * x = -x - * if x > 10 - * x = x - 1 - * end - * end - * puts x - * end - * ``` - * - * The basic block on line 8 is in the dominance frontier - * of the basic block starting on line 3 because that block - * dominates the basic block on line 4, which is a predecessor of - * `puts x`. Also, the basic block starting on line 3 does not - * dominate the basic block on line 8. - */ - predicate inDominanceFrontier(BasicBlock df) { super.inDominanceFrontier(df) } - - /** - * Gets the basic block that immediately dominates this basic block, if any. - * - * That is, the result is the unique basic block satisfying: - * 1. The result strictly dominates this basic block. - * 2. There exists no other basic block that is strictly dominated by the - * result and which strictly dominates this basic block. - * - * All basic blocks, except entry basic blocks, have a unique immediate - * dominator. - * - * Example: - * - * ```rb - * def m b - * if b - * return 0 - * end - * return 1 - * end - * ``` - * - * The basic block starting on line 2 is an immediate dominator of - * the basic block on line 5 (all paths from the entry point of `m` - * to `return 1` must go through the `if` block, and the `if` block - * is an immediate predecessor of `return 1`). - */ - BasicBlock getImmediateDominator() { result = super.getImmediateDominator() } - - /** - * Holds if the edge with successor type `s` out of this basic block is a - * dominating edge for `dominated`. - * - * That is, all paths reaching `dominated` from the entry point basic - * block must go through the `s` edge out of this basic block. - * - * Edge dominance is similar to node dominance except it concerns edges - * instead of nodes: A basic block is dominated by a _basic block_ `bb` if it - * can only be reached through `bb` and dominated by an _edge_ `e` if it can - * only be reached through `e`. - * - * Note that where all basic blocks (except the entry basic block) are - * strictly dominated by at least one basic block, a basic block may not be - * dominated by any edge. If an edge dominates a basic block `bb`, then - * both endpoints of the edge dominates `bb`. The converse is not the case, - * as there may be multiple paths between the endpoints with none of them - * dominating. - */ - predicate edgeDominates(BasicBlock dominated, SuccessorType s) { - super.edgeDominates(dominated, s) - } - - /** - * Holds if this basic block strictly post-dominates basic block `bb`. - * - * That is, all paths reaching a normal exit point basic block from basic - * block `bb` must go through this basic block (which must be different - * from `bb`). - * - * Example: - * - * ```rb - * def m b - * if b - * puts "b" - * end - * puts "m" - * end - * ``` - * - * The basic block on line 5 strictly post-dominates the basic block on - * line 3 (all paths to the exit point of `m` from `puts "b"` must go - * through `puts "m"`). - */ - predicate strictlyPostDominates(BasicBlock bb) { super.strictlyPostDominates(bb) } - - /** - * Holds if this basic block post-dominates basic block `bb`. - * - * That is, all paths reaching a normal exit point basic block from basic - * block `bb` must go through this basic block. - * - * Example: - * - * ```rb - * def m b - * if b - * puts "b" - * end - * puts "m" - * end - * ``` - * - * The basic block on line 5 post-dominates the basic block on line 3 - * (all paths to the exit point of `m` from `puts "b"` must go through - * `puts "m"`). - */ - predicate postDominates(BasicBlock bb) { super.postDominates(bb) } -} - -/** - * An entry basic block, that is, a basic block whose first node is - * an entry node. - */ -final class EntryBasicBlock extends BasicBlock, BasicBlocksImpl::EntryBasicBlock { } - -/** - * An annotated exit basic block, that is, a basic block that contains an - * annotated exit node. - */ -final class AnnotatedExitBasicBlock extends BasicBlock, BasicBlocksImpl::AnnotatedExitBasicBlock { } - -/** - * An exit basic block, that is, a basic block whose last node is - * an exit node. - */ -final class ExitBasicBlock extends BasicBlock, BasicBlocksImpl::ExitBasicBlock { } - -/** A basic block with more than one predecessor. */ -final class JoinBlock extends BasicBlock, BasicBlocksImpl::JoinBasicBlock { - JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = super.getJoinBlockPredecessor(i) } -} - -/** A basic block that is an immediate predecessor of a join block. */ -final class JoinBlockPredecessor extends BasicBlock, BasicBlocksImpl::JoinPredecessorBasicBlock { } - -/** - * A basic block that terminates in a condition, splitting the subsequent - * control flow. - */ -final class ConditionBlock extends BasicBlock, BasicBlocksImpl::ConditionBasicBlock { - /** - * DEPRECATED: Use `edgeDominates` instead. - * - * Holds if basic block `succ` is immediately controlled by this basic - * block with conditional value `s`. That is, `succ` is an immediate - * successor of this block, and `succ` can only be reached from - * the callable entry point by going via the `s` edge out of this basic block. - */ - deprecated predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) { - this.getASuccessor(s) = succ and - BasicBlocksImpl::dominatingEdge(this, succ) - } - - /** - * DEPRECATED: Use `edgeDominates` instead. - * - * Holds if basic block `controlled` is controlled by this basic block with - * conditional value `s`. That is, `controlled` can only be reached from the - * callable entry point by going via the `s` edge out of this basic block. - */ - deprecated predicate controls(BasicBlock controlled, ConditionalSuccessor s) { - super.edgeDominates(controlled, s) - } -} - -private class BasicBlockAlias = BasicBlock; - -private class EntryBasicBlockAlias = EntryBasicBlock; - -module Cfg implements BB::CfgSig { - class ControlFlowNode = CfgNode; - - class BasicBlock = BasicBlockAlias; - - class EntryBasicBlock = EntryBasicBlockAlias; - - predicate dominatingEdge(BasicBlock bb1, BasicBlock bb2) { - BasicBlocksImpl::dominatingEdge(bb1, bb2) - } -} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll b/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll index 0da31370ff01..d2a25ac3e23c 100644 --- a/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll +++ b/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll @@ -3,30 +3,9 @@ overlay[local] module; private import codeql.ruby.AST -private import codeql.ruby.controlflow.BasicBlocks private import codeql.ruby.dataflow.SSA private import codeql.ruby.ast.internal.Constant private import ControlFlowGraph -private import internal.ControlFlowGraphImpl as CfgImpl - -/** An entry node for a given scope. */ -class EntryNode extends CfgNode, CfgImpl::EntryNode { - override string getAPrimaryQlClass() { result = "EntryNode" } - - final override EntryBasicBlock getBasicBlock() { result = super.getBasicBlock() } -} - -/** An exit node for a given scope, annotated with the type of exit. */ -class AnnotatedExitNode extends CfgNode, CfgImpl::AnnotatedExitNode { - override string getAPrimaryQlClass() { result = "AnnotatedExitNode" } - - final override AnnotatedExitBasicBlock getBasicBlock() { result = super.getBasicBlock() } -} - -/** An exit node for a given scope. */ -class ExitNode extends CfgNode, CfgImpl::ExitNode { - override string getAPrimaryQlClass() { result = "ExitNode" } -} /** * A node for an AST node. @@ -35,7 +14,11 @@ class ExitNode extends CfgNode, CfgImpl::ExitNode { * (dead) code or not important for control flow, and multiple when there are different * splits for the AST node. */ -class AstCfgNode extends CfgNode, CfgImpl::AstCfgNode { +class AstCfgNode extends CfgNode { + AstCfgNode() { this.injects(_) } + + AstNode getAstNode() { this.injects(result) } + /** Gets the name of the primary QL class for this node. */ overlay[global] override string getAPrimaryQlClass() { result = "AstCfgNode" } @@ -123,7 +106,7 @@ abstract private class ChildMapping extends AstNode { cached predicate hasCfgChild(AstNode child, CfgNode cfn, CfgNode cfnChild) { this.reachesBasicBlock(child, cfn, cfnChild.getBasicBlock()) and - cfnChild.getAstNode() = desugar(child) + cfnChild.injects(desugar(child)) } } @@ -154,7 +137,7 @@ abstract private class NonExprChildMapping extends ChildMapping { pragma[nomagic] override predicate reachesBasicBlock(AstNode child, CfgNode cfn, BasicBlock bb) { this.relevantChild(child) and - cfn.getAstNode() = this and + cfn.injects(this) and bb.getANode() = cfn or exists(BasicBlock mid | @@ -409,7 +392,7 @@ module ExprNodes { predicate patternReachesBasicBlock(int i, CfgNode cfnPattern, BasicBlock bb) { exists(Expr pattern | pattern = this.getPattern(i) and - cfnPattern.getAstNode() = pattern and + cfnPattern.injects(pattern) and bb.getANode() = cfnPattern ) or @@ -423,7 +406,7 @@ module ExprNodes { predicate bodyReachesBasicBlock(CfgNode cfnBody, BasicBlock bb) { exists(Stmt body | body = this.getBody() and - cfnBody.getAstNode() = body and + cfnBody.injects(body) and bb.getANode() = cfnBody ) or diff --git a/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll b/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll index 8a7abea30904..02fd1e91ca17 100644 --- a/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll +++ b/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll @@ -4,10 +4,7 @@ module; import codeql.controlflow.SuccessorType private import codeql.ruby.AST -private import codeql.ruby.controlflow.BasicBlocks -private import internal.ControlFlowGraphImpl as CfgImpl -private import internal.Splitting as Splitting -private import internal.Completion +import ControlFlowGraph2 /** * An AST node with an associated control-flow graph. @@ -17,12 +14,12 @@ private import internal.Completion * Note that module declarations are not themselves CFG scopes, as they are part of * the CFG of the enclosing top-level or callable. */ -class CfgScope extends Scope instanceof CfgImpl::CfgScopeImpl { +class CfgScope extends Scope instanceof CfgScopeImpl { /** Gets the CFG scope that this scope is nested under, if any. */ final CfgScope getOuterCfgScope() { exists(AstNode parent | parent = this.getParent() and - result = CfgImpl::getCfgScope(parent) + result = getEnclosingCallable(parent) ) } } @@ -35,14 +32,11 @@ class CfgScope extends Scope instanceof CfgImpl::CfgScopeImpl { * * Only nodes that can be reached from an entry point are included in the CFG. */ -class CfgNode extends CfgImpl::Node { +class CfgNode extends ControlFlowNode { /** Gets the name of the primary QL class for this node. */ overlay[global] string getAPrimaryQlClass() { none() } - /** Gets the file of this control flow node. */ - final File getFile() { result = this.getLocation().getFile() } - /** Gets a successor node of a given type, if any. */ final CfgNode getASuccessor(SuccessorType t) { result = super.getASuccessor(t) } @@ -54,14 +48,4 @@ class CfgNode extends CfgImpl::Node { /** Gets an immediate predecessor, if any. */ final CfgNode getAPredecessor() { result = this.getAPredecessor(_) } - - /** Gets the basic block that this control flow node belongs to. */ - BasicBlock getBasicBlock() { result.getANode() = this } -} - -class Split = Splitting::Split; - -/** Provides different kinds of control flow graph splittings. */ -module Split { - class ConditionalCompletionSplit = Splitting::ConditionalCompletionSplit; } diff --git a/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll b/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll index e03a164e1b51..faf3c8ab2eed 100644 --- a/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll +++ b/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll @@ -1,6 +1,8 @@ /** * Provides classes representing the control flow graph within callables. */ +overlay[local] +module; private import ruby as R private import codeql.Locations @@ -20,10 +22,12 @@ private import Cfg1 private import Cfg2 import Public -module Consistency = ControlFlow::Consistency; - private Ast::AstNode desugar(R::Ast::AstNode n) { result = n or result = n.getDesugared() } +class CfgScopeImpl = Ast::Callable; + +predicate getEnclosingCallable = Ast::getEnclosingCallable/1; + /** Provides an implementation of the AST signature for Ruby. */ private module Ast implements AstSig { private import codeql.ruby.ast.internal.AST @@ -124,6 +128,7 @@ private module Ast implements AstSig { cached Callable getEnclosingCallable(AstNode node) { result = parentScope*(scopeOfInclSynth(node)) } + // TODO: Include EndBlock class Callable extends AstNode { Callable() { this instanceof R::Ast::Toplevel or this instanceof R::Ast::Callable } } @@ -278,9 +283,7 @@ private module Ast implements AstSig { } class Case extends AstNode { - private R::Ast::CaseExpr switch; - - Case() { this = switch.getABranch() } + Case() { this = any(R::Ast::CaseExpr switch).getABranch() } AstNode getPattern(int index) { desugar(this.(R::Ast::WhenClause).getPattern(index)) = result @@ -426,10 +429,8 @@ private module Input implements InputSig1, InputSig2 { predicate beginAbruptCompletion( Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always ) { - ast instanceof NonReturningCall and + ast.(NonReturningCall).getASuccessorType() = c.asSimpleAbruptCompletion() and n.isIn(ast) and - // TODO: diff between raise and exit completions - c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and always = true or mayRaise(ast) and diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll deleted file mode 100644 index bcfe8f98d437..000000000000 --- a/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll +++ /dev/null @@ -1,565 +0,0 @@ -/** - * Provides classes representing control flow completions. - * - * A completion represents how a statement or expression terminates. - */ -overlay[local] -module; - -private import codeql.ruby.AST -private import codeql.ruby.ast.internal.AST -private import codeql.ruby.ast.internal.Control -private import codeql.ruby.controlflow.ControlFlowGraph -private import ControlFlowGraphImpl as CfgImpl -private import NonReturning - -private newtype TCompletion = - TSimpleCompletion() or - TBooleanCompletion(boolean b) { b in [false, true] } or - TMatchingCompletion(boolean isMatch) { isMatch in [false, true] } or - TReturnCompletion() or - TBreakCompletion() or - TNextCompletion() or - TRedoCompletion() or - TRetryCompletion() or - TRaiseCompletion() or // TODO: Add exception type? - TExitCompletion() or - TNestedCompletion(TCompletion inner, TCompletion outer, int nestLevel) { - inner = TBreakCompletion() and - outer instanceof NonNestedNormalCompletion and - nestLevel = 0 - or - inner instanceof TBooleanCompletion and - outer instanceof TMatchingCompletion and - nestLevel = 0 - or - inner instanceof NormalCompletion and - nestedEnsureCompletion(outer, nestLevel) - } - -pragma[noinline] -private predicate nestedEnsureCompletion(TCompletion outer, int nestLevel) { - ( - outer = TReturnCompletion() - or - outer = TBreakCompletion() - or - outer = TNextCompletion() - or - outer = TRedoCompletion() - or - outer = TRetryCompletion() - or - outer = TRaiseCompletion() - or - outer = TExitCompletion() - ) and - nestLevel = any(CfgImpl::Trees::BodyStmtTree t).getNestLevel() -} - -pragma[noinline] -private predicate completionIsValidForStmt(AstNode n, Completion c) { - n instanceof BreakStmt and - c = TBreakCompletion() - or - n instanceof NextStmt and - c = TNextCompletion() - or - n instanceof RedoStmt and - c = TRedoCompletion() - or - n instanceof ReturnStmt and - c = TReturnCompletion() -} - -private AstNode getARescuableBodyChild() { - exists(CfgImpl::Trees::BodyStmtTree bst | result = bst.getBodyChild(_, true) | - exists(bst.getARescue()) - or - exists(bst.getEnsure()) - ) - or - result = getARescuableBodyChild().getAChild() -} - -/** - * Holds if `c` happens in an exception-aware context, that is, it may be - * `rescue`d or `ensure`d. In such cases, we assume that the target of `c` - * may raise an exception (in addition to evaluating normally). - */ -private predicate mayRaise(Call c) { c = getARescuableBodyChild() } - -/** A completion of a statement or an expression. */ -abstract class Completion extends TCompletion { - private predicate isValidForSpecific0(AstNode n) { - this = n.(NonReturningCall).getACompletion() - or - completionIsValidForStmt(n, this) - or - mustHaveBooleanCompletion(n) and - ( - exists(boolean value | isBooleanConstant(n, value) | this = TBooleanCompletion(value)) - or - not isBooleanConstant(n, _) and - this = TBooleanCompletion(_) - ) - or - mustHaveMatchingCompletion(n) and - this = TMatchingCompletion(_) - or - n = any(RescueModifierExpr parent).getBody() and - this = [TSimpleCompletion().(TCompletion), TRaiseCompletion()] - } - - private predicate isValidForSpecific(AstNode n) { - this.isValidForSpecific0(n) - or - exists(AstNode other | n = other.getDesugared() and this.isValidForSpecific(other)) - or - mayRaise(n) and - ( - this = TRaiseCompletion() - or - not any(Completion c).isValidForSpecific0(n) and - this = TSimpleCompletion() - ) - } - - /** Holds if this completion is valid for node `n`. */ - predicate isValidFor(AstNode n) { - this.isValidForSpecific(n) - or - not any(Completion c).isValidForSpecific(n) and - this = TSimpleCompletion() - } - - /** - * Holds if this completion will continue a loop when it is the completion - * of a loop body. - */ - predicate continuesLoop() { - this instanceof NormalCompletion or - this instanceof NextCompletion - } - - /** - * Gets the inner completion. This is either the inner completion, - * when the completion is nested, or the completion itself. - */ - Completion getInnerCompletion() { result = this } - - /** - * Gets the outer completion. This is either the outer completion, - * when the completion is nested, or the completion itself. - */ - Completion getOuterCompletion() { result = this } - - /** Gets a successor type that matches this completion. */ - abstract SuccessorType getAMatchingSuccessorType(); - - /** Gets a textual representation of this completion. */ - abstract string toString(); -} - -/** Holds if node `n` has the Boolean constant value `value`. */ -private predicate isBooleanConstant(AstNode n, boolean value) { - mustHaveBooleanCompletion(n) and - ( - n.(BooleanLiteral).isTrue() and - value = true - or - n.(BooleanLiteral).isFalse() and - value = false - ) -} - -/** - * Holds if a normal completion of `n` must be a Boolean completion. - */ -private predicate mustHaveBooleanCompletion(AstNode n) { - inBooleanContext(n) and - not n instanceof NonReturningCall -} - -/** - * Holds if `n` is used in a Boolean context. That is, the value - * that `n` evaluates to determines a true/false branch successor. - */ -private predicate inBooleanContext(AstNode n) { - exists(ConditionalExpr i | - n = i.getCondition() - or - inBooleanContext(i) and - n = i.getBranch(_) - ) - or - n = any(ConditionalLoop parent).getCondition() - or - n = any(InClause parent).getCondition() - or - exists(LogicalAndExpr parent | - n = parent.getLeftOperand() - or - inBooleanContext(parent) and - n = parent.getRightOperand() - ) - or - exists(LogicalOrExpr parent | - n = parent.getLeftOperand() - or - inBooleanContext(parent) and - n = parent.getRightOperand() - ) - or - n = any(NotExpr parent | inBooleanContext(parent)).getOperand() - or - n = any(StmtSequence parent | inBooleanContext(parent)).getLastStmt() - or - exists(CaseExpr c, WhenClause w | - not exists(c.getValue()) and - c.getABranch() = w - | - w.getPattern(_) = n - or - w = n - ) -} - -/** - * Holds if a normal completion of `n` must be a matching completion. - */ -private predicate mustHaveMatchingCompletion(AstNode n) { - inMatchingContext(n) and - not n instanceof NonReturningCall -} - -/** - * Holds if `n` is used in a matching context. That is, whether or - * not the value of `n` matches, determines the successor. - */ -private predicate inMatchingContext(AstNode n) { - n = any(RescueClause r).getException(_) - or - exists(CaseExpr c, WhenClause w | - exists(c.getValue()) and - c.getABranch() = w - | - w.getPattern(_) = n - or - w = n - ) - or - n instanceof CasePattern - or - n = any(ReferencePattern p).getExpr() - or - n.(CfgImpl::Trees::DefaultValueParameterTree).hasDefaultValue() -} - -/** - * A completion that represents normal evaluation of a statement or an - * expression. - */ -abstract class NormalCompletion extends Completion { } - -abstract private class NonNestedNormalCompletion extends NormalCompletion { } - -/** A simple (normal) completion. */ -class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion { - override DirectSuccessor getAMatchingSuccessorType() { any() } - - override string toString() { result = "simple" } -} - -/** - * A completion that represents evaluation of an expression, whose value determines - * the successor. Either a Boolean completion (`BooleanCompletion`), or a matching - * completion (`MatchingCompletion`). - */ -abstract class ConditionalCompletion extends NormalCompletion { - boolean value; - - bindingset[value] - ConditionalCompletion() { any() } - - /** Gets the Boolean value of this conditional completion. */ - final boolean getValue() { result = value } -} - -/** - * A completion that represents evaluation of an expression - * with a Boolean value. - */ -class BooleanCompletion extends ConditionalCompletion, NonNestedNormalCompletion, TBooleanCompletion -{ - BooleanCompletion() { this = TBooleanCompletion(value) } - - /** Gets the dual Boolean completion. */ - BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) } - - override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value } - - override string toString() { result = value.toString() } -} - -/** A Boolean `true` completion. */ -class TrueCompletion extends BooleanCompletion { - TrueCompletion() { this.getValue() = true } -} - -/** A Boolean `false` completion. */ -class FalseCompletion extends BooleanCompletion { - FalseCompletion() { this.getValue() = false } -} - -/** - * A completion that represents evaluation of a matching test, for example - * a test in a `rescue` statement. - */ -class MatchingCompletion extends ConditionalCompletion { - MatchingCompletion() { - this = TMatchingCompletion(value) - or - this = TNestedCompletion(_, TMatchingCompletion(value), _) - } - - override ConditionalSuccessor getAMatchingSuccessorType() { - this = TMatchingCompletion(result.(MatchingSuccessor).getValue()) - } - - override string toString() { if value = true then result = "match" else result = "no-match" } -} - -/** - * A completion that represents evaluation of a statement or an - * expression resulting in a return. - */ -class ReturnCompletion extends Completion { - ReturnCompletion() { - this = TReturnCompletion() or - this = TNestedCompletion(_, TReturnCompletion(), _) - } - - override ReturnSuccessor getAMatchingSuccessorType() { any() } - - override string toString() { - // `NestedCompletion` defines `toString()` for the other case - this = TReturnCompletion() and result = "return" - } -} - -/** - * A completion that represents evaluation of a statement or an - * expression resulting in a break from a loop. - */ -class BreakCompletion extends Completion { - BreakCompletion() { - this = TBreakCompletion() or - this = TNestedCompletion(_, TBreakCompletion(), _) - } - - override BreakSuccessor getAMatchingSuccessorType() { any() } - - override string toString() { - // `NestedCompletion` defines `toString()` for the other case - this = TBreakCompletion() and result = "break" - } -} - -/** - * A completion that represents evaluation of a statement or an - * expression resulting in a continuation of a loop. - */ -class NextCompletion extends Completion { - NextCompletion() { - this = TNextCompletion() or - this = TNestedCompletion(_, TNextCompletion(), _) - } - - override ContinueSuccessor getAMatchingSuccessorType() { any() } - - override string toString() { - // `NestedCompletion` defines `toString()` for the other case - this = TNextCompletion() and result = "next" - } -} - -/** - * A completion that represents evaluation of a statement or an - * expression resulting in a redo of a loop iteration. - */ -class RedoCompletion extends Completion { - RedoCompletion() { - this = TRedoCompletion() or - this = TNestedCompletion(_, TRedoCompletion(), _) - } - - override RedoSuccessor getAMatchingSuccessorType() { any() } - - override string toString() { - // `NestedCompletion` defines `toString()` for the other case - this = TRedoCompletion() and result = "redo" - } -} - -/** - * A completion that represents evaluation of a statement or an - * expression resulting in a retry. - */ -class RetryCompletion extends Completion { - RetryCompletion() { - this = TRetryCompletion() or - this = TNestedCompletion(_, TRetryCompletion(), _) - } - - override RetrySuccessor getAMatchingSuccessorType() { any() } - - override string toString() { - // `NestedCompletion` defines `toString()` for the other case - this = TRetryCompletion() and result = "retry" - } -} - -/** - * A completion that represents evaluation of a statement or an - * expression resulting in a thrown exception. - */ -class RaiseCompletion extends Completion { - RaiseCompletion() { - this = TRaiseCompletion() or - this = TNestedCompletion(_, TRaiseCompletion(), _) - } - - override ExceptionSuccessor getAMatchingSuccessorType() { any() } - - override string toString() { - // `NestedCompletion` defines `toString()` for the other case - this = TRaiseCompletion() and result = "raise" - } -} - -/** - * A completion that represents evaluation of a statement or an - * expression resulting in an abort/exit. - */ -class ExitCompletion extends Completion { - ExitCompletion() { - this = TExitCompletion() or - this = TNestedCompletion(_, TExitCompletion(), _) - } - - override ExitSuccessor getAMatchingSuccessorType() { any() } - - override string toString() { - // `NestedCompletion` defines `toString()` for the other case - this = TExitCompletion() and result = "exit" - } -} - -/** - * A nested completion. For example, in - * - * ```rb - * def m - * while x >= 0 - * x -= 1 - * if num > 100 - * break - * end - * end - * puts "done" - * end - * ``` - * - * the `while` loop can have a nested completion where the inner completion - * is a `break` and the outer completion is a simple successor. - */ -abstract class NestedCompletion extends Completion, TNestedCompletion { - Completion inner; - Completion outer; - int nestLevel; - - NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) } - - /** Gets a completion that is compatible with the inner completion. */ - Completion getAnInnerCompatibleCompletion() { - result.getOuterCompletion() = this.getInnerCompletion() - } - - /** Gets the level of this nested completion. */ - final int getNestLevel() { result = nestLevel } - - override string toString() { result = outer + " [" + inner + "] (" + nestLevel + ")" } -} - -class NestedBreakCompletion extends NormalCompletion, NestedCompletion { - NestedBreakCompletion() { - inner = TBreakCompletion() and - outer instanceof NonNestedNormalCompletion - } - - override BreakCompletion getInnerCompletion() { result = inner } - - override NonNestedNormalCompletion getOuterCompletion() { result = outer } - - override Completion getAnInnerCompatibleCompletion() { - result = inner and - outer = TSimpleCompletion() - or - result = TNestedCompletion(outer, inner, _) - } - - override SuccessorType getAMatchingSuccessorType() { - outer instanceof SimpleCompletion and - result instanceof BreakSuccessor - or - result = outer.(ConditionalCompletion).getAMatchingSuccessorType() - } -} - -class NestedEnsureCompletion extends NestedCompletion { - NestedEnsureCompletion() { - inner instanceof NormalCompletion and - nestedEnsureCompletion(outer, nestLevel) - } - - override NormalCompletion getInnerCompletion() { result = inner } - - override Completion getOuterCompletion() { result = outer } - - override SuccessorType getAMatchingSuccessorType() { none() } -} - -/** - * A completion used for conditions in pattern matching: - * - * ```rb - * in x if x == 5 then puts "five" - * in x unless x == 4 then puts "not four" - * ``` - * - * The outer (Matching) completion indicates whether there is a match, and - * the inner (Boolean) completion indicates what the condition evaluated - * to. - * - * For the condition `x == 5` above, `TNestedCompletion(true, true, 0)` and - * `TNestedCompletion(false, false, 0)` are both valid completions, while - * `TNestedCompletion(true, false, 0)` and `TNestedCompletion(false, true, 0)` - * are valid completions for `x == 4`. - */ -class NestedMatchingCompletion extends NestedCompletion, MatchingCompletion { - NestedMatchingCompletion() { - inner instanceof TBooleanCompletion and - outer instanceof TMatchingCompletion - } - - override BooleanCompletion getInnerCompletion() { result = inner } - - override MatchingCompletion getOuterCompletion() { result = outer } - - override BooleanSuccessor getAMatchingSuccessorType() { - result.getValue() = this.getInnerCompletion().getValue() - } - - override string toString() { result = NestedCompletion.super.toString() } -} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll deleted file mode 100644 index e66e8bad0037..000000000000 --- a/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ /dev/null @@ -1,1510 +0,0 @@ -/** - * Provides an implementation for constructing control-flow graphs (CFGs) from - * abstract syntax trees (ASTs), using the shared library from `codeql.controlflow.Cfg`. - */ -overlay[local] -module; - -private import codeql.controlflow.Cfg as CfgShared -private import codeql.ruby.AST -private import codeql.ruby.AST as Ast -private import codeql.ruby.ast.internal.AST as AstInternal -private import codeql.ruby.ast.internal.Scope -private import codeql.ruby.ast.internal.Synthesis -private import codeql.ruby.ast.Scope -private import codeql.ruby.ast.internal.TreeSitter -private import codeql.ruby.ast.internal.Variable -private import codeql.ruby.controlflow.ControlFlowGraph -private import Completion - -class AstNode extends Ast::AstNode { - AstNode() { not any(Synthesis s).excludeFromControlFlowTree(this) } -} - -private module CfgInput implements CfgShared::InputSig { - private import ControlFlowGraphImpl as Impl - private import Completion as Comp - private import codeql.ruby.CFG as Cfg - - class AstNode = Impl::AstNode; - - class Completion = Comp::Completion; - - predicate completionIsNormal(Completion c) { c instanceof Comp::NormalCompletion } - - predicate completionIsSimple(Completion c) { c instanceof Comp::SimpleCompletion } - - predicate completionIsValidFor(Completion c, AstNode e) { c.isValidFor(e) } - - class CfgScope = Cfg::CfgScope; - - CfgScope getCfgScope(AstNode n) { result = Impl::getCfgScope(n) } - - predicate scopeFirst(CfgScope scope, AstNode first) { scope.(Impl::CfgScopeImpl).entry(first) } - - predicate scopeLast(CfgScope scope, AstNode last, Completion c) { - scope.(Impl::CfgScopeImpl).exit(last, c) - } - - private class SuccessorType = Cfg::SuccessorType; - - SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() } - - private predicate id(Ruby::AstNode node1, Ruby::AstNode node2) { node1 = node2 } - - private predicate idOf(Ruby::AstNode node, int id) = equivalenceRelation(id/2)(node, id) - - int idOfAstNode(AstNode node) { idOf(AstInternal::toGeneratedInclSynth(node), result) } - - int idOfCfgScope(CfgScope node) { result = idOfAstNode(node) } -} - -private module CfgSplittingInput implements CfgShared::SplittingInputSig { - private import Splitting as S - - class SplitKindBase = S::TSplitKind; - - class Split = S::Split; -} - -private module ConditionalCompletionSplittingInput implements - CfgShared::ConditionalCompletionSplittingInputSig -{ - import Splitting::ConditionalCompletionSplitting::ConditionalCompletionSplittingInput -} - -import CfgShared::MakeWithSplitting - -abstract class CfgScopeImpl extends AstNode { - abstract predicate entry(AstNode first); - - abstract predicate exit(AstNode last, Completion c); -} - -private class ToplevelScope extends CfgScopeImpl, Toplevel { - final override predicate entry(AstNode first) { first(this, first) } - - final override predicate exit(AstNode last, Completion c) { last(this, last, c) } -} - -private class EndBlockScope extends CfgScopeImpl, EndBlock { - final override predicate entry(AstNode first) { - first(this.(Trees::EndBlockTree).getBodyChild(0, _), first) - } - - final override predicate exit(AstNode last, Completion c) { - last(this.(Trees::EndBlockTree).getLastBodyChild(), last, c) - or - last(this.(Trees::EndBlockTree).getBodyChild(_, _), last, c) and - not c instanceof NormalCompletion - } -} - -private class CallableScope extends CfgScopeImpl, Callable { - final override predicate entry(AstNode first) { - first(this.(Trees::CallableTree).getBodyChild(0), first) - } - - final override predicate exit(AstNode last, Completion c) { - this.getBody().(Trees::BodyStmtTree).last(last, c) - or - exists(int i | - not exists(this.getBody()) and - last(this.(Trees::CallableTree).getBodyChild(i), last, c) and - not exists(this.(Trees::CallableTree).getBodyChild(i + 1)) - ) - or - exists(AstNode child | - child = this.(Trees::CallableTree).getBodyChild(_) and - not child = this.getBody() and - last(child, last, c) and - not c instanceof NormalCompletion - ) - } -} - -/** Holds if `first` is first executed when entering `scope`. */ -pragma[nomagic] -predicate succEntry(CfgScopeImpl scope, AstNode first) { scope.entry(first) } - -/** Holds if `last` with completion `c` can exit `scope`. */ -pragma[nomagic] -predicate succExit(CfgScopeImpl scope, AstNode last, Completion c) { scope.exit(last, c) } - -/** Defines the CFG by dispatch on the various AST types. */ -module Trees { - private class AliasStmtTree extends StandardPreOrderTree instanceof AliasStmt { - final override ControlFlowTree getChildNode(int i) { - result = super.getNewName() and i = 0 - or - result = super.getOldName() and i = 1 - } - } - - private class ArgumentListTree extends StandardPostOrderTree instanceof ArgumentList { - final override ControlFlowTree getChildNode(int i) { result = super.getElement(i) } - } - - private class AssignExprTree extends StandardPostOrderTree instanceof AssignExpr { - AssignExprTree() { - exists(Expr left | left = super.getLeftOperand() | - left instanceof VariableAccess or - left instanceof ConstantAccess - ) - } - - final override ControlFlowTree getChildNode(int i) { - result = super.getLeftOperand() and i = 0 - or - result = super.getRightOperand() and i = 1 - } - } - - private class BeginTree extends BodyStmtTree instanceof BeginExpr { - final override predicate propagatesAbnormal(AstNode child) { none() } - } - - private class BlockArgumentTree extends StandardPostOrderTree instanceof BlockArgument { - final override ControlFlowTree getChildNode(int i) { result = super.getValue() and i = 0 } - } - - abstract private class NonDefaultValueParameterTree extends ControlFlowTree instanceof NamedParameter - { - final override predicate first(AstNode first) { - super.getDefiningAccess().(ControlFlowTree).first(first) - or - not exists(super.getDefiningAccess()) and first = this - } - - final override predicate last(AstNode last, Completion c) { - super.getDefiningAccess().(ControlFlowTree).last(last, c) - or - not exists(super.getDefiningAccess()) and - last = this and - c.isValidFor(this) - } - - override predicate propagatesAbnormal(AstNode child) { - super.getDefiningAccess().(ControlFlowTree).propagatesAbnormal(child) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() } - } - - private class BlockParameterTree extends NonDefaultValueParameterTree instanceof BlockParameter { - } - - class BodyStmtTree extends StmtSequenceTree instanceof BodyStmt { - /** Gets a rescue clause in this block. */ - final RescueClause getARescue() { result = super.getRescue(_) } - - /** Gets the `ensure` clause in this block, if any. */ - final StmtSequence getEnsure() { result = super.getEnsure() } - - override predicate first(AstNode first) { - first(this.getBodyChild(0, _), first) - or - not exists(this.getBodyChild(_, _)) and - first(super.getEnsure(), first) - } - - override predicate last(AstNode last, Completion c) { - exists(boolean ensurable | last = this.getAnEnsurePredecessor(c, ensurable) | - not super.hasEnsure() - or - ensurable = false - ) - or - // If the body completes normally, take the completion from the `ensure` block - this.lastEnsure(last, c, any(NormalCompletion nc), _) - or - // If the `ensure` block completes normally, it inherits any non-normal - // completion from the body - c = - any(NestedEnsureCompletion nec | - this.lastEnsure(last, nec.getAnInnerCompatibleCompletion(), nec.getOuterCompletion(), - nec.getNestLevel()) - ) - or - not exists(this.getBodyChild(_, _)) and - not exists(super.getRescue(_)) and - this.lastEnsure0(last, c) - or - last([super.getEnsure().(AstNode), this.getBodyChild(_, false)], last, c) and - not c instanceof NormalCompletion - } - - override predicate succ(AstNode pred, AstNode succ, Completion c) { - // Normal left-to-right evaluation in the body - exists(int i | - last(this.getBodyChild(i, _), pred, c) and - first(this.getBodyChild(i + 1, _), succ) and - c instanceof NormalCompletion - ) - or - // Exceptional flow from body to first `rescue` - this.lastBody(pred, c, true) and - first(super.getRescue(0), succ) and - c instanceof RaiseCompletion - or - // Flow from one `rescue` clause to the next when there is no match - exists(RescueTree rescue, int i | rescue = super.getRescue(i) | - rescue.lastNoMatch(pred, c) and - first(super.getRescue(i + 1), succ) - ) - or - // Flow from body to `else` block when no exception - this.lastBody(pred, c, _) and - first(super.getElse(), succ) and - c instanceof NormalCompletion - or - // Flow into `ensure` block - pred = this.getAnEnsurePredecessor(c, true) and - first(super.getEnsure(), succ) - } - - /** - * Gets a last element from this block that may finish with completion `c`, such - * that control may be transferred to the `ensure` block (if it exists), but only - * if `ensurable = true`. - */ - pragma[nomagic] - private AstNode getAnEnsurePredecessor(Completion c, boolean ensurable) { - this.lastBody(result, c, ensurable) and - ( - // Any non-throw completion will always continue directly to the `ensure` block, - // unless there is an `else` block - not c instanceof RaiseCompletion and - not exists(super.getElse()) - or - // Any completion will continue to the `ensure` block when there are no `rescue` - // blocks - not exists(super.getRescue(_)) - ) - or - // Last element from any matching `rescue` block continues to the `ensure` block - last(super.getRescue(_), result, c) and - ensurable = true - or - // If the last `rescue` block does not match, continue to the `ensure` block - exists(int lst, MatchingCompletion mc | - super.getRescue(lst).(RescueTree).lastNoMatch(result, mc) and - mc.getValue() = false and - not exists(super.getRescue(lst + 1)) and - c = - any(NestedEnsureCompletion nec | - nec.getOuterCompletion() instanceof RaiseCompletion and - nec.getInnerCompletion() = mc and - nec.getNestLevel() = 0 - ) and - ensurable = true - ) - or - // Last element of `else` block continues to the `ensure` block - last(super.getElse(), result, c) and - ensurable = true - } - - pragma[nomagic] - private predicate lastEnsure0(AstNode last, Completion c) { last(super.getEnsure(), last, c) } - - /** - * Gets a descendant that belongs to the `ensure` block of this block, if any. - * Nested `ensure` blocks are not included. - */ - pragma[nomagic] - AstNode getAnEnsureDescendant() { - result = super.getEnsure() - or - exists(AstNode mid | - mid = this.getAnEnsureDescendant() and - result = mid.getAChild() and - getCfgScope(result) = getCfgScope(mid) and - not exists(BodyStmt nestedBlock | - result = nestedBlock.getEnsure() and - nestedBlock != this - ) - ) - } - - /** - * Holds if `innerBlock` has an `ensure` block and is immediately nested inside the - * `ensure` block of this block. - */ - private predicate nestedEnsure(BodyStmtTree innerBlock) { - exists(StmtSequence innerEnsure | - innerEnsure = this.getAnEnsureDescendant().getAChild() and - getCfgScope(innerEnsure) = getCfgScope(this) and - innerEnsure = innerBlock.getEnsure() - ) - } - - /** - * Gets the `ensure`-nesting level of this block. That is, the number of `ensure` - * blocks that this block is nested under. - */ - int getNestLevel() { result = count(BodyStmtTree outer | outer.nestedEnsure+(this)) } - - pragma[nomagic] - private predicate lastEnsure( - AstNode last, NormalCompletion ensure, Completion outer, int nestLevel - ) { - this.lastEnsure0(last, ensure) and - exists( - this.getAnEnsurePredecessor(any(Completion c0 | outer = c0.getOuterCompletion()), true) - ) and - nestLevel = this.getNestLevel() - } - - /** - * Holds if `last` is a last element in the body of this block. `ensurable` - * indicates whether `last` may be a predecessor of an `ensure` block. - */ - pragma[nomagic] - private predicate lastBody(AstNode last, Completion c, boolean ensurable) { - exists(boolean rescuable | - if c instanceof RaiseCompletion then ensurable = rescuable else ensurable = true - | - last(this.getBodyChild(_, rescuable), last, c) and - not c instanceof NormalCompletion - or - exists(int lst | - last(this.getBodyChild(lst, rescuable), last, c) and - not exists(this.getBodyChild(lst + 1, _)) - ) - ) - } - } - - private class BooleanLiteralTree extends LeafTree instanceof BooleanLiteral { } - - class BraceBlockTree extends CallableTree instanceof BraceBlock { - final override AstNode getBodyChild(int i) { - result = super.getParameter(i) - or - result = super.getLocalVariable(i - super.getNumberOfParameters()) - or - result = super.getBody() and - i = super.getNumberOfParameters() + count(super.getALocalVariable()) - } - } - - class CallableTree extends PostOrderTree instanceof Callable { - final override predicate propagatesAbnormal(AstNode child) { none() } - - override predicate first(AstNode first) { first = this } - - abstract AstNode getBodyChild(int i); - - override predicate succ(AstNode pred, AstNode succ, Completion c) { - exists(int i | - last(this.getBodyChild(i), pred, c) and - first(this.getBodyChild(i + 1), succ) and - c instanceof NormalCompletion - ) - } - } - - private class CallTree extends StandardPostOrderTree instanceof Call { - CallTree() { - // Logical operations are handled separately - not this instanceof UnaryLogicalOperation and - not this instanceof BinaryLogicalOperation and - // Calls with the `&.` operator are desugared - not this.(MethodCall).isSafeNavigation() - } - - override ControlFlowTree getChildNode(int i) { result = super.getArgument(i) } - } - - private class CaseTree extends PostOrderTree instanceof CaseExpr, AstInternal::TCaseExpr { - final override predicate propagatesAbnormal(AstNode child) { - child = super.getValue() or child = super.getABranch() - } - - final override predicate first(AstNode first) { - first(super.getValue(), first) - or - not exists(super.getValue()) and first(super.getBranch(0), first) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getValue(), pred, c) and - first(super.getBranch(0), succ) and - c instanceof SimpleCompletion - or - exists(int i, WhenTree branch | branch = super.getBranch(i) | - pred = branch and - first(super.getBranch(i + 1), succ) and - c.isValidFor(branch) and - c.(ConditionalCompletion).getValue() = false - ) - or - succ = this and - c instanceof NormalCompletion and - ( - last(super.getValue(), pred, c) and not exists(super.getABranch()) - or - last(super.getABranch().(WhenClause).getBody(), pred, c) - or - exists(int i, ControlFlowTree lastBranch | - lastBranch = super.getBranch(i) and - not exists(super.getBranch(i + 1)) and - last(lastBranch, pred, c) - ) - ) - } - } - - private class CaseMatchTree extends PostOrderTree instanceof CaseExpr, AstInternal::TCaseMatch { - final override predicate propagatesAbnormal(AstNode child) { - child = super.getValue() or child = super.getABranch() - } - - final override predicate first(AstNode first) { first(super.getValue(), first) } - - final override predicate last(AstNode last, Completion c) { - super.last(last, c) - or - not exists(super.getElseBranch()) and - exists(MatchingCompletion lc, AstNode lastBranch | - lastBranch = max(int i | | super.getBranch(i) order by i) and - lc.getValue() = false and - last(lastBranch, last, lc) and - c instanceof RaiseCompletion and - not c instanceof NestedCompletion - ) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getValue(), pred, c) and - first(super.getBranch(0), succ) and - c instanceof SimpleCompletion - or - exists(int i, AstNode branch | branch = super.getBranch(i) | - last(branch, pred, c) and - first(super.getBranch(i + 1), succ) and - c.(MatchingCompletion).getValue() = false - ) - or - succ = this and - c instanceof NormalCompletion and - ( - last(super.getABranch(), pred, c) and - not c.(MatchingCompletion).getValue() = false - or - last(super.getElseBranch(), pred, c) - ) - } - } - - private class CaseElseBranchTree extends ControlFlowTree instanceof CaseElseBranch { - final override predicate propagatesAbnormal(AstNode child) { child = super.getBody() } - - final override predicate first(AstNode first) { first(super.getBody(), first) } - - final override predicate last(AstNode last, Completion c) { last(super.getBody(), last, c) } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() } - } - - private class PatternVariableAccessTree extends LocalVariableAccessTree instanceof LocalVariableWriteAccess, - CasePattern - { - final override predicate last(AstNode last, Completion c) { - super.last(last, c) and - c.(MatchingCompletion).getValue() = true - } - } - - private class ArrayPatternTree extends ControlFlowTree instanceof ArrayPattern { - final override predicate propagatesAbnormal(AstNode child) { - child = super.getClass() or - child = super.getPrefixElement(_) or - child = super.getRestVariableAccess() or - child = super.getSuffixElement(_) - } - - final override predicate first(AstNode first) { - first(super.getClass(), first) - or - not exists(super.getClass()) and first = this - } - - final override predicate last(AstNode last, Completion c) { - c.(MatchingCompletion).getValue() = false and - ( - last = this and - c.isValidFor(this) - or - exists(AstNode node | - node = super.getClass() or - node = super.getPrefixElement(_) or - node = super.getSuffixElement(_) - | - last(node, last, c) - ) - ) - or - c.(MatchingCompletion).getValue() = true and - last = this and - c.isValidFor(this) and - not exists(super.getPrefixElement(_)) and - not exists(super.getRestVariableAccess()) - or - c.(MatchingCompletion).getValue() = true and - last(max(int i | | super.getPrefixElement(i) order by i), last, c) and - not exists(super.getRestVariableAccess()) - or - last(super.getRestVariableAccess(), last, c) and - not exists(super.getSuffixElement(_)) - or - c.(MatchingCompletion).getValue() = true and - last(max(int i | | super.getSuffixElement(i) order by i), last, c) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getClass(), pred, c) and - succ = this and - c.(MatchingCompletion).getValue() = true - or - exists(AstNode next | - pred = this and - c.(MatchingCompletion).getValue() = true and - first(next, succ) - | - next = super.getPrefixElement(0) - or - not exists(super.getPrefixElement(_)) and - next = super.getRestVariableAccess() - ) - or - last(max(int i | | super.getPrefixElement(i) order by i), pred, c) and - first(super.getRestVariableAccess(), succ) and - c.(MatchingCompletion).getValue() = true - or - exists(int i | - last(super.getPrefixElement(i), pred, c) and - first(super.getPrefixElement(i + 1), succ) and - c.(MatchingCompletion).getValue() = true - ) - or - last(super.getRestVariableAccess(), pred, c) and - first(super.getSuffixElement(0), succ) and - c instanceof SimpleCompletion - or - exists(int i | - last(super.getSuffixElement(i), pred, c) and - first(super.getSuffixElement(i + 1), succ) and - c.(MatchingCompletion).getValue() = true - ) - } - } - - private class FindPatternTree extends ControlFlowTree instanceof FindPattern { - final override predicate propagatesAbnormal(AstNode child) { - child = super.getClass() or - child = super.getPrefixVariableAccess() or - child = super.getElement(_) or - child = super.getSuffixVariableAccess() - } - - final override predicate first(AstNode first) { - first(super.getClass(), first) - or - not exists(super.getClass()) and first = this - } - - final override predicate last(AstNode last, Completion c) { - last(super.getSuffixVariableAccess(), last, c) - or - last(max(int i | | super.getElement(i) order by i), last, c) and - not exists(super.getSuffixVariableAccess()) - or - c.(MatchingCompletion).getValue() = false and - ( - last = this and - c.isValidFor(this) - or - exists(AstNode node | node = super.getClass() or node = super.getElement(_) | - last(node, last, c) - ) - ) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getClass(), pred, c) and - succ = this and - c.(MatchingCompletion).getValue() = true - or - exists(AstNode next | - pred = this and - c.(MatchingCompletion).getValue() = true and - first(next, succ) - | - next = super.getPrefixVariableAccess() - or - not exists(super.getPrefixVariableAccess()) and - next = super.getElement(0) - ) - or - last(super.getPrefixVariableAccess(), pred, c) and - first(super.getElement(0), succ) and - c instanceof SimpleCompletion - or - c.(MatchingCompletion).getValue() = true and - exists(int i | - last(super.getElement(i), pred, c) and - first(super.getElement(i + 1), succ) - ) - or - c.(MatchingCompletion).getValue() = true and - last(max(int i | | super.getElement(i) order by i), pred, c) and - first(super.getSuffixVariableAccess(), succ) - } - } - - private class HashPatternTree extends ControlFlowTree instanceof HashPattern { - final override predicate propagatesAbnormal(AstNode child) { - child = super.getClass() or - child = super.getValue(_) or - child = super.getRestVariableAccess() - } - - final override predicate first(AstNode first) { - first(super.getClass(), first) - or - not exists(super.getClass()) and first = this - } - - final override predicate last(AstNode last, Completion c) { - c.(MatchingCompletion).getValue() = false and - ( - last = this and - c.isValidFor(this) - or - exists(AstNode node | - node = super.getClass() or - node = super.getValue(_) - | - last(node, last, c) - ) - ) - or - c.(MatchingCompletion).getValue() = true and - last = this and - not exists(super.getValue(_)) and - not exists(super.getRestVariableAccess()) - or - c.(MatchingCompletion).getValue() = true and - last(max(int i | | super.getValue(i) order by i), last, c) and - not exists(super.getRestVariableAccess()) - or - last(super.getRestVariableAccess(), last, c) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getClass(), pred, c) and - succ = this and - c.(MatchingCompletion).getValue() = true - or - exists(AstNode next | - pred = this and - c.(MatchingCompletion).getValue() = true and - first(next, succ) - | - next = super.getValue(0) - or - not exists(super.getValue(_)) and - next = super.getRestVariableAccess() - ) - or - last(max(int i | | super.getValue(i) order by i), pred, c) and - first(super.getRestVariableAccess(), succ) and - c.(MatchingCompletion).getValue() = true - or - exists(int i | - last(super.getValue(i), pred, c) and - first(super.getValue(i + 1), succ) and - c.(MatchingCompletion).getValue() = true - ) - } - } - - private class LineLiteralTree extends LeafTree instanceof LineLiteral { } - - private class FileLiteralTree extends LeafTree instanceof FileLiteral { } - - private class EncodingLiteralTree extends LeafTree instanceof EncodingLiteral { } - - private class AlternativePatternTree extends PreOrderTree instanceof AlternativePattern { - final override predicate propagatesAbnormal(AstNode child) { child = super.getAnAlternative() } - - final override predicate last(AstNode last, Completion c) { - last(super.getAnAlternative(), last, c) and - c.(MatchingCompletion).getValue() = true - or - last(max(int i | | super.getAlternative(i) order by i), last, c) and - c.(MatchingCompletion).getValue() = false - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - pred = this and - first(super.getAlternative(0), succ) and - c instanceof SimpleCompletion - or - exists(int i | - last(super.getAlternative(i), pred, c) and - first(super.getAlternative(i + 1), succ) and - c.(MatchingCompletion).getValue() = false - ) - } - } - - private class AsPatternTree extends PreOrderTree instanceof AsPattern { - final override predicate propagatesAbnormal(AstNode child) { child = super.getPattern() } - - final override predicate last(AstNode last, Completion c) { - last(super.getPattern(), last, c) and - c.(MatchingCompletion).getValue() = false - or - last(super.getVariableAccess(), last, any(SimpleCompletion x)) and - c.(MatchingCompletion).getValue() = true and - not c instanceof NestedCompletion - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - pred = this and - first(super.getPattern(), succ) and - c instanceof SimpleCompletion - or - last(super.getPattern(), pred, c) and - first(super.getVariableAccess(), succ) and - c.(MatchingCompletion).getValue() = true - } - } - - private class ParenthesizedPatternTree extends StandardPreOrderTree instanceof ParenthesizedPattern - { - override ControlFlowTree getChildNode(int i) { result = super.getPattern() and i = 0 } - } - - private class ReferencePatternTree extends StandardPreOrderTree instanceof ReferencePattern { - override ControlFlowTree getChildNode(int i) { result = super.getExpr() and i = 0 } - } - - private class InClauseTree extends PreOrderTree instanceof InClause { - final override predicate propagatesAbnormal(AstNode child) { - child = super.getPattern() or - child = super.getCondition() - } - - private predicate lastCondition(AstNode last, BooleanCompletion c, boolean flag) { - last(super.getCondition(), last, c) and - ( - flag = true and super.hasIfCondition() - or - flag = false and super.hasUnlessCondition() - ) - } - - final override predicate last(AstNode last, Completion c) { - last(super.getPattern(), last, c) and - c.(MatchingCompletion).getValue() = false - or - exists(BooleanCompletion bc, boolean flag, MatchingCompletion mc | - this.lastCondition(last, bc, flag) and - c = - any(NestedMatchingCompletion nmc | - nmc.getInnerCompletion() = bc and nmc.getOuterCompletion() = mc - ) - | - mc.getValue() = false and - bc.getValue() = flag.booleanNot() - or - not exists(super.getBody()) and - mc.getValue() = true and - bc.getValue() = flag - ) - or - last(super.getBody(), last, c) - or - not exists(super.getBody()) and - not exists(super.getCondition()) and - last(super.getPattern(), last, c) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - pred = this and - first(super.getPattern(), succ) and - c instanceof SimpleCompletion - or - exists(Expr next | - last(super.getPattern(), pred, c) and - c.(MatchingCompletion).getValue() = true and - first(next, succ) - | - next = super.getCondition() - or - not exists(super.getCondition()) and next = super.getBody() - ) - or - exists(boolean flag | - this.lastCondition(pred, c, flag) and - c.(BooleanCompletion).getValue() = flag and - first(super.getBody(), succ) - ) - } - } - - private class CharacterTree extends LeafTree instanceof CharacterLiteral { } - - private class ClassDeclarationTree extends NamespaceTree instanceof ClassDeclaration { - /** Gets the `i`th child in the body of this block. */ - final override AstNode getBodyChild(int i, boolean rescuable) { - result = super.getScopeExpr() and i = 0 and rescuable = false - or - result = super.getSuperclassExpr() and - i = count(super.getScopeExpr()) and - rescuable = true - or - result = - super - .getBodyChild(i - count(super.getScopeExpr()) - count(super.getSuperclassExpr()), - rescuable) - } - } - - private class ClassVariableTree extends LeafTree instanceof ClassVariableAccess { } - - private class ConditionalExprTree extends PostOrderTree instanceof ConditionalExpr { - final override predicate propagatesAbnormal(AstNode child) { - child = super.getCondition() or child = super.getBranch(_) - } - - final override predicate first(AstNode first) { first(super.getCondition(), first) } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - exists(boolean b | - last(super.getCondition(), pred, c) and - b = c.(BooleanCompletion).getValue() - | - first(super.getBranch(b), succ) - or - not exists(super.getBranch(b)) and - succ = this - ) - or - last(super.getBranch(_), pred, c) and - succ = this and - c instanceof NormalCompletion - } - } - - private class ConditionalLoopTree extends PostOrderTree instanceof ConditionalLoop { - final override predicate propagatesAbnormal(AstNode child) { child = super.getCondition() } - - final override predicate first(AstNode first) { first(super.getCondition(), first) } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getCondition(), pred, c) and - super.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue()) and - first(super.getBody(), succ) - or - last(super.getBody(), pred, c) and - first(super.getCondition(), succ) and - c.continuesLoop() - or - last(super.getBody(), pred, c) and - first(super.getBody(), succ) and - c instanceof RedoCompletion - or - succ = this and - ( - last(super.getCondition(), pred, c) and - super.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue().booleanNot()) - or - last(super.getBody(), pred, c) and - not c.continuesLoop() and - not c instanceof BreakCompletion and - not c instanceof RedoCompletion - or - last(super.getBody(), pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) - ) - } - } - - private class ConstantAccessTree extends PostOrderTree instanceof ConstantAccess { - ConstantAccessTree() { - not this instanceof ClassDeclaration and - not this instanceof ModuleDeclaration and - // constant accesses with scope expression in compound assignments are desugared - not ( - this = any(AssignOperation op).getLeftOperand() and - exists(super.getScopeExpr()) - ) - } - - final override predicate propagatesAbnormal(AstNode child) { child = super.getScopeExpr() } - - final override predicate first(AstNode first) { - first(super.getScopeExpr(), first) - or - not exists(super.getScopeExpr()) and - first = this - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getScopeExpr(), pred, c) and - succ = this and - c instanceof NormalCompletion - } - } - - /** A parameter that may have a default value. */ - abstract class DefaultValueParameterTree extends ControlFlowTree { - abstract Expr getDefaultValueExpr(); - - abstract AstNode getAccessNode(); - - predicate hasDefaultValue() { exists(this.getDefaultValueExpr()) } - - final override predicate propagatesAbnormal(AstNode child) { - child = this.getDefaultValueExpr() or child = this.getAccessNode() - } - - final override predicate first(AstNode first) { first = this.getAccessNode() } - - final override predicate last(AstNode last, Completion c) { - last(this.getDefaultValueExpr(), last, c) and - c instanceof NormalCompletion - or - last = this.getAccessNode() and - ( - not this.hasDefaultValue() and - c instanceof SimpleCompletion - or - this.hasDefaultValue() and - c.(MatchingCompletion).getValue() = true and - c.isValidFor(this) - ) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - pred = this.getAccessNode() and - first(this.getDefaultValueExpr(), succ) and - c.(MatchingCompletion).getValue() = false - } - } - - private class DestructuredParameterTree extends StandardPostOrderTree instanceof DestructuredParameter - { - final override ControlFlowTree getChildNode(int i) { result = super.getElement(i) } - } - - private class DesugaredTree extends ControlFlowTree { - ControlFlowTree desugared; - - DesugaredTree() { desugared = this.getDesugared() } - - final override predicate propagatesAbnormal(AstNode child) { - desugared.propagatesAbnormal(child) - } - - final override predicate first(AstNode first) { desugared.first(first) } - - final override predicate last(AstNode last, Completion c) { desugared.last(last, c) } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() } - } - - private class DoBlockTree extends CallableTree instanceof DoBlock { - /** Gets the `i`th child in the body of this block. */ - final override AstNode getBodyChild(int i) { - result = super.getParameter(i) - or - result = super.getLocalVariable(i - super.getNumberOfParameters()) - or - result = super.getBody() and - i = super.getNumberOfParameters() + count(super.getALocalVariable()) - } - } - - private class EmptyStatementTree extends LeafTree instanceof EmptyStmt { } - - class EndBlockTree extends StmtSequenceTree instanceof EndBlock { - override predicate first(AstNode first) { first = this } - - override predicate succ(AstNode pred, AstNode succ, Completion c) { - // Normal left-to-right evaluation in the body - exists(int i | - last(super.getBodyChild(i, _), pred, c) and - first(super.getBodyChild(i + 1, _), succ) and - c instanceof NormalCompletion - ) - } - } - - private class ForwardedArgumentsTree extends LeafTree instanceof ForwardedArguments { } - - private class ForwardParameterTree extends LeafTree instanceof ForwardParameter { } - - private class GlobalVariableTree extends LeafTree instanceof GlobalVariableAccess { } - - private class HashSplatNilParameterTree extends LeafTree instanceof HashSplatNilParameter { } - - private class HashSplatParameterTree extends NonDefaultValueParameterTree instanceof HashSplatParameter - { } - - private class HereDocTree extends StandardPostOrderTree instanceof HereDoc { - final override ControlFlowTree getChildNode(int i) { result = super.getComponent(i) } - } - - private class InstanceVariableTree extends StandardPostOrderTree instanceof InstanceVariableAccess - { - final override ControlFlowTree getChildNode(int i) { result = super.getReceiver() and i = 0 } - } - - private class KeywordParameterTree extends DefaultValueParameterTree instanceof KeywordParameter { - final override Expr getDefaultValueExpr() { result = super.getDefaultValue() } - - final override AstNode getAccessNode() { result = super.getDefiningAccess() } - } - - private class LambdaTree extends CallableTree instanceof Lambda { - /** Gets the `i`th child in the body of this block. */ - final override AstNode getBodyChild(int i) { - result = super.getParameter(i) - or - result = super.getBody() and i = super.getNumberOfParameters() - } - } - - private class LocalVariableAccessTree extends LeafTree instanceof LocalVariableAccess { } - - private class LogicalAndTree extends PostOrderTree instanceof LogicalAndExpr { - final override predicate propagatesAbnormal(AstNode child) { child = super.getAnOperand() } - - final override predicate first(AstNode first) { first(super.getLeftOperand(), first) } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getLeftOperand(), pred, c) and - c instanceof TrueCompletion and - first(super.getRightOperand(), succ) - or - last(super.getLeftOperand(), pred, c) and - c instanceof FalseCompletion and - succ = this - or - last(super.getRightOperand(), pred, c) and - c instanceof NormalCompletion and - succ = this - } - } - - private class LogicalNotTree extends PostOrderTree instanceof NotExpr { - final override predicate propagatesAbnormal(AstNode child) { child = super.getOperand() } - - final override predicate first(AstNode first) { first(super.getOperand(), first) } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - succ = this and - last(super.getOperand(), pred, c) and - c instanceof NormalCompletion - } - } - - private class LogicalOrTree extends PostOrderTree instanceof LogicalOrExpr { - final override predicate propagatesAbnormal(AstNode child) { child = super.getAnOperand() } - - final override predicate first(AstNode first) { first(super.getLeftOperand(), first) } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getLeftOperand(), pred, c) and - c instanceof FalseCompletion and - first(super.getRightOperand(), succ) - or - last(super.getLeftOperand(), pred, c) and - c instanceof TrueCompletion and - succ = this - or - last(super.getRightOperand(), pred, c) and - c instanceof NormalCompletion and - succ = this - } - } - - private class MethodCallTree extends CallTree instanceof MethodCall { - final override ControlFlowTree getChildNode(int i) { - result = super.getReceiver() and i = 0 - or - result = super.getArgument(i - 1) - or - result = super.getBlock() and i = 1 + super.getNumberOfArguments() - } - } - - private class MethodNameTree extends LeafTree instanceof MethodName, AstInternal::TTokenMethodName - { } - - private class MethodTree extends CallableTree instanceof Method { - /** Gets the `i`th child in the body of this block. */ - final override AstNode getBodyChild(int i) { - result = super.getParameter(i) - or - result = super.getBody() and i = super.getNumberOfParameters() - } - } - - private class ModuleDeclarationTree extends NamespaceTree instanceof ModuleDeclaration { - /** Gets the `i`th child in the body of this block. */ - final override AstNode getBodyChild(int i, boolean rescuable) { - result = super.getScopeExpr() and i = 0 and rescuable = false - or - result = NamespaceTree.super.getBodyChild(i - count(super.getScopeExpr()), rescuable) - } - } - - /** - * Namespaces (i.e. modules or classes) behave like other `BodyStmt`s except they are - * executed in pre-order rather than post-order. We do this in order to insert a write for - * `self` before any subsequent reads in the namespace body. - */ - private class NamespaceTree extends BodyStmtTree instanceof Namespace { - final override predicate first(AstNode first) { first = this } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - BodyStmtTree.super.succ(pred, succ, c) - or - pred = this and - super.first(succ) and - c instanceof SimpleCompletion - } - - final override predicate last(AstNode last, Completion c) { - super.last(last, c) - or - not exists(this.getAChild(_)) and - last = this and - c.isValidFor(this) - } - } - - private class NilTree extends LeafTree instanceof NilLiteral { } - - private class NumericLiteralTree extends LeafTree instanceof NumericLiteral { } - - private class OptionalParameterTree extends DefaultValueParameterTree instanceof OptionalParameter - { - final override Expr getDefaultValueExpr() { result = super.getDefaultValue() } - - final override AstNode getAccessNode() { result = super.getDefiningAccess() } - } - - private class PairTree extends StandardPostOrderTree instanceof Pair { - final override ControlFlowTree getChildNode(int i) { - result = super.getKey() and i = 0 - or - result = super.getValue() and i = 1 - } - } - - private class RangeLiteralTree extends StandardPostOrderTree instanceof RangeLiteral { - final override ControlFlowTree getChildNode(int i) { - result = super.getBegin() and i = 0 - or - result = super.getEnd() and i = 1 - } - } - - private class RedoStmtTree extends LeafTree instanceof RedoStmt { } - - private class RescueModifierTree extends PostOrderTree instanceof RescueModifierExpr { - final override predicate propagatesAbnormal(AstNode child) { child = super.getHandler() } - - final override predicate first(AstNode first) { first(super.getBody(), first) } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getBody(), pred, c) and - ( - c instanceof RaiseCompletion and - first(super.getHandler(), succ) - or - not c instanceof RaiseCompletion and - succ = this - ) - or - last(super.getHandler(), pred, c) and - c instanceof NormalCompletion and - succ = this - } - } - - private class RescueTree extends PostOrderTree instanceof RescueClause { - final override predicate propagatesAbnormal(AstNode child) { - child = super.getAnException() or - child = super.getBody() - } - - final override predicate first(AstNode first) { - first(super.getException(0), first) - or - not exists(super.getException(0)) and - ( - first(super.getVariableExpr(), first) - or - not exists(super.getVariableExpr()) and - ( - first(super.getBody(), first) - or - not exists(super.getBody()) and first = this - ) - ) - } - - private Expr getLastException() { - exists(int i | result = super.getException(i) and not exists(super.getException(i + 1))) - } - - predicate lastNoMatch(AstNode last, Completion c) { - last(this.getLastException(), last, c) and - c.(MatchingCompletion).getValue() = false - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - last(super.getAnException(), pred, c) and - c.(MatchingCompletion).getValue() = true and - ( - first(super.getVariableExpr(), succ) - or - not exists(super.getVariableExpr()) and - ( - first(super.getBody(), succ) - or - not exists(super.getBody()) and succ = this - ) - ) - or - exists(int i | - last(super.getException(i), pred, c) and - c.(MatchingCompletion).getValue() = false and - first(super.getException(i + 1), succ) - ) - or - last(super.getVariableExpr(), pred, c) and - c instanceof NormalCompletion and - ( - first(super.getBody(), succ) - or - not exists(super.getBody()) and succ = this - ) - or - last(super.getBody(), pred, c) and - c instanceof NormalCompletion and - succ = this - } - } - - private class RetryStmtTree extends LeafTree instanceof RetryStmt { } - - private class ReturningStmtTree extends StandardPostOrderTree instanceof ReturningStmt { - final override ControlFlowTree getChildNode(int i) { result = super.getValue() and i = 0 } - } - - private class SimpleParameterTree extends NonDefaultValueParameterTree instanceof SimpleParameter { - } - - // Corner case: For duplicated '_' parameters, only the first occurrence has a defining - // access. For subsequent parameters we simply include the parameter itself in the CFG - private class SimpleParameterTreeDupUnderscore extends LeafTree instanceof SimpleParameter { - SimpleParameterTreeDupUnderscore() { not exists(this.getDefiningAccess()) } - } - - private class SingletonClassTree extends BodyStmtTree instanceof SingletonClass { - final override predicate first(AstNode first) { - super.first(first) - or - not exists(this.getAChild(_)) and - first = this - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - BodyStmtTree.super.succ(pred, succ, c) - or - succ = this and - super.last(pred, c) - } - - final override predicate last(AstNode last, Completion c) { - last = this and - c.isValidFor(this) - } - - /** Gets the `i`th child in the body of this block. */ - final override AstNode getBodyChild(int i, boolean rescuable) { - ( - result = super.getValue() and i = 0 and rescuable = false - or - result = BodyStmtTree.super.getBodyChild(i - 1, rescuable) - ) - } - } - - private class SingletonMethodTree extends CallableTree instanceof SingletonMethod { - /** Gets the `i`th child in the body of this block. */ - final override AstNode getBodyChild(int i) { - result = super.getParameter(i) - or - result = super.getBody() and i = super.getNumberOfParameters() - } - - override predicate first(AstNode first) { first(super.getObject(), first) } - - override predicate succ(AstNode pred, AstNode succ, Completion c) { - CallableTree.super.succ(pred, succ, c) - or - last(super.getObject(), pred, c) and - succ = this and - c instanceof NormalCompletion - } - } - - private class SplatParameterTree extends NonDefaultValueParameterTree instanceof SplatParameter { - } - - class StmtSequenceTree extends PostOrderTree instanceof StmtSequence { - override predicate propagatesAbnormal(AstNode child) { child = super.getAStmt() } - - override predicate first(AstNode first) { - // If this sequence contains any statements, go to the first one. - first(super.getStmt(0), first) - or - // Otherwise, treat this node as a leaf node. - not exists(super.getStmt(0)) and first = this - } - - /** Gets the `i`th child in the body of this body statement. */ - AstNode getBodyChild(int i, boolean rescuable) { - result = super.getStmt(i) and - rescuable = true - } - - final AstNode getLastBodyChild() { - exists(int i | - result = this.getBodyChild(i, _) and - not exists(this.getBodyChild(i + 1, _)) - ) - } - - override predicate succ(AstNode pred, AstNode succ, Completion c) { - // Normal left-to-right evaluation in the body - exists(int i | - last(this.getBodyChild(i, _), pred, c) and - first(this.getBodyChild(i + 1, _), succ) and - c instanceof NormalCompletion - ) - or - succ = this and - last(this.getLastBodyChild(), pred, c) and - c instanceof NormalCompletion - } - } - - private class StringConcatenationTree extends StandardPostOrderTree instanceof StringConcatenation - { - final override ControlFlowTree getChildNode(int i) { result = super.getString(i) } - } - - private class StringlikeLiteralTree extends StandardPostOrderTree instanceof StringlikeLiteral { - StringlikeLiteralTree() { - not this instanceof HereDoc and - not this instanceof AstInternal::TSimpleSymbolLiteralSynth - } - - final override ControlFlowTree getChildNode(int i) { result = super.getComponent(i) } - } - - private class StringComponentTree extends LeafTree instanceof StringComponent { - StringComponentTree() { - // Interpolations contain `StmtSequence`s, so they shouldn't be treated as leaf nodes. - not this instanceof StringInterpolationComponent and - // In the interests of brevity we treat regexes as string literals when constructing the CFG. - // Thus we must exclude regex interpolations here too. - not this instanceof RegExpInterpolationComponent - } - } - - private class ToplevelTree extends BodyStmtTree instanceof Toplevel { - final override AstNode getBodyChild(int i, boolean rescuable) { - result = super.getBeginBlock(i) and rescuable = true - or - result = BodyStmtTree.super.getBodyChild(i - count(super.getABeginBlock()), rescuable) - } - } - - private class UndefStmtTree extends StandardPreOrderTree instanceof UndefStmt { - final override ControlFlowTree getChildNode(int i) { result = super.getMethodName(i) } - } - - private class WhenTree extends ControlFlowTree instanceof WhenClause { - final override predicate propagatesAbnormal(AstNode child) { - child = [super.getAPattern(), super.getBody()] - } - - final Expr getLastPattern() { - exists(int i | - result = super.getPattern(i) and - not exists(super.getPattern(i + 1)) - ) - } - - final override predicate first(AstNode first) { first(super.getPattern(0), first) } - - final override predicate last(AstNode last, Completion c) { - last = this and - c.isValidFor(this) and - c.(ConditionalCompletion).getValue() = false - or - last(super.getBody(), last, c) - } - - final override predicate succ(AstNode pred, AstNode succ, Completion c) { - pred = this and - c.isValidFor(this) and - c.(ConditionalCompletion).getValue() = true and - first(super.getBody(), succ) - or - exists(int i, Expr p, boolean b | - p = super.getPattern(i) and - last(p, pred, c) and - b = c.(ConditionalCompletion).getValue() - | - b = true and - succ = this - or - b = false and - first(super.getPattern(i + 1), succ) - or - not exists(super.getPattern(i + 1)) and - succ = this - ) - } - } -} - -private Scope parent(Scope n) { - result = n.getOuterScope() and - not n instanceof CfgScopeImpl -} - -cached -private CfgScope getCfgScopeImpl(AstNode n) { result = parent*(scopeOfInclSynth(n)) } - -/** Gets the CFG scope of node `n`. */ -pragma[inline] -CfgScope getCfgScope(AstNode n) { - exists(AstNode n0 | - pragma[only_bind_into](n0) = n and - pragma[only_bind_into](result) = getCfgScopeImpl(n0) - ) -} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll index 387fb03dc9a8..123f02760493 100644 --- a/ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll @@ -6,7 +6,7 @@ private import codeql.ruby.CFG /** Holds if the guard `guard` controls block `bb` upon evaluating to `branch`. */ pragma[nomagic] predicate guardControlsBlock(CfgNodes::AstCfgNode guard, BasicBlock bb, boolean branch) { - exists(ConditionBlock conditionBlock, ConditionalSuccessor s | + exists(BasicBlock conditionBlock, ConditionalSuccessor s | guard = conditionBlock.getLastNode() and s.getValue() = branch and conditionBlock.edgeDominates(bb, s) diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll index 45b299a5d2b7..36061ea5f635 100644 --- a/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll @@ -3,22 +3,22 @@ overlay[local] module; private import codeql.ruby.AST -private import Completion +private import codeql.controlflow.SuccessorType /** A call that definitely does not return (conservative analysis). */ abstract class NonReturningCall extends MethodCall { - /** Gets a valid completion for this non-returning call. */ - abstract Completion getACompletion(); + /** Gets a valid successor type for this non-returning call. */ + abstract AbruptSuccessor getASuccessorType(); } private class RaiseCall extends NonReturningCall { RaiseCall() { this.getMethodName() = "raise" } - override RaiseCompletion getACompletion() { not result instanceof NestedCompletion } + override ExceptionSuccessor getASuccessorType() { any() } } private class ExitCall extends NonReturningCall { ExitCall() { this.getMethodName() in ["abort", "exit"] } - override ExitCompletion getACompletion() { not result instanceof NestedCompletion } + override ExitSuccessor getASuccessorType() { any() } } diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll deleted file mode 100644 index 737f450b4f23..000000000000 --- a/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll +++ /dev/null @@ -1,348 +0,0 @@ -/** - * Provides classes and predicates relevant for splitting the control flow graph. - */ -overlay[local] -module; - -private import codeql.ruby.AST as Ast -private import Completion as Comp -private import Comp -private import ControlFlowGraphImpl -private import codeql.ruby.controlflow.ControlFlowGraph - -cached -private module Cached { - cached - newtype TSplitKind = - TConditionalCompletionSplitKind() { forceCachingInSameStage() } or - TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::BodyStmtTree t).getNestLevel() } - - cached - newtype TSplit = - TConditionalCompletionSplit(ConditionalCompletion c) or - TEnsureSplit(EnsureSplitting::EnsureSplitType type, int nestLevel) { - nestLevel = any(Trees::BodyStmtTree t).getNestLevel() - } -} - -import Cached - -/** A split for a control flow node. */ -class Split extends TSplit { - /** Gets a textual representation of this split. */ - string toString() { none() } -} - -module ConditionalCompletionSplitting { - /** - * A split for conditional completions. For example, in - * - * ```rb - * def method x - * if x < 2 and x > 0 - * puts "x is 1" - * end - * end - * ``` - * - * we record whether `x < 2` and `x > 0` evaluate to `true` or `false`, and - * restrict the edges out of `x < 2 and x > 0` accordingly. - */ - class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit { - ConditionalCompletion completion; - - ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) } - - ConditionalCompletion getCompletion() { result = completion } - - override string toString() { result = completion.toString() } - } - - private class ConditionalCompletionSplitKind_ extends SplitKind, TConditionalCompletionSplitKind { - override int getListOrder() { result = 0 } - - override predicate isEnabled(AstNode n) { this.appliesTo(n) } - - override string toString() { result = "ConditionalCompletion" } - } - - module ConditionalCompletionSplittingInput { - private import Completion as Comp - - class ConditionalCompletion = Comp::ConditionalCompletion; - - class ConditionalCompletionSplitKind extends ConditionalCompletionSplitKind_, TSplitKind { } - - class ConditionalCompletionSplit = ConditionalCompletionSplitting::ConditionalCompletionSplit; - - bindingset[parent, parentCompletion] - predicate condPropagateExpr( - AstNode parent, ConditionalCompletion parentCompletion, AstNode child, - ConditionalCompletion childCompletion - ) { - child = parent.(Ast::NotExpr).getOperand() and - childCompletion.(BooleanCompletion).getDual() = parentCompletion - or - childCompletion = parentCompletion and - ( - child = parent.(Ast::LogicalAndExpr).getAnOperand() - or - child = parent.(Ast::LogicalOrExpr).getAnOperand() - or - child = parent.(Ast::StmtSequence).getLastStmt() - or - child = parent.(Ast::ConditionalExpr).getBranch(_) - ) - } - } - - int getNextListOrder() { result = 1 } - - private class ConditionalCompletionSplitImpl extends SplitImplementations::ConditionalCompletionSplitting::ConditionalCompletionSplitImpl - { - override predicate hasEntry(AstNode pred, AstNode succ, Completion c) { - super.hasEntry(pred, succ, c) - or - // a non-standard case is needed for `when` clauses - succ(pred, succ, c) and - succ instanceof Ast::WhenClause and - c = this.getCompletion() - } - } -} - -class ConditionalCompletionSplit = ConditionalCompletionSplitting::ConditionalCompletionSplit; - -module EnsureSplitting { - /** - * The type of a split `ensure` node. - * - * The type represents one of the possible ways of entering an `ensure` - * block. For example, if a block ends with a `return` statement, then - * the `ensure` block must end with a `return` as well (provided that - * the `ensure` block executes normally). - */ - class EnsureSplitType extends SuccessorType { - EnsureSplitType() { not this instanceof ConditionalSuccessor } - - /** Holds if this split type matches entry into an `ensure` block with completion `c`. */ - predicate isSplitForEntryCompletion(Completion c) { - if c instanceof NormalCompletion - then - // If the entry into the `ensure` block completes with any normal completion, - // it simply means normal execution after the `ensure` block - this instanceof DirectSuccessor - else this = c.getAMatchingSuccessorType() - } - } - - /** A node that belongs to an `ensure` block. */ - private class EnsureNode extends AstNode { - private Trees::BodyStmtTree block; - - EnsureNode() { this = block.getAnEnsureDescendant() } - - int getNestLevel() { result = block.getNestLevel() } - - /** Holds if this node is the entry node in the `ensure` block it belongs to. */ - predicate isEntryNode() { first(block.getEnsure(), this) } - - Ast::BodyStmt getBlock() { result = block } - - pragma[noinline] - predicate isEntered(AstNode pred, int nestLevel, Completion c) { - this.isEntryNode() and - nestLevel = this.getNestLevel() and - succ(pred, this, c) and - // the entry node may be reachable via a backwards loop edge; in this case - // the split has already been entered - not pred = block.getAnEnsureDescendant() - } - } - - /** - * A split for nodes belonging to an `ensure` block, which determines how to - * continue execution after leaving the `ensure` block. For example, in - * - * ```rb - * begin - * if x - * raise "Exception" - * end - * ensure - * puts "Ensure" - * end - * ``` - * - * all control flow nodes in the `ensure` block have two splits: one representing - * normal execution of the body (when `x` evaluates to `true`), and one representing - * exceptional execution of the body (when `x` evaluates to `false`). - */ - class EnsureSplit extends Split, TEnsureSplit { - private EnsureSplitType type; - private int nestLevel; - - EnsureSplit() { this = TEnsureSplit(type, nestLevel) } - - /** - * Gets the type of this `ensure` split, that is, how to continue execution after the - * `ensure` block. - */ - EnsureSplitType getType() { result = type } - - /** Gets the nesting level. */ - int getNestLevel() { result = nestLevel } - - override string toString() { - if type instanceof DirectSuccessor - then result = "" - else - if nestLevel > 0 - then result = "ensure(" + nestLevel + "): " + type.toString() - else result = "ensure: " + type.toString() - } - } - - private int getListOrder(EnsureSplitKind kind) { - result = ConditionalCompletionSplitting::getNextListOrder() + kind.getNestLevel() - } - - int getNextListOrder() { - result = max([getListOrder(_) + 1, ConditionalCompletionSplitting::getNextListOrder()]) - } - - private class EnsureSplitKind extends SplitKind, TEnsureSplitKind { - private int nestLevel; - - EnsureSplitKind() { this = TEnsureSplitKind(nestLevel) } - - /** Gets the nesting level. */ - int getNestLevel() { result = nestLevel } - - override int getListOrder() { result = getListOrder(this) } - - override string toString() { result = "ensure (" + nestLevel + ")" } - } - - private class EnsureSplitImpl extends SplitImpl instanceof EnsureSplit { - override EnsureSplitKind getKind() { result.getNestLevel() = super.getNestLevel() } - - override predicate hasEntry(AstNode pred, AstNode succ, Completion c) { - succ.(EnsureNode).isEntered(pred, super.getNestLevel(), c) and - super.getType().isSplitForEntryCompletion(c) - } - - override predicate hasEntryScope(CfgScope scope, AstNode first) { none() } - - /** - * Holds if this split applies to `pred`, where `pred` is a valid predecessor. - */ - private predicate appliesToPredecessor(AstNode pred) { - this.appliesTo(pred) and - (succ(pred, _, _) or succExit(_, pred, _)) - } - - pragma[noinline] - private predicate exit0(AstNode pred, Trees::BodyStmtTree block, int nestLevel, Completion c) { - this.appliesToPredecessor(pred) and - nestLevel = block.getNestLevel() and - block.last(pred, c) - } - - /** - * Holds if `pred` may exit this split with completion `c`. The Boolean - * `inherited` indicates whether `c` is an inherited completion from the - * body. - */ - private predicate exit(AstNode pred, Completion c, boolean inherited) { - exists(Trees::BodyStmtTree block, EnsureSplitType type | - this.exit0(pred, block, super.getNestLevel(), c) and - type = super.getType() - | - if last(block.getEnsure(), pred, c) - then - // `ensure` block can itself exit with completion `c`: either `c` must - // match this split, `c` must be an abnormal completion, or this split - // does not require another completion to be recovered - inherited = false and - ( - type = c.getAMatchingSuccessorType() - or - not c instanceof NormalCompletion - or - type instanceof DirectSuccessor - ) - else ( - // `ensure` block can exit with inherited completion `c`, which must - // match this split - inherited = true and - type = c.getAMatchingSuccessorType() and - not type instanceof DirectSuccessor - ) - ) - or - // If this split is normal, and an outer split can exit based on an inherited - // completion, we need to exit this split as well. For example, in - // - // ```rb - // def m(b1, b2) - // if b1 - // return - // end - // ensure - // begin - // if b2 - // raise "Exception" - // end - // ensure - // puts "inner ensure" - // end - // end - // ``` - // - // if the outer split for `puts "inner ensure"` is `return` and the inner split - // is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner - // split must be able to exit with a `return` completion. - this.appliesToPredecessor(pred) and - exists(EnsureSplitImpl outer | - outer.(EnsureSplit).getNestLevel() = super.getNestLevel() - 1 and - outer.exit(pred, c, inherited) and - super.getType() instanceof DirectSuccessor and - inherited = true - ) - } - - override predicate hasExit(AstNode pred, AstNode succ, Completion c) { - succ(pred, succ, c) and - ( - this.exit(pred, c, _) - or - this.exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) - ) - } - - override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) { - succExit(scope, last, c) and - ( - this.exit(last, c, _) - or - this.exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) - ) - } - - override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { - this.appliesToPredecessor(pred) and - succ(pred, succ, c) and - succ = - any(EnsureNode en | - if en.isEntryNode() and en.getBlock() != pred.(EnsureNode).getBlock() - then - // entering a nested `ensure` block - en.getNestLevel() > super.getNestLevel() - else - // staying in the same (possibly nested) `ensure` block as `pred` - en.getNestLevel() >= super.getNestLevel() - ) - } - } -} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll b/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll index 08ca08732b6b..d95740a8c867 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll @@ -384,20 +384,7 @@ module Ssa { inp = SsaImpl::phiHasInputFromBlock(this, bb) } - private string getSplitString() { - result = this.getBasicBlock().getFirstNode().(CfgNodes::AstCfgNode).getSplitsString() - } - - override string toString() { - exists(string prefix | - prefix = "[" + this.getSplitString() + "] " - or - not exists(this.getSplitString()) and - prefix = "" - | - result = prefix + "phi" - ) - } + override string toString() { result = "phi" } /* * The location of a phi node is the same as the location of the first node diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll index 3c647f41bbbe..3d3e8a4d0d04 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll @@ -183,7 +183,7 @@ class NormalCall extends DataFlowCall, TNormalCall { override CfgNodes::ExprNodes::CallCfgNode asCall() { result = c } - override DataFlowCallable getEnclosingCallable() { result = TCfgScope(c.getScope()) } + override DataFlowCallable getEnclosingCallable() { result = TCfgScope(c.getEnclosingCallable()) } override string toString() { result = c.toString() } @@ -700,7 +700,7 @@ private CfgScope privateFilter(DataFlowCall call) { // This may remove some plausible targets, but also removes a lot of // implausible targets ( - isToplevelMethodInFile(result, call.asCall().getFile()) or + isToplevelMethodInFile(result, call.asCall().getLocation().getFile()) or not isToplevelMethodInFile(result, _) ) } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 9646592c0c23..a2d4df49f166 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -286,7 +286,6 @@ predicate isNonConstantExpr(CfgNodes::ExprCfgNode n) { /** Provides logic related to captured variables. */ module VariableCapture { private import codeql.dataflow.VariableCapture as Shared - private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks private predicate closureFlowStep(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) { e1 = getALastEvalNode(e2) @@ -297,13 +296,11 @@ module VariableCapture { ) } - private module CaptureInput implements Shared::InputSig { + private module CaptureInput implements Shared::InputSig { private import codeql.ruby.controlflow.ControlFlowGraph as Cfg private import TaintTrackingPrivate as TaintTrackingPrivate - Callable basicBlockGetEnclosingCallable(BasicBlocks::Cfg::BasicBlock bb) { - result = bb.getScope() - } + Callable basicBlockGetEnclosingCallable(BasicBlock bb) { result = bb.getScope() } class CapturedVariable extends LocalVariable { CapturedVariable() { @@ -375,7 +372,7 @@ module VariableCapture { class ClosureExpr = CaptureInput::ClosureExpr; - module Flow = Shared::Flow; + module Flow = Shared::Flow; private Flow::ClosureNode asClosureNode(Node n) { result = n.(CaptureNode).getSynthesizedCaptureNode() @@ -855,7 +852,7 @@ class ReturningStatementNode extends NodeImpl, TReturningNode { /** Gets the expression corresponding to this node. */ CfgNodes::ReturningCfgNode getReturningNode() { result = n } - override CfgScope getCfgScopeImpl() { result = n.getScope() } + override CfgScope getCfgScopeImpl() { result = n.getEnclosingCallable() } override Location getLocationImpl() { result = n.getLocation() } @@ -1355,7 +1352,7 @@ module ArgumentNodes { this.sourceArgumentOf(call.asCall(), pos) } - override CfgScope getCfgScopeImpl() { result = yield.getScope() } + override CfgScope getCfgScopeImpl() { result = yield.getEnclosingCallable() } override Location getLocationImpl() { result = yield.getLocation() } } @@ -1385,7 +1382,7 @@ module ArgumentNodes { this.sourceArgumentOf(call.asCall(), pos) } - override CfgScope getCfgScopeImpl() { result = sup.getScope() } + override CfgScope getCfgScopeImpl() { result = sup.getEnclosingCallable() } override Location getLocationImpl() { result = sup.getLocation() } } @@ -1608,7 +1605,7 @@ private module ReturnNodes { private predicate isValid(CfgNodes::ReturningCfgNode node) { exists(ReturningStmt stmt, Callable scope | stmt = node.getAstNode() and - scope = node.getScope() + scope = node.getEnclosingCallable() | stmt instanceof ReturnStmt and (scope instanceof Method or scope instanceof SingletonMethod or scope instanceof Lambda) @@ -1628,8 +1625,8 @@ private module ReturnNodes { class ExplicitReturnNode extends SourceReturnNode, ReturningStatementNode { ExplicitReturnNode() { isValid(n) and - n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and - n.getScope() instanceof Callable + n.getASuccessor() instanceof ControlFlow::NormalExitNode and + n.getEnclosingCallable() instanceof Callable } override ReturnKind getKindSource() { @@ -1648,7 +1645,7 @@ private module ReturnNodes { private AstNode implicitReturn(Callable c, ExprNode n) { exists(CfgNodes::ExprCfgNode en | en = n.getExprNode() and - en.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and + en.getASuccessor() instanceof ControlFlow::NormalExitNode and n.(NodeImpl).getCfgScope() = c and result = en.getExpr() ) @@ -2392,9 +2389,7 @@ module TypeInference { private predicate hasTypeCheckedRead( Ssa::Definition def, CfgNodes::ExprCfgNode caseRead, CfgNodes::ExprCfgNode read, Module m ) { - exists( - CfgNodes::ExprCfgNode pattern, ConditionBlock cb, CfgNodes::ExprNodes::CaseExprCfgNode case - | + exists(CfgNodes::ExprCfgNode pattern, BasicBlock cb, CfgNodes::ExprNodes::CaseExprCfgNode case | m = resolveConstantReadAccess(pattern.getExpr()) and cb.getLastNode() = pattern and cb.edgeDominates(read.getBasicBlock(), any(MatchingSuccessor match | match.getValue() = true)) and diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll index 74394150ca32..c7acbe0855f7 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll @@ -4,13 +4,11 @@ module; private import codeql.ssa.Ssa as SsaImplCommon private import codeql.ruby.AST private import codeql.ruby.CFG as Cfg -private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks -private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as ControlFlowGraphImpl private import codeql.ruby.dataflow.SSA private import codeql.ruby.ast.Variable private import Cfg::CfgNodes::ExprNodes -private class BasicBlock = BasicBlocks::Cfg::BasicBlock; +private class BasicBlock = Cfg::BasicBlock; module SsaInput implements SsaImplCommon::InputSig { private import codeql.ruby.controlflow.ControlFlowGraph as Cfg @@ -25,11 +23,11 @@ module SsaInput implements SsaImplCommon::InputSig { ( exists(Scope scope | scope = v.(SelfVariable).getDeclaringScope() | // We consider the `self` variable to have a single write at the entry to a method block... - scope = bb.(BasicBlocks::EntryBasicBlock).getScope() and + scope = bb.(Cfg::EntryBasicBlock).getScope() and i = 0 or // ...or a class or module block. - bb.getNode(i).getAstNode() = scope.(ModuleBase).getAControlFlowEntryNode() and + bb.getNode(i).isBefore(scope.(ModuleBase)) and not scope instanceof Toplevel // handled by case above ) or @@ -60,7 +58,7 @@ module SsaInput implements SsaImplCommon::InputSig { } } -import SsaImplCommon::Make as Impl +import SsaImplCommon::Make as Impl class Definition = Impl::Definition; @@ -98,12 +96,17 @@ private predicate writesCapturedVariable(Cfg::BasicBlock bb, LocalVariable v) { ) } +private predicate isAnnotatedExitBlock(BasicBlock bb) { + bb.getANode() instanceof Cfg::ControlFlow::AnnotatedExitNode +} + /** * Holds if a pseudo read of captured variable `v` should be inserted * at index `i` in exit block `bb`. */ -private predicate capturedExitRead(Cfg::AnnotatedExitBasicBlock bb, int i, LocalVariable v) { +private predicate capturedExitRead(BasicBlock bb, int i, LocalVariable v) { writesCapturedVariable(bb.getAPredecessor*(), v) and + isAnnotatedExitBlock(bb) and i = bb.length() } @@ -112,13 +115,11 @@ private predicate capturedExitRead(Cfg::AnnotatedExitBasicBlock bb, int i, Local * at index `i` in basic block `bb`. We do this to ensure that namespace * self-variables always get an SSA definition. */ -private predicate namespaceSelfExitRead(Cfg::AnnotatedExitBasicBlock bb, int i, SelfVariable v) { - exists(Namespace ns, AstNode last | +private predicate namespaceSelfExitRead(BasicBlock bb, int i, SelfVariable v) { + exists(Namespace ns, Cfg::ControlFlowNode n | v.getDeclaringScope() = ns and - last = ControlFlowGraphImpl::getAControlFlowExitNode(ns) and - if last = ns - then bb.getNode(i).getAPredecessor().getAstNode() = last - else bb.getNode(i).getAstNode() = last + n.isAfter(ns) and + if n.isBefore(ns) then bb.getNode(i - 1) = n else bb.getNode(i) = n ) } @@ -129,7 +130,8 @@ private predicate namespaceSelfExitRead(Cfg::AnnotatedExitBasicBlock bb, int i, pragma[noinline] private predicate hasCapturedRead(Variable v, Cfg::CfgScope scope) { any(LocalVariableReadAccessCfgNode read | - read.getVariable() = v and scope = read.getScope().getOuterCfgScope*() + read.getVariable() = v and + scope = read.getEnclosingCallable().(Cfg::CfgScope).getOuterCfgScope*() ).getExpr().isCapturedAccess() } @@ -188,7 +190,8 @@ private predicate variableReadActual(Cfg::BasicBlock bb, int i, LocalVariable v) pragma[noinline] private predicate hasCapturedWrite(Variable v, Cfg::CfgScope scope) { any(LocalVariableWriteAccessCfgNode write | - write.getVariable() = v and scope = write.getScope().getOuterCfgScope*() + write.getVariable() = v and + scope = write.getEnclosingCallable().(Cfg::CfgScope).getOuterCfgScope*() ).getExpr().isCapturedAccess() } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll b/ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll index eabee2ea5133..3ffdb915c6f3 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll @@ -289,7 +289,7 @@ module Sinatra { private predicate blockPostSelf(DataFlow::PostUpdateNode n, DataFlow::BlockNode b) { exists(SelfVariableAccessCfgNode self | n.getPreUpdateNode().asExpr() = self and - self.getScope() = b.asExpr().getAstNode() + self.getEnclosingCallable() = b.asExpr().getAstNode() ) } diff --git a/ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qll index a64cd1039c17..cf752c3aaeb1 100644 --- a/ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qll @@ -46,8 +46,8 @@ module ConditionalBypass { SensitiveAction action; SensitiveActionGuardConditional() { - exists(ConditionBlock cb, BasicBlock controlled | - cb.edgeDominates(controlled, _) and + exists(BasicBlock cb, BasicBlock controlled | + cb.edgeDominates(controlled, any(ConditionalSuccessor s)) and controlled.getANode() = action.asExpr() and cb.getLastNode() = this.asExpr() ) diff --git a/ruby/ql/lib/ide-contextual-queries/printCfg.ql b/ruby/ql/lib/ide-contextual-queries/printCfg.ql index 9730f330d8d6..c749f035f748 100644 --- a/ruby/ql/lib/ide-contextual-queries/printCfg.ql +++ b/ruby/ql/lib/ide-contextual-queries/printCfg.ql @@ -8,9 +8,7 @@ */ private import codeql.Locations -private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl private import codeql.ruby.controlflow.ControlFlowGraph -private import codeql.ruby.controlflow.ControlFlowGraph2 as Cfg2 external string selectedSourceFile(); @@ -24,8 +22,7 @@ external int selectedSourceColumn(); private predicate selectedSourceColumnAlias = selectedSourceColumn/0; -// module ViewCfgQueryInput implements ViewCfgQueryInputSig { -module ViewCfgQueryInput implements Cfg2::ControlFlow::ViewCfgQueryInputSig { +module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig { predicate selectedSourceFile = selectedSourceFileAlias/0; predicate selectedSourceLine = selectedSourceLineAlias/0; @@ -33,13 +30,11 @@ module ViewCfgQueryInput implements Cfg2::ControlFlow::ViewCfgQueryInputSig -import Cfg2::ControlFlow::ViewCfgQuery +import ControlFlow::ViewCfgQuery diff --git a/ruby/ql/src/experimental/performance/UseDetect.ql b/ruby/ql/src/experimental/performance/UseDetect.ql index 56ab99ae4056..61cf8ccd3f33 100644 --- a/ruby/ql/src/experimental/performance/UseDetect.ql +++ b/ruby/ql/src/experimental/performance/UseDetect.ql @@ -42,13 +42,11 @@ class EndCall extends MethodCall { } Expr getUniqueRead(Expr e) { - forex(CfgNode eNode | eNode.getAstNode() = e | - exists(Ssa::WriteDefinition def | - def.assigns(eNode) and - strictcount(def.getARead()) = 1 and - not def = any(Ssa::PhiNode phi).getAnInput() and - def.getARead().getAstNode() = result - ) + exists(Ssa::WriteDefinition def | + def.assigns(e.getControlFlowNode()) and + strictcount(def.getARead()) = 1 and + not def = any(Ssa::PhiNode phi).getAnInput() and + def.getARead().getAstNode() = result ) } diff --git a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql index c81cdd6b2f63..67e9ba4f9c77 100644 --- a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql +++ b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql @@ -36,7 +36,7 @@ class LoopingCall extends DataFlow::CallNode { } /** Holds if `c` is executed as part of the body of this loop. */ - predicate executesCall(DataFlow::CallNode c) { c.asExpr().getScope() = loopScope } + predicate executesCall(DataFlow::CallNode c) { c.asExpr().getEnclosingCallable() = loopScope } } /** Holds if `ar` influences a guard that may control the execution of a loop. */ diff --git a/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql b/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql index 849fb8ecd5a7..70d3191b8a72 100644 --- a/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql +++ b/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql @@ -19,7 +19,12 @@ import codeql.ruby.ApiGraphs pragma[nomagic] private predicate hasErbResultCall(CfgScope scope) { - scope = API::getTopLevelMember("ERB").getInstance().getAMethodCall("result").asExpr().getScope() + scope = + API::getTopLevelMember("ERB") + .getInstance() + .getAMethodCall("result") + .asExpr() + .getEnclosingCallable() } class RelevantLocalVariableWriteAccess extends LocalVariableWriteAccess { diff --git a/ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.ql b/ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.ql index c99de9bc0a86..de648d227e72 100644 --- a/ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.ql +++ b/ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.ql @@ -1,5 +1,4 @@ import codeql.ruby.controlflow.ControlFlowGraph -import codeql.ruby.controlflow.BasicBlocks query predicate dominates(BasicBlock bb1, BasicBlock bb2) { bb1.dominates(bb2) } @@ -9,14 +8,10 @@ query predicate immediateDominator(BasicBlock bb1, BasicBlock bb2) { bb1.getImmediateDominator() = bb2 } -query predicate controls(ConditionBlock bb1, BasicBlock bb2, ConditionalSuccessor t) { +query predicate controls(BasicBlock bb1, BasicBlock bb2, ConditionalSuccessor t) { bb1.edgeDominates(bb2, t) } -query predicate successor(ConditionBlock bb1, BasicBlock bb2, SuccessorType t) { +query predicate successor(BasicBlock bb1, BasicBlock bb2, ConditionalSuccessor t) { bb1.getASuccessor(t) = bb2 } - -query predicate joinBlockPredecessor(JoinBlock bb1, BasicBlock bb2, int i) { - bb1.getJoinBlockPredecessor(i) = bb2 -} diff --git a/ruby/ql/test/library-tests/controlflow/graph/Cfg.ql b/ruby/ql/test/library-tests/controlflow/graph/Cfg.ql index 39d0627613e9..54fc30b8eec3 100644 --- a/ruby/ql/test/library-tests/controlflow/graph/Cfg.ql +++ b/ruby/ql/test/library-tests/controlflow/graph/Cfg.ql @@ -1,2 +1,2 @@ import codeql.ruby.CFG -import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::TestOutput +import ControlFlow::TestOutput diff --git a/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql index 86609a73909c..e4f35886dd70 100644 --- a/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql +++ b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql @@ -3,7 +3,6 @@ import codeql.ruby.dataflow.internal.DataFlowPublic import codeql.ruby.dataflow.BarrierGuards import codeql.ruby.controlflow.CfgNodes import codeql.ruby.controlflow.ControlFlowGraph -import codeql.ruby.controlflow.BasicBlocks import codeql.ruby.DataFlow import utils.test.InlineExpectationsTest @@ -13,7 +12,7 @@ query predicate newStyleBarrierGuards(DataFlow::Node n) { } query predicate controls(CfgNode condition, BasicBlock bb, ConditionalSuccessor s) { - exists(ConditionBlock cb | + exists(BasicBlock cb | cb.edgeDominates(bb, s) and condition = cb.getLastNode() )