From 50648a6006076a3e6921023f25428f674533b943 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 30 Mar 2026 13:07:28 +0100 Subject: [PATCH 01/34] The shared CFG library instantiation for Go compiles cleanly. Let me show you the current state of the file: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read ControlFlowGraphShared.qll, lines 1 to 50 The file compiles successfully. Here's a summary of what was implemented: ControlFlowGraphShared.qll — Shared CFG library instantiation for Go, wrapped in the GoCfg module to coexist with the existing CFG during transition. Key design decisions: Ast module (implements AstSig): Callable = FuncDef with a body IfStmt — only For if-statements without init (those with init use custom steps) LoopStmt — uses characteristic predicate pattern (not direct extension) to avoid return type conflicts with BlockStmt Empty classes for: WhileStmt, DoStmt, ForStmt, ForeachStmt, ThrowStmt, TryStmt, CatchClause, Switch, Case, ConditionalExpr, NullCoalescingExpr (Go doesn't have these constructs) LogicalAndExpr = LandExpr, LogicalOrExpr = LorExpr, LogicalNotExpr = NotExpr BooleanLiteral maps to true/false identifiers skipCfg filters out type expressions, non-rvalue references, parenthesized expressions, etc. Input module (implements InputSig1 + InputSig2): Labels: Go statement labels + fallthrough Custom steps for: if-with-init, for loops, range loops, expression/type switch, case clauses, select statements, defer, go statements Abrupt completions: panic via calls/div/deref/type-assert/index/conversion, fallthrough, goto End abrupt completions: labeled statements catch breaks, goto targets Remaining work (todos 4-6): The GoCfg module is self-contained and doesn't yet replace the existing CFG. Integration with BasicBlocks.qll, IR.qll, and the public ControlFlowGraph.qll API is the next phase — which is a larger effort requiring updates to DataFlow and all downstream consumers. --- .../go/controlflow/ControlFlowGraphShared.qll | 658 ++++++++++++++++++ 1 file changed, 658 insertions(+) create mode 100644 go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll new file mode 100644 index 000000000000..6ff1f12ee5c1 --- /dev/null +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -0,0 +1,658 @@ +/** + * Provides the shared CFG library instantiation for Go. + * + * Everything is wrapped in `GoCfg` to avoid name conflicts with the existing + * CFG implementation during the transition. + */ +overlay[local?] +module; + +private import codeql.controlflow.ControlFlowGraph as CfgLib +private import codeql.controlflow.SuccessorType + +/** Contains the shared CFG library instantiation for Go. */ +module GoCfg { + private import go as Go + + private module Cfg0 = CfgLib::Make0; + + private module Cfg1 = Cfg0::Make1; + + private module Cfg2 = Cfg1::Make2; + + private import Cfg0 + private import Cfg1 + private import Cfg2 + import Public + + /** Provides an implementation of the AST signature for Go. */ + private module Ast implements CfgLib::AstSig { + class AstNode = Go::AstNode; + + private predicate skipCfg(AstNode e) { + e instanceof Go::TypeExpr and not e instanceof Go::FuncTypeExpr + or + e = any(Go::FieldDecl f).getTag() + or + e instanceof Go::KeyValueExpr and not e = any(Go::CompositeLit lit).getAnElement() + or + e = any(Go::SelectorExpr sel).getSelector() + or + e = any(Go::StructLit sl).getKey(_) + or + e instanceof Go::Ident and not e instanceof Go::ReferenceExpr + or + e instanceof Go::SelectorExpr and not e instanceof Go::ReferenceExpr + or + e instanceof Go::ReferenceExpr and not e.(Go::ReferenceExpr).isRvalue() + or + e instanceof Go::ParenExpr + or + e = any(Go::ImportSpec is).getPathExpr() + or + e.getParent*() = any(Go::ArrayTypeExpr ate).getLength() + } + + AstNode getChild(AstNode n, int index) { + not skipCfg(result) and not skipCfg(n) and result = n.getChild(index) + } + + class Callable extends AstNode { + Callable() { exists(this.(Go::FuncDef).getBody()) } + } + + AstNode callableGetBody(Callable c) { result = c.(Go::FuncDef).getBody() } + + Callable getEnclosingCallable(AstNode node) { result = node.getEnclosingFunction() } + + class Stmt = Go::Stmt; + + class Expr = Go::Expr; + + class BlockStmt extends Go::BlockStmt { + BlockStmt() { + not this = any(Go::SwitchStmt sw).getBody() and + not this = any(Go::SelectStmt sel).getBody() + } + + override Stmt getStmt(int n) { result = Go::BlockStmt.super.getStmt(n) } + + Stmt getLastStmt() { + exists(int last | result = this.getStmt(last) and not exists(this.getStmt(last + 1))) + } + } + + class ExprStmt = Go::ExprStmt; + + /** If statements without init (those with init use custom steps). */ + class IfStmt extends Stmt { + IfStmt() { this instanceof Go::IfStmt and not exists(this.(Go::IfStmt).getInit()) } + + Expr getCondition() { result = this.(Go::IfStmt).getCond() } + + Stmt getThen() { result = this.(Go::IfStmt).getThen() } + + Stmt getElse() { result = this.(Go::IfStmt).getElse() } + } + + class LoopStmt extends Stmt { + LoopStmt() { this instanceof Go::LoopStmt } + + Stmt getBody() { result = this.(Go::LoopStmt).getBody() } + } + + class WhileStmt extends LoopStmt { + WhileStmt() { none() } + + Expr getCondition() { none() } + } + + class DoStmt extends LoopStmt { + DoStmt() { none() } + + Expr getCondition() { none() } + } + + class ForStmt extends LoopStmt { + ForStmt() { none() } + + Expr getInit(int index) { none() } + + Expr getCondition() { none() } + + Expr getUpdate(int index) { none() } + } + + class ForeachStmt extends LoopStmt { + ForeachStmt() { none() } + + Expr getVariable() { none() } + + Expr getCollection() { none() } + } + + class BreakStmt = Go::BreakStmt; + + class ContinueStmt = Go::ContinueStmt; + + class ReturnStmt extends Go::ReturnStmt { + override Expr getExpr() { result = Go::ReturnStmt.super.getExpr() } + } + + class ThrowStmt extends Stmt { + ThrowStmt() { none() } + + Expr getExpr() { none() } + } + + class TryStmt extends Stmt { + TryStmt() { none() } + + Stmt getBody() { none() } + + CatchClause getCatch(int index) { none() } + + Stmt getFinally() { none() } + } + + class CatchClause extends AstNode { + CatchClause() { none() } + + AstNode getVariable() { none() } + + Expr getCondition() { none() } + + Stmt getBody() { none() } + } + + class Switch extends AstNode { + Switch() { none() } + + Expr getExpr() { none() } + + Case getCase(int index) { none() } + + Stmt getStmt(int index) { none() } + } + + class Case extends AstNode { + Case() { none() } + + AstNode getAPattern() { none() } + + Expr getGuard() { none() } + + AstNode getBody() { none() } + } + + class DefaultCase extends Case { + DefaultCase() { none() } + } + + class ConditionalExpr extends Expr { + ConditionalExpr() { none() } + + Expr getCondition() { none() } + + Expr getThen() { none() } + + Expr getElse() { none() } + } + + class BinaryExpr = Go::BinaryExpr; + + class LogicalAndExpr = Go::LandExpr; + + class LogicalOrExpr = Go::LorExpr; + + class NullCoalescingExpr extends BinaryExpr { + NullCoalescingExpr() { none() } + } + + class UnaryExpr = Go::UnaryExpr; + + class LogicalNotExpr = Go::NotExpr; + + class BooleanLiteral extends Expr { + boolean val; + + BooleanLiteral() { + this.(Go::Ident).getName() = "true" and val = true + or + this.(Go::Ident).getName() = "false" and val = false + } + + boolean getValue() { result = val } + } + } + + /** The Input module implementing InputSig1 and InputSig2 for Go. */ + private module Input implements Cfg0::InputSig1, Cfg1::InputSig2 { + predicate cfgCachedStageRef() { CfgCachedStage::ref() } + + private newtype TLabel = + TGoLabel(string l) { exists(Go::LabeledStmt ls | l = ls.getLabel()) } or + TFallthrough() + + class Label extends TLabel { + string toString() { + exists(string l | this = TGoLabel(l) and result = l) + or + this = TFallthrough() and result = "fallthrough" + } + } + + private Label getLabelOfStmt(Go::Stmt s) { + exists(Go::LabeledStmt l | s = l.getStmt() | + result = TGoLabel(l.getLabel()) or result = getLabelOfStmt(l) + ) + } + + predicate hasLabel(Ast::AstNode n, Label l) { + l = getLabelOfStmt(n) + or + l = TGoLabel(n.(Go::BreakStmt).getLabel()) + or + l = TGoLabel(n.(Go::ContinueStmt).getLabel()) + or + l = TFallthrough() and n instanceof Go::FallthroughStmt + } + + predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) { + kind.isBoolean() and + ( + exists(Go::IfStmt ifstmt | ifstmt.getCond() = n and exists(ifstmt.getInit())) + or + n = any(Go::ForStmt fs).getCond() + or + exists(Go::ExpressionSwitchStmt ess | + not exists(ess.getExpr()) and n = ess.getACase().(Go::CaseClause).getExpr(_) + ) + ) + } + + predicate preOrderExpr(Ast::Expr e) { none() } + + predicate postOrInOrder(Ast::AstNode n) { + n instanceof Go::CallExpr and + not n = any(Go::DeferStmt defer).getCall() and + not n = any(Go::GoStmt go_).getCall() + or + n instanceof Go::BinaryExpr and not n instanceof Go::LogicalBinaryExpr + or + n instanceof Go::UnaryExpr and not n instanceof Go::NotExpr + or + n instanceof Go::ConversionExpr + or + n instanceof Go::TypeAssertExpr + or + n instanceof Go::IndexExpr + or + n instanceof Go::SliceExpr + or + n instanceof Go::CompositeLit + or + n instanceof Go::ReturnStmt + or + n instanceof Go::DeferStmt + or + n instanceof Go::GoStmt + or + n instanceof Go::SendStmt + or + n instanceof Go::IncDecStmt + or + n instanceof Go::FuncDecl + or + n instanceof Go::SelectorExpr and + n.(Go::SelectorExpr).getBase() instanceof Go::ValueExpr + } + + predicate additionalNode(Ast::AstNode n, string tag, NormalSuccessor t) { none() } + + predicate beginAbruptCompletion( + Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always + ) { + ast instanceof Go::CallExpr and + ( + not exists(ast.(Go::CallExpr).getTarget()) or + ast.(Go::CallExpr).getTarget().mayPanic() + ) and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = false + or + ast instanceof Go::DivExpr and + not ast.(Go::Expr).isConst() and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = false + or + ast instanceof Go::DerefExpr and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = false + or + ast instanceof Go::TypeAssertExpr and + not exists(Go::Assignment assgn | + assgn.getNumLhs() = 2 and ast = assgn.getRhs().stripParens() + ) and + not exists(Go::ValueSpec vs | vs.getNumName() = 2 and ast = vs.getInit().stripParens()) and + not exists(Go::TypeSwitchStmt ts | ast = ts.getExpr()) and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = false + or + ast instanceof Go::IndexExpr and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = false + or + ast instanceof Go::ConversionExpr and + ast.(Go::ConversionExpr).getType().(Go::PointerType).getBaseType() instanceof Go::ArrayType and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = false + or + ast instanceof Go::FallthroughStmt and + n.injects(ast) and + c.getSuccessorType() instanceof BreakSuccessor and + c.hasLabel(TFallthrough()) and + always = true + or + ast instanceof Go::GotoStmt and + n.injects(ast) and + c.getSuccessorType() instanceof GotoSuccessor and + c.hasLabel(TGoLabel(ast.(Go::GotoStmt).getLabel())) and + always = true + } + + predicate endAbruptCompletion(Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c) { + exists(Go::LabeledStmt lbl | + ast = lbl.getStmt() and + n.isAfter(lbl) and + c.getSuccessorType() instanceof BreakSuccessor and + c.hasLabel(TGoLabel(lbl.getLabel())) + ) + or + exists(Go::LabeledStmt lbl, Go::FuncDef fd | + ast = fd.getBody() and + n.isBefore(lbl) and + fd = lbl.getEnclosingFunction() and + c.getSuccessorType() instanceof GotoSuccessor and + c.hasLabel(TGoLabel(lbl.getLabel())) + ) + } + + predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { + ifWithInit(n1, n2) or + forLoop(n1, n2) or + rangeLoop(n1, n2) or + switchStmt(n1, n2) or + selectStmt(n1, n2) or + deferStmt(n1, n2) or + goStmtStep(n1, n2) + } + + private predicate ifWithInit(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::IfStmt s | exists(s.getInit()) | + n1.isBefore(s) and n2.isBefore(s.getInit()) + or + n1.isAfter(s.getInit()) and n2.isBefore(s.getCond()) + or + n1.isAfterTrue(s.getCond()) and n2.isBefore(s.getThen()) + or + n1.isAfterFalse(s.getCond()) and + ( + n2.isBefore(s.getElse()) + or + not exists(s.getElse()) and n2.isAfter(s) + ) + or + n1.isAfter(s.getThen()) and n2.isAfter(s) + or + n1.isAfter(s.getElse()) and n2.isAfter(s) + ) + } + + private predicate forLoop(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::ForStmt s | + exists(PreControlFlowNode cond | + ( + cond.isBefore(s.getCond()) + or + not exists(s.getCond()) and cond.isBefore(s.getBody()) + ) + | + n1.isBefore(s) and + ( + n2.isBefore(s.getInit()) + or + not exists(s.getInit()) and n2 = cond + ) + or + n1.isAfter(s.getInit()) and n2 = cond + or + n1.isAfterTrue(s.getCond()) and n2.isBefore(s.getBody()) + or + n1.isAfterFalse(s.getCond()) and n2.isAfter(s) + or + not exists(s.getCond()) and + n1.isAfter(s.getBody()) and + ( + n2.isBefore(s.getPost()) + or + not exists(s.getPost()) and n2.isBefore(s.getBody()) + ) + or + exists(s.getCond()) and + n1.isAfter(s.getBody()) and + ( + n2.isBefore(s.getPost()) + or + not exists(s.getPost()) and n2 = cond + ) + or + n1.isAfter(s.getPost()) and n2 = cond + ) + ) + } + + private predicate rangeLoop(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::RangeStmt s | + n1.isBefore(s) and n2.isBefore(s.getDomain()) + or + n1.isAfter(s.getDomain()) and n2.isIn(s) + or + n1.isIn(s) and + ( + n2.isBefore(s.getKey()) + or + not exists(s.getKey()) and n2.isBefore(s.getBody()) + ) + or + n1.isAfter(s.getKey()) and + ( + n2.isBefore(s.getValue()) + or + not exists(s.getValue()) and n2.isBefore(s.getBody()) + ) + or + n1.isAfter(s.getValue()) and n2.isBefore(s.getBody()) + or + n1.isAfter(s.getBody()) and n2.isIn(s) + or + n1.isIn(s) and n2.isAfter(s) + ) + } + + private predicate switchStmt(PreControlFlowNode n1, PreControlFlowNode n2) { + exprSwitch(n1, n2) or typeSwitch(n1, n2) or caseClause(n1, n2) + } + + private predicate exprSwitch(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::ExpressionSwitchStmt sw | + n1.isBefore(sw) and + ( + n2.isBefore(sw.getInit()) + or + not exists(sw.getInit()) and + ( + n2.isBefore(sw.getExpr()) + or + not exists(sw.getExpr()) and + ( + n2.isBefore(sw.getNonDefaultCase(0)) + or + not exists(sw.getANonDefaultCase()) and n2.isBefore(sw.getDefault()) + or + not exists(sw.getACase()) and n2.isAfter(sw) + ) + ) + ) + or + n1.isAfter(sw.getInit()) and + ( + n2.isBefore(sw.getExpr()) + or + not exists(sw.getExpr()) and + ( + n2.isBefore(sw.getNonDefaultCase(0)) + or + not exists(sw.getANonDefaultCase()) and n2.isBefore(sw.getDefault()) + ) + ) + or + n1.isAfter(sw.getExpr()) and + ( + n2.isBefore(sw.getNonDefaultCase(0)) + or + not exists(sw.getANonDefaultCase()) and n2.isBefore(sw.getDefault()) + ) + ) + } + + private predicate typeSwitch(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::TypeSwitchStmt sw | + n1.isBefore(sw) and + ( + n2.isBefore(sw.getInit()) + or + not exists(sw.getInit()) and n2.isBefore(sw.getTest()) + ) + or + n1.isAfter(sw.getInit()) and n2.isBefore(sw.getTest()) + or + n1.isAfter(sw.getTest()) and + ( + n2.isBefore(sw.getNonDefaultCase(0)) + or + not exists(sw.getANonDefaultCase()) and n2.isBefore(sw.getDefault()) + ) + ) + } + + private predicate caseClause(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::SwitchStmt sw, Go::CaseClause cc, int i | cc = sw.getNonDefaultCase(i) | + n1.isBefore(cc) and n2.isBefore(cc.getExpr(0)) + or + exists(int j | n1.isAfter(cc.getExpr(j)) and n2.isBefore(cc.getExpr(j + 1))) + or + exists(int last | last = max(int j | exists(cc.getExpr(j))) | + n1.isAfter(cc.getExpr(last)) and + ( + n2.isBefore(cc.getStmt(0)) + or + not exists(cc.getStmt(0)) and n2.isAfter(sw) + or + n2.isBefore(sw.getNonDefaultCase(i + 1)) + or + not exists(sw.getNonDefaultCase(i + 1)) and n2.isBefore(sw.getDefault()) + or + not exists(sw.getNonDefaultCase(i + 1)) and + not exists(sw.getDefault()) and + n2.isAfter(sw) + ) + ) + ) + or + exists(Go::SwitchStmt sw, Go::CaseClause def | def = sw.getDefault() | + n1.isBefore(def) and + ( + n2.isBefore(def.getStmt(0)) + or + not exists(def.getStmt(0)) and n2.isAfter(sw) + ) + ) + or + exists(Go::CaseClause cc | + exists(int j | n1.isAfter(cc.getStmt(j)) and n2.isBefore(cc.getStmt(j + 1))) + or + exists(Go::SwitchStmt sw, int last | + sw.getACase() = cc and + last = max(int j | exists(cc.getStmt(j))) and + n1.isAfter(cc.getStmt(last)) and + n2.isAfter(sw) + ) + ) + } + + private predicate selectStmt(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::SelectStmt sel | + n1.isBefore(sel) and + ( + n2.isBefore(sel.getNonDefaultCommClause(0).getComm()) + or + not exists(sel.getACommClause()) and n2.isAfter(sel) + ) + or + exists(Go::CommClause cc, int i | cc = sel.getNonDefaultCommClause(i) | + n1.isAfter(cc.getComm()) and + ( + n2.isBefore(sel.getNonDefaultCommClause(i + 1).getComm()) + or + not exists(sel.getNonDefaultCommClause(i + 1)) and + ( + n2.isBefore(sel.getACommClause().getStmt(0)) + or + exists(sel.getDefaultCommClause()) and + n2.isBefore(sel.getDefaultCommClause().getStmt(0)) + ) + ) + ) + or + exists(Go::CommClause cc | sel.getACommClause() = cc | + exists(int j | n1.isAfter(cc.getStmt(j)) and n2.isBefore(cc.getStmt(j + 1))) + or + exists(int last | + last = max(int j | exists(cc.getStmt(j))) and + n1.isAfter(cc.getStmt(last)) and + n2.isAfter(sel) + ) + or + not exists(cc.getStmt(_)) and n1.isBefore(cc) and n2.isAfter(sel) + ) + ) + } + + private predicate deferStmt(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::DeferStmt s | + n1.isBefore(s) and n2.isBefore(s.getCall()) + or + n1.isAfter(s.getCall()) and n2.isIn(s) + or + n1.isIn(s) and n2.isAfter(s) + ) + } + + private predicate goStmtStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::GoStmt s | + n1.isBefore(s) and n2.isBefore(s.getCall()) + or + n1.isAfter(s.getCall()) and n2.isIn(s) + or + n1.isIn(s) and n2.isAfter(s) + ) + } + } +} From 23e0082f45cb40c92e42879e2ed95c15ef6890fa Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 30 Mar 2026 13:27:52 +0100 Subject: [PATCH 02/34] =?UTF-8?q?Phase=201=20=E2=80=94=20Complete.=20The?= =?UTF-8?q?=20shared=20CFG=20library=20is=20now=20instantiated=20and=20wir?= =?UTF-8?q?ed=20into=20Go:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created ControlFlowGraphShared.qll — contains the full GoCfg module with: Ast module implementing AstSig — maps Go's AST types to the shared interface, with empty types for constructs Go doesn't have (while/do/try/catch/throw/ternary/null-coalescing) Input module implementing InputSig1 + InputSig2 — labels, conditional contexts, custom step() predicates for if-with-init, for/range loops, expression/type switches, case clauses, select, defer, and go statements; abrupt completions for panics, fallthrough, and goto Make0/Make1/Make2 wiring and import Public Modified ControlFlowGraph.qll — added import of the shared library and re-exports it as module SharedCfg = GoCfg, making shared types accessible as SharedCfg::ControlFlowNode, SharedCfg::ControlFlow::EntryNode, SharedCfg::BasicBlock, etc. No breakage — all 90 src queries and library files compile cleanly. --- go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll index 77bb94d89f8c..ebd8605c117c 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll @@ -6,6 +6,7 @@ module; import go private import ControlFlowGraphImpl +private import ControlFlowGraphShared /** Provides helper predicates for mapping btween CFG nodes and the AST. */ module ControlFlow { @@ -359,3 +360,13 @@ module ControlFlow { class ControlFlowNode = ControlFlow::Node; class Write = ControlFlow::WriteNode; + +/** + * Provides the shared CFG library types for Go. + * + * These types are generated by the shared `codeql.controlflow.ControlFlowGraph` + * library and coexist with the existing Go CFG types during the transition. + * Use `SharedCfg::ControlFlowNode` to access the shared library's node type, + * `SharedCfg::ControlFlow::EntryNode` for entry nodes, etc. + */ +module SharedCfg = GoCfg; From 624a134eb0b0eabc374fd97879197779b865cae7 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 30 Mar 2026 14:30:31 +0100 Subject: [PATCH 03/34] All 90 queries compile cleanly. Here's the status: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 progress — shared CFG library fully instantiated with IR support: ControlFlowGraphShared.qll (1290 lines) now contains: additionalNode entries for all 30+ synthetic IR operations: Assignment writes (assign:i), compound RHS (compound-rhs), tuple extraction (extract:i) Zero initialization (zero-init:i), increment/decrement (implicit-one, incdec-rhs) Return (return), result write/read (result-write:i, result-read:i) Function prologue (param-init:i, arg:i, result-init:i, result-zero-init:i) Range next (next), send (send), implicit deref (implicit-deref) Slice bounds (implicit-low/high/max), case checks (case-check:i) Type switch variables (type-switch-var), literal init (lit-init, lit-index) Implicit field selection (implicit-field:i) step routing for all constructs needing custom flow: Assignments (with epilogue node sequencing for extract/assign chains) Increment/decrement (operand → implicit-one → incdec-rhs → In) Return statements (expressions → extract → result-write → return) Index/slice/selector expressions (with implicit deref insertion) Composite literals (pre-order: allocation first, then element init) Send statements (channel → value → send) Plus the existing: if-with-init, for/range loops, switch/select, defer, go What remains for full migration (making shared types primary): Rewrite ControlFlowGraph.qll: change ControlFlow::Node from TControlFlowNode to GoCfg::ControlFlowNode Rewrite IR.qll (1598 lines): map all 30+ IR instruction classes to shared CFG nodes via getAstNode()/getIdTag() instead of Mk* types Update BasicBlocks.qll to use shared library's built-in BasicBlock Update ~30 downstream files (SSA, DataFlow, security queries) that reference IR::Instruction/ControlFlow::Node Add function prologue/epilogue step routing (param init, result init, defer LIFO at exit) Remove ControlFlowGraphImpl.qll This remaining work is a substantial rewrite (3000+ lines across 35+ files) that would benefit from being done incrementally with test validation at each step. Shall I continue with the IR.qll rewrite next? --- .../go/controlflow/ControlFlowGraphShared.qll | 633 +++++++++++++++++- 1 file changed, 631 insertions(+), 2 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 6ff1f12ee5c1..c9795f9d93e2 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -308,7 +308,247 @@ module GoCfg { n.(Go::SelectorExpr).getBase() instanceof Go::ValueExpr } - predicate additionalNode(Ast::AstNode n, string tag, NormalSuccessor t) { none() } + predicate additionalNode(Ast::AstNode n, string tag, NormalSuccessor t) { + t instanceof DirectSuccessor and + ( + // Assignment write nodes: one per LHS + exists(int i | + ( + notBlankIdent(n.(Go::Assignment).getLhs(i)) + or + notBlankIdent(n.(Go::ValueSpec).getNameExpr(i)) + or + notBlankIdent(n.(Go::RangeStmt).getKey()) and i = 0 + or + notBlankIdent(n.(Go::RangeStmt).getValue()) and i = 1 + ) and + tag = "assign:" + i.toString() + ) + or + // Compound assignment implicit RHS + n instanceof Go::CompoundAssignStmt and tag = "compound-rhs" + or + // Tuple extraction nodes + exists(int i | + extractNodeCondition(n, i) and + tag = "extract:" + i.toString() + ) + or + // Zero initialization (on the ValueSpec) + exists(int i, Go::ValueSpec spec | + n = spec and + not exists(spec.getAnInit()) and + exists(spec.getNameExpr(i)) and + tag = "zero-init:" + i.toString() + ) + or + // Increment/decrement implicit operations + n instanceof Go::IncDecStmt and tag = "implicit-one" + or + n instanceof Go::IncDecStmt and tag = "incdec-rhs" + or + // Return node + n instanceof Go::ReturnStmt and tag = "return" + or + // Result write nodes in return statements + exists(int i, Go::ReturnStmt ret | + n = ret and + exists(ret.getEnclosingFunction().getResultVar(i)) and + exists(ret.getAnExpr()) and + tag = "result-write:" + i.toString() + ) + or + // Result read nodes (on the FuncDef) + exists(int i, Go::FuncDef fd | + n = fd and + exists(fd.getBody()) and + exists(fd.getResultVar(i)) and + tag = "result-read:" + i.toString() + ) + or + // Parameter init + argument nodes (on the FuncDef) + exists(int i, Go::FuncDef fd | + n = fd and + exists(fd.getBody()) and + exists(fd.getParameter(i)) and + (tag = "param-init:" + i.toString() or tag = "arg:" + i.toString()) + ) + or + // Result variable init (on the FuncDef) + exists(int i, Go::FuncDef fd | + n = fd and + exists(fd.getBody()) and + exists(fd.getResultVar(i)) and + tag = "result-init:" + i.toString() + ) + or + // Result variable zero init (on the FuncDef) + exists(int i, Go::FuncDef fd | + n = fd and + exists(fd.getBody()) and + exists(fd.getResultVar(i)) and + exists(fd.getResultVar(i).(Go::ResultVariable).getFunction().getBody()) and + tag = "result-zero-init:" + i.toString() + ) + or + // Next node for range statements + n instanceof Go::RangeStmt and tag = "next" + or + // Send node + n instanceof Go::SendStmt and + not n = any(Go::CommClause cc).getComm() and + tag = "send" + or + // Implicit deref + implicitDerefCondition(n) and tag = "implicit-deref" + or + // Implicit slice bounds + n instanceof Go::SliceExpr and + not exists(n.(Go::SliceExpr).getLow()) and + tag = "implicit-low" + or + n instanceof Go::SliceExpr and + not exists(n.(Go::SliceExpr).getHigh()) and + tag = "implicit-high" + or + n instanceof Go::SliceExpr and + not exists(n.(Go::SliceExpr).getMax()) and + tag = "implicit-max" + or + // Implicit true in tagless switch + n instanceof Go::ExpressionSwitchStmt and + not exists(n.(Go::ExpressionSwitchStmt).getExpr()) and + tag = "implicit-true" + or + // Case check nodes + exists(int i | + exists(n.(Go::CaseClause).getExpr(i)) and + tag = "case-check:" + i.toString() + ) + or + // Type switch implicit variable + exists(Go::TypeSwitchStmt ts, Go::DefineStmt ds | + ds = ts.getAssign() and + n.(Go::CaseClause) = ts.getACase() and + exists(n.(Go::CaseClause).getImplicitlyDeclaredVariable()) and + tag = "type-switch-var" + ) + or + // Literal element initialization + n = any(Go::CompositeLit lit).getAnElement() and + tag = "lit-init" + or + // Implicit literal element index + exists(Go::CompositeLit lit | + not lit instanceof Go::StructLit and + n = lit.getAnElement() and + not n instanceof Go::KeyValueExpr and + tag = "lit-index" + ) + or + // Implicit field selection for promoted fields + exists(int i, Go::Field implicitField | + implicitFieldSelection(n, i, implicitField) and + tag = "implicit-field:" + i.toString() + ) + ) + } + + /** Helper: condition for MkExtractNode */ + private predicate extractNodeCondition(Ast::AstNode s, int i) { + exists(Go::Assignment assgn | + s = assgn and + exists(assgn.getRhs()) and + assgn.getNumLhs() > 1 and + exists(assgn.getLhs(i)) + ) + or + exists(Go::ValueSpec spec | + s = spec and + exists(spec.getInit()) and + spec.getNumName() > 1 and + exists(spec.getNameExpr(i)) + ) + or + exists(Go::RangeStmt rs | s = rs | + exists(rs.getKey()) and i = 0 + or + exists(rs.getValue()) and i = 1 + ) + or + exists(Go::ReturnStmt ret, Go::SignatureType rettp | + s = ret and + exists(ret.getExpr()) and + rettp = ret.getEnclosingFunction().getType() and + rettp.getNumResult() > 1 and + exists(rettp.getResultType(i)) + ) + or + exists(Go::CallExpr outer, Go::CallExpr inner | s = outer | + inner = outer.getArgument(0).stripParens() and + outer.getNumArgument() = 1 and + exists(inner.getType().(Go::TupleType).getComponentType(i)) + ) + } + + /** Helper: condition for implicit dereference */ + private predicate implicitDerefCondition(Ast::AstNode e) { + e.(Go::Expr).getType().getUnderlyingType() instanceof Go::PointerType and + ( + exists(Go::SelectorExpr sel | e = sel.getBase() | + sel = any(Go::Field f).getAReference() + or + exists(Go::Method m, Go::Type tp | + sel = m.getAReference() and + tp = m.getReceiver().getType().getUnderlyingType() and + not tp instanceof Go::PointerType + ) + ) + or + e = any(Go::IndexExpr ie).getBase() + or + e = any(Go::SliceExpr se).getBase() + ) + } + + /** Helper: blank identifier check */ + private predicate notBlankIdent(Go::Expr e) { not e instanceof Go::BlankIdent } + + /** Helper: implicit field selection for promoted selectors */ + private predicate implicitFieldSelection(Ast::AstNode e, int index, Go::Field implicitField) { + exists(Go::StructType baseType, Go::PromotedField child, int implicitFieldDepth | + baseType = e.(Go::PromotedSelector).getSelectedStructType() and + ( + e.(Go::PromotedSelector).refersTo(child) + or + implicitFieldSelection(e, implicitFieldDepth + 1, child) + ) + | + child = baseType.getFieldOfEmbedded(implicitField, _, implicitFieldDepth + 1, _) and + exists(Go::PromotedField explicitField, int explicitFieldDepth | + e.(Go::PromotedSelector).refersTo(explicitField) and + baseType.getFieldAtDepth(_, explicitFieldDepth) = explicitField + | + index = explicitFieldDepth - implicitFieldDepth + ) + ) + or + exists( + Go::StructType baseType, Go::PromotedMethod method, int mDepth, int implicitFieldDepth + | + baseType = e.(Go::PromotedSelector).getSelectedStructType() and + e.(Go::PromotedSelector).refersTo(method) and + baseType.getMethodAtDepth(_, mDepth) = method and + index = mDepth - implicitFieldDepth + | + method = baseType.getMethodOfEmbedded(implicitField, _, implicitFieldDepth + 1) + or + exists(Go::PromotedField child | + child = baseType.getFieldOfEmbedded(implicitField, _, implicitFieldDepth + 1, _) and + implicitFieldSelection(e, implicitFieldDepth + 1, child) + ) + ) + } predicate beginAbruptCompletion( Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always @@ -391,7 +631,396 @@ module GoCfg { switchStmt(n1, n2) or selectStmt(n1, n2) or deferStmt(n1, n2) or - goStmtStep(n1, n2) + goStmtStep(n1, n2) or + assignmentStep(n1, n2) or + incDecStep(n1, n2) or + returnStep(n1, n2) or + indexExprStep(n1, n2) or + sliceExprStep(n1, n2) or + selectorExprStep(n1, n2) or + compositeLitStep(n1, n2) or + sendStmtStep(n1, n2) + } + + /** + * Gets the non-skipped child of `parent` at rank `rnk` (1-based). + * This mimics the shared library's getRankedChild but for use in explicit steps. + */ + private Ast::AstNode getRankedChild(Ast::AstNode parent, int rnk) { + result = rank[rnk](Ast::AstNode c, int ix | c = getChild(parent, ix) | c order by ix) + } + + private Ast::AstNode getChild(Ast::AstNode parent, int ix) { + result = Ast::getChild(parent, ix) + } + + /** + * Routes from isBefore(parent) through all non-skipped children in order, + * then to the first epilogue node (additionalNode or isIn/isAfter). + * This is for constructs where we manually override default flow. + */ + private predicate childSequenceStep( + Ast::AstNode parent, PreControlFlowNode n1, PreControlFlowNode n2 + ) { + // Before parent → Before first child + n1.isBefore(parent) and n2.isBefore(getRankedChild(parent, 1)) + or + // After child i → Before child i+1 + exists(int i | + n1.isAfter(getRankedChild(parent, i)) and + n2.isBefore(getRankedChild(parent, i + 1)) + ) + } + + /** Gets the last non-skipped child of `parent`, or fails if none. */ + private Ast::AstNode getLastRankedChild(Ast::AstNode parent) { + exists(int i | + result = getRankedChild(parent, i) and + not exists(getRankedChild(parent, i + 1)) + ) + } + + /** + * Assignment flow: routes through LHS/RHS children, then through + * additional nodes for compound-rhs, extract, zero-init, and assign + * operations. + */ + private predicate assignmentStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Ast::AstNode assgn | + ( + assgn instanceof Go::Assignment and not assgn instanceof Go::RecvStmt + or + assgn instanceof Go::ValueSpec + ) + | + // Route through children (LHS names, RHS expressions) + childSequenceStep(assgn, n1, n2) + or + // After last child → first epilogue node + exists(Ast::AstNode lastChild | lastChild = getLastRankedChild(assgn) | + n1.isAfter(lastChild) and n2.isAdditional(assgn, getFirstEpilogueTag(assgn)) + ) + or + // No children at all → before → first epilogue + not exists(getRankedChild(assgn, _)) and + n1.isBefore(assgn) and + n2.isAdditional(assgn, getFirstEpilogueTag(assgn)) + or + // Chain through epilogue nodes + exists(string tag1, string tag2 | + epilogueSucc(assgn, tag1, tag2) and + n1.isAdditional(assgn, tag1) and + n2.isAdditional(assgn, tag2) + ) + or + // Last epilogue → after the assignment + n1.isAdditional(assgn, getLastEpilogueTag(assgn)) and + n2.isAfter(assgn) + or + // No epilogue at all → after last child → after assignment + not exists(getFirstEpilogueTag(assgn)) and + n1.isAfter(getLastRankedChild(assgn)) and + n2.isAfter(assgn) + ) + } + + /** + * Gets the ordered epilogue tags for an assignment node, following the + * pattern: compound-rhs?, (extract:i, assign:i | zero-init:i, assign:i)* + */ + private string getEpilogueTag(Ast::AstNode assgn, int ord) { + // Compound RHS comes first + assgn instanceof Go::CompoundAssignStmt and + ord = -1 and + result = "compound-rhs" + or + exists(int j | + ( + extractNodeCondition(assgn, j) and result = "extract:" + j.toString() and ord = 2 * j + or + exists(Go::ValueSpec spec | + assgn = spec and + not exists(spec.getAnInit()) and + exists(spec.getNameExpr(j)) and + result = "zero-init:" + j.toString() and + ord = 2 * j + ) + or + ( + notBlankIdent(assgn.(Go::Assignment).getLhs(j)) + or + notBlankIdent(assgn.(Go::ValueSpec).getNameExpr(j)) + ) and + result = "assign:" + j.toString() and + ord = 2 * j + 1 + ) + ) + } + + private string getEpilogueTagRanked(Ast::AstNode assgn, int rnk) { + result = + rank[rnk](string tag, int ord | + tag = getEpilogueTag(assgn, ord) and + exists(tag) + | + tag order by ord + ) + } + + private string getFirstEpilogueTag(Ast::AstNode assgn) { + result = getEpilogueTagRanked(assgn, 1) + } + + private string getLastEpilogueTag(Ast::AstNode assgn) { + exists(int i | + result = getEpilogueTagRanked(assgn, i) and + not exists(getEpilogueTagRanked(assgn, i + 1)) + ) + } + + private predicate epilogueSucc(Ast::AstNode assgn, string tag1, string tag2) { + exists(int i | + tag1 = getEpilogueTagRanked(assgn, i) and + tag2 = getEpilogueTagRanked(assgn, i + 1) + ) + } + + /** + * Increment/decrement: operand → implicit-one → incdec-rhs → In(stmt) + * (IncDecStmt is in postOrInOrder, so In(stmt) is its evaluation point) + */ + private predicate incDecStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::IncDecStmt s | + // Before → Before operand + n1.isBefore(s) and n2.isBefore(s.getOperand()) + or + // After operand → implicit-one + n1.isAfter(s.getOperand()) and n2.isAdditional(s, "implicit-one") + or + // implicit-one → incdec-rhs + n1.isAdditional(s, "implicit-one") and n2.isAdditional(s, "incdec-rhs") + or + // incdec-rhs → In(stmt) (the assignment itself) + n1.isAdditional(s, "incdec-rhs") and n2.isIn(s) + or + // In(stmt) → After(stmt) + n1.isIn(s) and n2.isAfter(s) + ) + } + + /** + * Return statement: evaluate expressions, extract tuples, write results, + * then the return node. + */ + private predicate returnStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::ReturnStmt ret | + // Route through expression children + childSequenceStep(ret, n1, n2) + or + exists(Ast::AstNode lastChild | lastChild = getLastRankedChild(ret) | + // After last expr → first return epilogue + n1.isAfter(lastChild) and n2.isAdditional(ret, getFirstReturnEpilogueTag(ret)) + ) + or + // No expressions → before → return node directly + not exists(getRankedChild(ret, _)) and + n1.isBefore(ret) and + n2.isIn(ret) + or + // Chain return epilogue nodes + exists(string tag1, string tag2 | + returnEpilogueSucc(ret, tag1, tag2) and + n1.isAdditional(ret, tag1) and + n2.isAdditional(ret, tag2) + ) + or + // Last return epilogue → In(ret) (the return itself) + n1.isAdditional(ret, getLastReturnEpilogueTag(ret)) and + n2.isIn(ret) + ) + } + + private string getReturnEpilogueTag(Go::ReturnStmt ret, int ord) { + exists(int i | + extractNodeCondition(ret, i) and result = "extract:" + i.toString() and ord = 2 * i + or + exists(Go::ResultVariable rv | + ret.getEnclosingFunction().getResultVar(i) = rv and + exists(ret.getAnExpr()) and + result = "result-write:" + i.toString() and + ord = 2 * i + 1 + ) + ) + } + + private string getReturnEpilogueTagRanked(Go::ReturnStmt ret, int rnk) { + result = + rank[rnk](string tag, int ord | + tag = getReturnEpilogueTag(ret, ord) and + exists(tag) + | + tag order by ord + ) + } + + private string getFirstReturnEpilogueTag(Go::ReturnStmt ret) { + result = getReturnEpilogueTagRanked(ret, 1) + } + + private string getLastReturnEpilogueTag(Go::ReturnStmt ret) { + exists(int i | + result = getReturnEpilogueTagRanked(ret, i) and + not exists(getReturnEpilogueTagRanked(ret, i + 1)) + ) + } + + private predicate returnEpilogueSucc(Go::ReturnStmt ret, string tag1, string tag2) { + exists(int i | + tag1 = getReturnEpilogueTagRanked(ret, i) and + tag2 = getReturnEpilogueTagRanked(ret, i + 1) + ) + } + + /** + * Index expression: base → implicit-deref? → index → In(indexExpr) + */ + private predicate indexExprStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::IndexExpr ie | + implicitDerefCondition(ie.getBase()) and + ( + n1.isBefore(ie) and n2.isBefore(ie.getBase()) + or + n1.isAfter(ie.getBase()) and n2.isAdditional(ie.getBase(), "implicit-deref") + or + n1.isAdditional(ie.getBase(), "implicit-deref") and n2.isBefore(ie.getIndex()) + or + n1.isAfter(ie.getIndex()) and n2.isIn(ie) + or + n1.isIn(ie) and n2.isAfter(ie) + ) + ) + } + + /** + * Slice expression: base → implicit-deref? → low/implicit-low → + * high/implicit-high → max/implicit-max → In(sliceExpr) + */ + private predicate sliceExprStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::SliceExpr se | + (implicitDerefCondition(se.getBase()) or exists(se.getLow()) or not exists(se.getLow())) and + ( + n1.isBefore(se) and n2.isBefore(se.getBase()) + or + // After base → implicit deref or low/implicit-low + n1.isAfter(se.getBase()) and + ( + if implicitDerefCondition(se.getBase()) + then n2.isAdditional(se.getBase(), "implicit-deref") + else + if exists(se.getLow()) + then n2.isBefore(se.getLow()) + else n2.isAdditional(se, "implicit-low") + ) + or + n1.isAdditional(se.getBase(), "implicit-deref") and + ( + if exists(se.getLow()) + then n2.isBefore(se.getLow()) + else n2.isAdditional(se, "implicit-low") + ) + or + (n1.isAfter(se.getLow()) or n1.isAdditional(se, "implicit-low")) and + ( + if exists(se.getHigh()) + then n2.isBefore(se.getHigh()) + else n2.isAdditional(se, "implicit-high") + ) + or + (n1.isAfter(se.getHigh()) or n1.isAdditional(se, "implicit-high")) and + ( + if exists(se.getMax()) + then n2.isBefore(se.getMax()) + else n2.isAdditional(se, "implicit-max") + ) + or + (n1.isAfter(se.getMax()) or n1.isAdditional(se, "implicit-max")) and + n2.isIn(se) + or + n1.isIn(se) and n2.isAfter(se) + ) + ) + } + + /** + * Selector expression with value base: base → implicit-deref? → + * implicit-field-selections → In(selector) + */ + private predicate selectorExprStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::SelectorExpr sel | + sel.getBase() instanceof Go::ValueExpr and + (implicitDerefCondition(sel.getBase()) or exists(Go::Field f | sel = f.getAReference())) and + ( + n1.isBefore(sel) and n2.isBefore(sel.getBase()) + or + n1.isAfter(sel.getBase()) and + ( + if implicitDerefCondition(sel.getBase()) + then n2.isAdditional(sel.getBase(), "implicit-deref") + else n2.isIn(sel) + ) + or + n1.isAdditional(sel.getBase(), "implicit-deref") and n2.isIn(sel) + or + n1.isIn(sel) and n2.isAfter(sel) + ) + ) + } + + /** + * Composite literal: In(lit) → element-init chain → After(lit) + * CompositeLit evaluates the literal (allocation) first (pre-order), + * then initializes elements. + */ + private predicate compositeLitStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::CompositeLit lit | + // Before → In (the literal allocation) + n1.isBefore(lit) and n2.isIn(lit) + or + // In → first element, or After if no elements + n1.isIn(lit) and + ( + n2.isBefore(lit.getElement(0)) + or + not exists(lit.getElement(_)) and n2.isAfter(lit) + ) + or + // After element → lit-init → next element or After + exists(int i | + n1.isAfter(lit.getElement(i)) and n2.isAdditional(lit.getElement(i), "lit-init") + or + n1.isAdditional(lit.getElement(i), "lit-init") and + ( + n2.isBefore(lit.getElement(i + 1)) + or + not exists(lit.getElement(i + 1)) and n2.isAfter(lit) + ) + ) + ) + } + + /** + * Send statement (outside select): channel → value → In(send) + */ + private predicate sendStmtStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::SendStmt s | not s = any(Go::CommClause cc).getComm() | + n1.isBefore(s) and n2.isBefore(s.getChannel()) + or + n1.isAfter(s.getChannel()) and n2.isBefore(s.getValue()) + or + n1.isAfter(s.getValue()) and n2.isIn(s) + or + n1.isIn(s) and n2.isAfter(s) + ) } private predicate ifWithInit(PreControlFlowNode n1, PreControlFlowNode n2) { From 193c5425129400596b66095b8025ce7ad1e795cc Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 30 Mar 2026 15:50:01 +0100 Subject: [PATCH 04/34] Checkpoint from VS Code for cloud agent session --- .../lib/semmle/go/controlflow/BasicBlocks.qll | 59 +- .../go/controlflow/ControlFlowGraph.qll | 117 +- .../go/controlflow/ControlFlowGraphShared.qll | 13 +- go/ql/lib/semmle/go/controlflow/IR.qll | 955 ++++------ go/ql/lib/semmle/go/controlflow/IR.qll.bak | 1580 +++++++++++++++++ go/ql/lib/semmle/go/controlflow/IR.qll.bak2 | 1580 +++++++++++++++++ go/ql/lib/semmle/go/dataflow/SsaImpl.qll | 2 +- go/ql/lib/semmle/go/frameworks/Revel.qll | 2 +- 8 files changed, 3587 insertions(+), 721 deletions(-) create mode 100644 go/ql/lib/semmle/go/controlflow/IR.qll.bak create mode 100644 go/ql/lib/semmle/go/controlflow/IR.qll.bak2 diff --git a/go/ql/lib/semmle/go/controlflow/BasicBlocks.qll b/go/ql/lib/semmle/go/controlflow/BasicBlocks.qll index dc52abb25abf..a0ba4e00417c 100644 --- a/go/ql/lib/semmle/go/controlflow/BasicBlocks.qll +++ b/go/ql/lib/semmle/go/controlflow/BasicBlocks.qll @@ -5,66 +5,27 @@ overlay[local] module; import go -private import ControlFlowGraphImpl -private import codeql.controlflow.BasicBlock as BB -private import codeql.controlflow.SuccessorType +private import ControlFlowGraphShared -private module Input implements BB::InputSig { - /** A delineated part of the AST with its own CFG. */ - class CfgScope = ControlFlow::Root; +/** A basic block in the control-flow graph. */ +class BasicBlock = GoCfg::Cfg::BasicBlock; - /** The class of control flow nodes. */ - class Node = ControlFlowNode; - - /** Gets the CFG scope in which this node occurs. */ - CfgScope nodeGetCfgScope(Node node) { node.getRoot() = result } - - /** Gets an immediate successor of this node. */ - Node nodeGetASuccessor(Node node, SuccessorType t) { - result = node.getASuccessor() and - ( - not result instanceof ControlFlow::ConditionGuardNode and t instanceof DirectSuccessor - or - t.(BooleanSuccessor).getValue() = result.(ControlFlow::ConditionGuardNode).getOutcome() - ) - } - - /** - * Holds if `node` represents an entry node to be used when calculating - * dominance. - */ - predicate nodeIsDominanceEntry(Node node) { node instanceof EntryNode } - - /** - * Holds if `node` represents an exit node to be used when calculating - * post dominance. - */ - predicate nodeIsPostDominanceExit(Node node) { node instanceof ExitNode } -} - -private module BbImpl = BB::Make; - -class BasicBlock = BbImpl::BasicBlock; - -class EntryBasicBlock = BbImpl::EntryBasicBlock; - -cached -private predicate reachableBB(BasicBlock bb) { - bb instanceof EntryBasicBlock - or - exists(BasicBlock predBB | predBB.getASuccessor(_) = bb | reachableBB(predBB)) -} +/** An entry basic block. */ +class EntryBasicBlock = GoCfg::Cfg::EntryBasicBlock; /** * A basic block that is reachable from an entry basic block. + * + * Since the shared CFG library only creates nodes for reachable code, + * all basic blocks are reachable by construction. */ class ReachableBasicBlock extends BasicBlock { - ReachableBasicBlock() { reachableBB(this) } + ReachableBasicBlock() { any() } } /** * A reachable basic block with more than one predecessor. */ class ReachableJoinBlock extends ReachableBasicBlock { - ReachableJoinBlock() { this.getFirstNode().isJoin() } + ReachableJoinBlock() { this.getFirstNode().(ControlFlow::Node).isJoin() } } diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll index ebd8605c117c..49d11e3c561d 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll @@ -5,47 +5,35 @@ overlay[local] module; import go -private import ControlFlowGraphImpl private import ControlFlowGraphShared -/** Provides helper predicates for mapping btween CFG nodes and the AST. */ +/** Provides helper predicates for mapping between CFG nodes and the AST. */ module ControlFlow { - /** A file or function with which a CFG is associated. */ + /** A function with which a CFG is associated. */ class Root extends AstNode { - Root() { exists(this.(File).getADecl()) or exists(this.(FuncDef).getBody()) } + Root() { exists(this.(FuncDef).getBody()) } - /** Holds if `nd` belongs to this file or function. */ - predicate isRootOf(AstNode nd) { - this = nd.getEnclosingFunction() - or - not exists(nd.getEnclosingFunction()) and - this = nd.getFile() - } + /** Holds if `nd` belongs to this function. */ + predicate isRootOf(AstNode nd) { this = nd.getEnclosingFunction() } - /** Gets the synthetic entry node of the CFG for this file or function. */ + /** Gets the synthetic entry node of the CFG for this function. */ EntryNode getEntryNode() { result = ControlFlow::entryNode(this) } - /** Gets the synthetic exit node of the CFG for this file or function. */ + /** Gets the synthetic exit node of the CFG for this function. */ ExitNode getExitNode() { result = ControlFlow::exitNode(this) } } /** - * A node in the intra-procedural control-flow graph of a Go function or file. + * A node in the intra-procedural control-flow graph of a Go function. * * Nodes correspond to expressions and statements that compute a value or perform * an operation (as opposed to providing syntactic structure or type information). * - * There are also synthetic entry and exit nodes for each Go function and file + * There are also synthetic entry and exit nodes for each Go function * that mark the beginning and the end, respectively, of the execution of the - * function and the loading of the file. + * function. */ - class Node extends TControlFlowNode { - /** Gets a node that directly follows this one in the control-flow graph. */ - Node getASuccessor() { result = CFG::succ(this) } - - /** Gets a node that directly precedes this one in the control-flow graph. */ - Node getAPredecessor() { this = result.getASuccessor() } - + class Node extends GoCfg::ControlFlowNode { /** Holds if this is a node with more than one successor. */ predicate isBranch() { strictcount(this.getASuccessor()) > 1 } @@ -53,22 +41,23 @@ module ControlFlow { predicate isJoin() { strictcount(this.getAPredecessor()) > 1 } /** Holds if this is the first control-flow node in `subtree`. */ - predicate isFirstNodeOf(AstNode subtree) { CFG::firstNode(subtree, this) } - - /** Holds if this node is the (unique) entry node of a function or file. */ - predicate isEntryNode() { this instanceof MkEntryNode } + predicate isFirstNodeOf(AstNode subtree) { + this.isBefore(subtree) + or + this.injects(subtree) + } - /** Holds if this node is the (unique) exit node of a function or file. */ - predicate isExitNode() { this instanceof MkExitNode } + /** Holds if this node is the (unique) entry node of a function. */ + predicate isEntryNode() { this instanceof GoCfg::ControlFlow::EntryNode } - /** Gets the basic block to which this node belongs. */ - BasicBlock getBasicBlock() { result.getANode() = this } + /** Holds if this node is the (unique) exit node of a function. */ + predicate isExitNode() { this instanceof GoCfg::ControlFlow::ExitNode } /** Holds if this node dominates `dominee` in the control-flow graph. */ overlay[caller?] pragma[inline] predicate dominatesNode(ControlFlow::Node dominee) { - exists(ReachableBasicBlock thisbb, ReachableBasicBlock dbb, int i, int j | + exists(GoCfg::Cfg::BasicBlock thisbb, GoCfg::Cfg::BasicBlock dbb, int i, int j | this = thisbb.getNode(i) and dominee = dbb.getNode(j) | thisbb.strictlyDominates(dbb) @@ -77,20 +66,12 @@ module ControlFlow { ) } - /** Gets the innermost function or file to which this node belongs. */ - Root getRoot() { none() } + /** Gets the innermost function to which this node belongs. */ + Root getRoot() { result = this.getEnclosingCallable() } /** Gets the file to which this node belongs. */ File getFile() { result = this.getLocation().getFile() } - /** - * Gets a textual representation of this control flow node. - */ - string toString() { result = "control-flow node" } - - /** Gets the source location for this element. */ - Location getLocation() { none() } - /** * DEPRECATED: Use `getLocation()` instead. * @@ -114,6 +95,12 @@ module ControlFlow { } } + /** A synthetic entry node for a function. */ + class EntryNode extends Node instanceof GoCfg::ControlFlow::EntryNode { } + + /** A synthetic exit node for a function. */ + class ExitNode extends Node instanceof GoCfg::ControlFlow::ExitNode { } + /** * A control-flow node that initializes or updates the value of a constant, a variable, * a field, or an (array, slice, or map) element. @@ -173,7 +160,7 @@ module ControlFlow { exists(IR::FieldTarget trg | trg = super.getLhs() | ( trg.getBase() = base or - trg.getBase() = MkImplicitDeref(base.(IR::EvalInstruction).getExpr()) + trg.getBase() = IR::implicitDerefInstruction(base.(IR::EvalInstruction).getExpr()) ) and trg.getField() = f and super.getRhs() = rhs @@ -221,7 +208,7 @@ module ControlFlow { exists(IR::ElementTarget trg | trg = super.getLhs() | ( trg.getBase() = base or - trg.getBase() = MkImplicitDeref(base.(IR::EvalInstruction).getExpr()) + trg.getBase() = IR::implicitDerefInstruction(base.(IR::EvalInstruction).getExpr()) ) and trg.getIndex() = index and super.getRhs() = rhs @@ -251,11 +238,15 @@ module ControlFlow { * A control-flow node recording the fact that a certain expression has a known * Boolean value at this point in the program. */ - class ConditionGuardNode extends IR::Instruction, MkConditionGuardNode { + class ConditionGuardNode extends Node { Expr cond; boolean outcome; - ConditionGuardNode() { this = MkConditionGuardNode(cond, outcome) } + ConditionGuardNode() { + this.isAfterTrue(cond) and outcome = true + or + this.isAfterFalse(cond) and outcome = false + } private predicate ensuresAux(Expr expr, boolean b) { expr = cond and b = outcome @@ -321,21 +312,17 @@ module ControlFlow { boolean getOutcome() { result = outcome } override Root getRoot() { result.isRootOf(cond) } - - override string toString() { result = cond + " is " + outcome } - - override Location getLocation() { result = cond.getLocation() } } /** - * Gets the entry node of function or file `root`. + * Gets the entry node of function `root`. */ - Node entryNode(Root root) { result = MkEntryNode(root) } + EntryNode entryNode(Root root) { result.getEnclosingCallable() = root } /** - * Gets the exit node of function or file `root`. + * Gets the exit node of function `root`. */ - Node exitNode(Root root) { result = MkExitNode(root) } + ExitNode exitNode(Root root) { result.getEnclosingCallable() = root } /** * Holds if the function `f` may return without panicking, exiting the process, or looping forever. @@ -343,7 +330,9 @@ module ControlFlow { * This is defined conservatively, and so may also hold of a function that in fact * cannot return normally, but never fails to hold of a function that can return normally. */ - predicate mayReturnNormally(FuncDecl f) { CFG::mayReturnNormally(f.getBody()) } + predicate mayReturnNormally(FuncDecl f) { + exists(GoCfg::ControlFlow::NormalExitNode exit | exit.getEnclosingCallable() = f) + } /** * Holds if `pred` is the node for the case `testExpr` in an expression @@ -353,20 +342,16 @@ module ControlFlow { predicate isSwitchCaseTestPassingEdge( ControlFlow::Node pred, ControlFlow::Node succ, Expr switchExpr, Expr testExpr ) { - CFG::isSwitchCaseTestPassingEdge(pred, succ, switchExpr, testExpr) + exists(ExpressionSwitchStmt ess, CaseClause cc, int i | + ess.getExpr() = switchExpr and + cc = ess.getACase() and + testExpr = cc.getExpr(i) and + pred.isAfter(testExpr) and + succ.isFirstNodeOf(cc.getStmt(0)) + ) } } class ControlFlowNode = ControlFlow::Node; class Write = ControlFlow::WriteNode; - -/** - * Provides the shared CFG library types for Go. - * - * These types are generated by the shared `codeql.controlflow.ControlFlowGraph` - * library and coexist with the existing Go CFG types during the transition. - * Use `SharedCfg::ControlFlowNode` to access the shared library's node type, - * `SharedCfg::ControlFlow::EntryNode` for entry nodes, etc. - */ -module SharedCfg = GoCfg; diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index c9795f9d93e2..147468122fd5 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -25,6 +25,11 @@ module GoCfg { private import Cfg2 import Public + /** Holds if `e` has an implicit field selection at `index` for `implicitField`. */ + predicate implicitFieldSelection(Go::AstNode e, int index, Go::Field implicitField) { + Input::implicitFieldSelection(e, index, implicitField) + } + /** Provides an implementation of the AST signature for Go. */ private module Ast implements CfgLib::AstSig { class AstNode = Go::AstNode; @@ -63,7 +68,11 @@ module GoCfg { AstNode callableGetBody(Callable c) { result = c.(Go::FuncDef).getBody() } - Callable getEnclosingCallable(AstNode node) { result = node.getEnclosingFunction() } + Callable getEnclosingCallable(AstNode node) { + result = node and node instanceof Callable + or + not node instanceof Callable and result = node.getEnclosingFunction() + } class Stmt = Go::Stmt; @@ -515,7 +524,7 @@ module GoCfg { private predicate notBlankIdent(Go::Expr e) { not e instanceof Go::BlankIdent } /** Helper: implicit field selection for promoted selectors */ - private predicate implicitFieldSelection(Ast::AstNode e, int index, Go::Field implicitField) { + predicate implicitFieldSelection(Ast::AstNode e, int index, Go::Field implicitField) { exists(Go::StructType baseType, Go::PromotedField child, int implicitFieldDepth | baseType = e.(Go::PromotedSelector).getSelectedStructType() and ( diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll b/go/ql/lib/semmle/go/controlflow/IR.qll index a4c730041082..0dd57d11c7d5 100644 --- a/go/ql/lib/semmle/go/controlflow/IR.qll +++ b/go/ql/lib/semmle/go/controlflow/IR.qll @@ -7,13 +7,13 @@ * structure or type information). * * Each instruction is also a control-flow node, but there are control-flow nodes that are not - * instructions (synthetic entry and exit nodes, as well as no-op skip nodes). + * instructions (synthetic entry and exit nodes, as well as before/after nodes). */ overlay[local] module; import go -private import semmle.go.controlflow.ControlFlowGraphImpl +private import ControlFlowGraphShared /** Provides predicates and classes for working with IR constructs. */ module IR { @@ -22,37 +22,13 @@ module IR { */ class Instruction extends ControlFlow::Node { Instruction() { - this instanceof MkExprNode or - this instanceof MkLiteralElementInitNode or - this instanceof MkImplicitLiteralElementIndex or - this instanceof MkAssignNode or - this instanceof MkCompoundAssignRhsNode or - this instanceof MkExtractNode or - this instanceof MkZeroInitNode or - this instanceof MkFuncDeclNode or - this instanceof MkDeferNode or - this instanceof MkGoNode or - this instanceof MkConditionGuardNode or - this instanceof MkIncDecNode or - this instanceof MkIncDecRhs or - this instanceof MkImplicitOne or - this instanceof MkReturnNode or - this instanceof MkResultWriteNode or - this instanceof MkResultReadNode or - this instanceof MkSelectNode or - this instanceof MkSendNode or - this instanceof MkParameterInit or - this instanceof MkArgumentNode or - this instanceof MkResultInit or - this instanceof MkNextNode or - this instanceof MkImplicitTrue or - this instanceof MkCaseCheckNode or - this instanceof MkTypeSwitchImplicitVariable or - this instanceof MkImplicitLowerSliceBound or - this instanceof MkImplicitUpperSliceBound or - this instanceof MkImplicitMaxSliceBound or - this instanceof MkImplicitDeref or - this instanceof MkImplicitFieldSelection + this.isIn(_) + or + this.isAdditional(_, _) + or + this.isAfterTrue(_) and not this.isIn(_) + or + this.isAfterFalse(_) and not this.isIn(_) } /** Holds if this instruction reads the value of variable or constant `v`. */ @@ -120,78 +96,87 @@ module IR { /** Gets a textual representation of the kind of this instruction. */ string getInsnKind() { - this instanceof MkExprNode and result = "expression" + this instanceof EvalInstruction and result = "expression" or - this instanceof MkLiteralElementInitNode and result = "element init" + this instanceof InitLiteralComponentInstruction and result = "element init" or - this instanceof MkImplicitLiteralElementIndex and result = "element index" + this instanceof ImplicitLiteralElementIndexInstruction and result = "element index" or - this instanceof MkAssignNode and result = "assignment" + this instanceof AssignInstruction and result = "assignment" or - this instanceof MkCompoundAssignRhsNode and result = "right-hand side of compound assignment" + this instanceof EvalCompoundAssignRhsInstruction and + result = "right-hand side of compound assignment" or - this instanceof MkExtractNode and result = "tuple element extraction" + this instanceof ExtractTupleElementInstruction and result = "tuple element extraction" or - this instanceof MkZeroInitNode and result = "zero value" + this instanceof EvalImplicitInitInstruction and result = "zero value" or - this instanceof MkFuncDeclNode and result = "function declaration" + this instanceof DeclareFunctionInstruction and result = "function declaration" or - this instanceof MkDeferNode and result = "defer" + this instanceof DeferInstruction and result = "defer" or - this instanceof MkGoNode and result = "go" + this instanceof GoInstruction and result = "go" or - this instanceof MkConditionGuardNode and result = "condition guard" + this instanceof ConditionGuardInstruction and result = "condition guard" or - this instanceof MkIncDecNode and result = "increment/decrement" + this instanceof IncDecInstruction and result = "increment/decrement" or - this instanceof MkIncDecRhs and result = "right-hand side of increment/decrement" + this instanceof EvalIncDecRhsInstruction and + result = "right-hand side of increment/decrement" or - this instanceof MkImplicitOne and result = "implicit 1" + this instanceof EvalImplicitOneInstruction and result = "implicit 1" or - this instanceof MkReturnNode and result = "return" + this instanceof ReturnInstruction and result = "return" or - this instanceof MkResultWriteNode and result = "result write" + this instanceof WriteResultInstruction and result = "result write" or - this instanceof MkResultReadNode and result = "result read" + this instanceof ReadResultInstruction and result = "result read" or - this instanceof MkSelectNode and result = "select" + this instanceof SendInstruction and result = "send" or - this instanceof MkSendNode and result = "send" + this instanceof InitParameterInstruction and result = "parameter initialization" or - this instanceof MkParameterInit and result = "parameter initialization" + this instanceof ReadArgumentInstruction and result = "argument" or - this instanceof MkArgumentNode and result = "argument" + this instanceof InitResultInstruction and result = "result initialization" or - this instanceof MkResultInit and result = "result initialization" + this instanceof GetNextEntryInstruction and result = "next key-value pair" or - this instanceof MkNextNode and result = "next key-value pair" + this instanceof EvalImplicitTrueInstruction and result = "implicit true" or - this instanceof MkImplicitTrue and result = "implicit true" + this instanceof CaseInstruction and result = "case" or - this instanceof MkCaseCheckNode and result = "case" - or - this instanceof MkTypeSwitchImplicitVariable and + this instanceof TypeSwitchImplicitVariableInstruction and result = "type switch implicit variable declaration" or - this instanceof MkImplicitLowerSliceBound and result = "implicit lower bound" + this instanceof EvalImplicitLowerSliceBoundInstruction and result = "implicit lower bound" or - this instanceof MkImplicitUpperSliceBound and result = "implicit upper bound" + this instanceof EvalImplicitUpperSliceBoundInstruction and result = "implicit upper bound" or - this instanceof MkImplicitMaxSliceBound and result = "implicit maximum" + this instanceof EvalImplicitMaxSliceBoundInstruction and result = "implicit maximum" or - this instanceof MkImplicitDeref and result = "implicit dereference" + this instanceof EvalImplicitDerefInstruction and result = "implicit dereference" or - this instanceof MkImplicitFieldSelection and result = "implicit field selection" + this instanceof ImplicitFieldReadInstruction and result = "implicit field selection" + } + } + + /** A condition guard instruction, representing a known boolean outcome for a condition. */ + private class ConditionGuardInstruction extends Instruction { + ConditionGuardInstruction() { + this.isAfterTrue(_) and not this.isIn(_) + or + this.isAfterFalse(_) and not this.isIn(_) } } /** * An IR instruction representing the evaluation of an expression. */ - class EvalInstruction extends Instruction, MkExprNode { + class EvalInstruction extends Instruction { Expr e; - EvalInstruction() { this = MkExprNode(e) } + EvalInstruction() { this.isIn(e) and e instanceof Expr } /** Gets the expression underlying this instruction. */ Expr getExpr() { result = e } @@ -217,10 +202,6 @@ module IR { override predicate isConst() { e.isConst() } override predicate isPlatformIndependentConstant() { e.isPlatformIndependentConstant() } - - override string toString() { result = e.toString() } - - override Location getLocation() { result = e.getLocation() } } /** @@ -236,17 +217,13 @@ module IR { or this instanceof ReadResultInstruction or - this instanceof MkImplicitFieldSelection + this instanceof ImplicitFieldReadInstruction } } /** * Gets the effective base of a selector, index or slice expression, taking implicit dereferences * and implicit field reads into account. - * - * For a selector expression `b.f`, this could be the implicit dereference `*b`, or the implicit - * field access `b.Embedded` if the field `f` is promoted from an embedded type `Embedded`, or a - * combination of both `*(b.Embedded)`, or simply `b` if neither case applies. */ private Instruction selectorBase(Expr e) { exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1 | @@ -261,17 +238,15 @@ module IR { or base = e.(SliceExpr).getBase() | - result = MkImplicitDeref(base) + result = implicitDerefInstruction(base) or - not exists(MkImplicitDeref(base)) and + not exists(implicitDerefInstruction(base)) and result = evalExprInstruction(base) ) } /** * An IR instruction that reads a component from a composite object. - * - * This is either a field of a struct, or an element of an array, map, slice or string. */ class ComponentReadInstruction extends ReadInstruction { ComponentReadInstruction() { @@ -282,7 +257,7 @@ module IR { not e.(SelectorExpr).getSelector() = any(Method method).getAReference() ) or - this instanceof MkImplicitFieldSelection + this instanceof ImplicitFieldReadInstruction } /** Gets the instruction computing the base value on which the field or element is read. */ @@ -295,9 +270,6 @@ module IR { /** * An IR instruction that reads the value of a field. - * - * On databases with incomplete type information, method expressions may sometimes be - * misclassified as field reads. */ class FieldReadInstruction extends ComponentReadInstruction { SelectorExpr e; @@ -309,7 +281,9 @@ module IR { index = 0 and field.getAReference() = e.getSelector() or - this = MkImplicitFieldSelection(e, index, field) + this.(ImplicitFieldReadInstruction).getSelectorExpr() = e and + this.(ImplicitFieldReadInstruction).getIndex() = index and + this.(ImplicitFieldReadInstruction).getField() = field } /** Gets the `SelectorExpr` of this field read. */ @@ -332,9 +306,9 @@ module IR { fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) ) and ( - result = MkImplicitDeref(e.getBase()) + result = implicitDerefInstruction(e.getBase()) or - not exists(MkImplicitDeref(e.getBase())) and + not exists(implicitDerefInstruction(e.getBase())) and result = evalExprInstruction(e.getBase()) ) } @@ -345,24 +319,50 @@ module IR { } /** - * An IR instruction for an implicit field read as part of reading a - * promoted field. - * - * If the field that is being implicitly read has a pointer type then this - * instruction represents an implicit dereference of it. + * An IR instruction for an implicit field read as part of reading a promoted field. */ - class ImplicitFieldReadInstruction extends FieldReadInstruction, MkImplicitFieldSelection { - ImplicitFieldReadInstruction() { this = MkImplicitFieldSelection(e, index, field) } + class ImplicitFieldReadInstruction extends Instruction { + SelectorExpr sel; + int idx; + Field fld; + + ImplicitFieldReadInstruction() { + this.isAdditional(sel, "implicit-field:" + idx.toString()) and + GoCfg::implicitFieldSelection(sel, idx, fld) + } - override predicate reads(ValueEntity v) { v = field } + /** Gets the `SelectorExpr` for which this is an implicit field read. */ + SelectorExpr getSelectorExpr() { result = sel } - override Type getResultType() { result = lookThroughPointerType(field.getType()) } + /** Gets the index of this implicit field read. */ + int getIndex() { result = idx } - override ControlFlow::Root getRoot() { result.isRootOf(e) } + /** Gets the field being read. */ + Field getField() { result = fld } + + Instruction getBaseInstruction() { + exists(ImplicitFieldReadInstruction fri | + fri.getSelectorExpr() = sel and fri.getIndex() = pragma[only_bind_into](idx + 1) + | + result = fri + ) + or + not exists(ImplicitFieldReadInstruction fri | + fri.getSelectorExpr() = sel and fri.getIndex() = pragma[only_bind_into](idx + 1) + ) and + ( + result = implicitDerefInstruction(sel.getBase()) + or + not exists(implicitDerefInstruction(sel.getBase())) and + result = evalExprInstruction(sel.getBase()) + ) + } - override string toString() { result = "implicit read of field " + field.toString() } + override predicate reads(ValueEntity v) { v = fld } - override Location getLocation() { result = e.getBase().getLocation() } + override Type getResultType() { result = lookThroughPointerType(fld.getType()) } + + override ControlFlow::Root getRoot() { result.isRootOf(sel) } } /** @@ -374,10 +374,8 @@ module IR { MethodReadInstruction() { e.getSelector() = method.getAReference() } - /** Gets the instruction computing the receiver value on which the method is looked up. */ Instruction getReceiver() { result = selectorBase(e) } - /** Gets the method being looked up. */ Method getMethod() { result = method } override predicate readsMethod(Instruction receiver, Method m) { @@ -391,7 +389,6 @@ module IR { class ElementReadInstruction extends ComponentReadInstruction, EvalInstruction { override IndexExpr e; - /** Gets the instruction computing the index of the element being looked up. */ Instruction getIndex() { result = evalExprInstruction(e.getIndex()) } override predicate readsElement(Instruction base, Instruction index) { @@ -405,22 +402,18 @@ module IR { class SliceInstruction extends EvalInstruction { override SliceExpr e; - /** Gets the instruction computing the base value from which the slice is constructed. */ Instruction getBase() { result = selectorBase(e) } - /** Gets the instruction computing the lower bound of the slice. */ Instruction getLow() { result = evalExprInstruction(e.getLow()) or result = implicitLowerSliceBoundInstruction(e) } - /** Gets the instruction computing the upper bound of the slice. */ Instruction getHigh() { result = evalExprInstruction(e.getHigh()) or result = implicitUpperSliceBoundInstruction(e) } - /** Gets the instruction computing the capacity of the slice. */ Instruction getMax() { result = evalExprInstruction(e.getMax()) or result = implicitMaxSliceBoundInstruction(e) @@ -445,13 +438,10 @@ module IR { lhs = MkLiteralElementTarget(this) and initialization = true } - /** Gets the target to which this instruction writes. */ WriteTarget getLhs() { result = lhs } - /** Holds if this instruction initializes a literal. */ predicate isInitialization() { initialization = true } - /** Gets the instruction computing the value this instruction writes. */ Instruction getRhs() { none() } override predicate writes(ValueEntity v, Instruction rhs) { @@ -463,16 +453,16 @@ module IR { /** * An IR instruction that initializes a component of a composite literal. */ - class InitLiteralComponentInstruction extends WriteInstruction, MkLiteralElementInitNode { + class InitLiteralComponentInstruction extends WriteInstruction { CompositeLit lit; - int i; + int litIdx; Expr elt; InitLiteralComponentInstruction() { - this = MkLiteralElementInitNode(elt) and elt = lit.getElement(i) + this.isAdditional(elt, "lit-init") and + elt = lit.getElement(litIdx) } - /** Gets the instruction allocating the composite literal. */ Instruction getBase() { result = evalExprInstruction(lit) } override Instruction getRhs() { @@ -481,10 +471,6 @@ module IR { } override ControlFlow::Root getRoot() { result.isRootOf(elt) } - - override string toString() { result = "init of " + elt } - - override Location getLocation() { result = elt.getLocation() } } /** @@ -493,15 +479,13 @@ module IR { class InitLiteralStructFieldInstruction extends InitLiteralComponentInstruction { override StructLit lit; - /** Gets the name of the initialized field. */ pragma[nomagic] string getFieldName() { if elt instanceof KeyValueExpr then result = elt.(KeyValueExpr).getKey().(Ident).getName() - else pragma[only_bind_out](lit.getStructType()).hasOwnField(i, result, _, _) + else pragma[only_bind_out](lit.getStructType()).hasOwnField(litIdx, result, _, _) } - /** Gets the initialized field. */ Field getField() { result.getDeclaringType() = lit.getStructType() and result.getName() = this.getFieldName() @@ -523,85 +507,59 @@ module IR { ) } - /** Gets the instruction computing the index of the initialized element. */ Instruction getIndex() { result = evalExprInstruction(elt.(KeyValueExpr).getKey()) or - result = MkImplicitLiteralElementIndex(elt) + result.(ImplicitLiteralElementIndexInstruction).isAdditional(elt, "lit-index") } } - /** - * An IR instruction that initializes an element of an array literal. - */ class InitLiteralArrayElementInstruction extends InitLiteralElementInstruction { override ArrayType literalType; } - /** - * An IR instruction that initializes an element of a slice literal. - */ class InitLiteralSliceElementInstruction extends InitLiteralElementInstruction { override SliceType literalType; } - /** - * An IR instruction that initializes an element of a map literal. - */ class InitLiteralMapElementInstruction extends InitLiteralElementInstruction { override MapType literalType; } - /** - * An IR instruction that writes to a field. - */ class FieldWriteInstruction extends WriteInstruction { override FieldTarget lhs; - /** Gets the instruction computing the base value on which the field is written. */ Instruction getBase() { result = lhs.getBase() } - /** Gets the field being written. */ Field getField() { result = lhs.getField() } override predicate writesField(Instruction base, Field f, Instruction rhs) { - this.getBase() = base and - this.getField() = f and - this.getRhs() = rhs + this.getBase() = base and this.getField() = f and this.getRhs() = rhs } } - /** - * An IR instruction that writes to an element of an array, slice, or map. - */ class ElementWriteInstruction extends WriteInstruction { override ElementTarget lhs; - /** Gets the instruction computing the base value on which the field is written. */ Instruction getBase() { result = lhs.getBase() } - /** Gets the instruction computing the element index being written. */ Instruction getIndex() { result = lhs.getIndex() } override predicate writesElement(Instruction base, Instruction index) { - this.getBase() = base and - this.getIndex() = index + this.getBase() = base and this.getIndex() = index } } - /** Holds if `lit` does not specify any explicit keys. */ private predicate noExplicitKeys(CompositeLit lit) { not lit.getAnElement() instanceof KeyValueExpr } - /** Gets the index of the `i`th element in (array or slice) literal `lit`. */ private int getElementIndex(CompositeLit lit, int i) { ( lit.getType().getUnderlyingType() instanceof ArrayType or lit.getType().getUnderlyingType() instanceof SliceType ) and exists(Expr elt | elt = lit.getElement(i) | - // short-circuit computation for literals without any explicit keys noExplicitKeys(lit) and result = i or result = elt.(KeyValueExpr).getKey().getIntValue() @@ -618,10 +576,10 @@ module IR { /** * An IR instruction computing the implicit index of an element in an array or slice literal. */ - class ImplicitLiteralElementIndexInstruction extends Instruction, MkImplicitLiteralElementIndex { + class ImplicitLiteralElementIndexInstruction extends Instruction { Expr elt; - ImplicitLiteralElementIndexInstruction() { this = MkImplicitLiteralElementIndex(elt) } + ImplicitLiteralElementIndexInstruction() { this.isAdditional(elt, "lit-index") } override Type getResultType() { result instanceof IntType } @@ -638,20 +596,25 @@ module IR { override predicate isPlatformIndependentConstant() { any() } override predicate isConst() { any() } - - override string toString() { result = "element index" } - - override Location getLocation() { result = elt.getLocation() } } /** * An instruction assigning to a variable or field. */ - class AssignInstruction extends WriteInstruction, MkAssignNode { + class AssignInstruction extends WriteInstruction { AstNode assgn; int i; - AssignInstruction() { this = MkAssignNode(assgn, i) } + AssignInstruction() { + this.isAdditional(assgn, "assign:" + i.toString()) and + ( + exists(assgn.(Assignment).getLhs(i)) + or + exists(assgn.(ValueSpec).getNameExpr(i)) + or + assgn instanceof RangeStmt and i in [0, 1] + ) + } override Instruction getRhs() { exists(SimpleAssignStmt a | a = assgn | @@ -663,51 +626,57 @@ module IR { spec.getNumName() = spec.getNumInit() and result = evalExprInstruction(spec.getInit(i)) or - result = MkZeroInitNode(any(ValueEntity v | spec.getNameExpr(i) = v.getDeclaration())) + result = + implicitInitInstruction(any(ValueEntity v | spec.getNameExpr(i) = v.getDeclaration())) ) or - result = MkCompoundAssignRhsNode(assgn) + result.(EvalCompoundAssignRhsInstruction).isAdditional(assgn, "compound-rhs") or - result = MkExtractNode(assgn, i) + result.(ExtractTupleElementInstruction).isAdditional(assgn, "extract:" + i.toString()) } override ControlFlow::Root getRoot() { result.isRootOf(assgn) } - - override string toString() { result = "assignment to " + this.getLhs() } - - override Location getLocation() { result = this.getLhs().getLocation() } } - /** An instruction computing the value of the right-hand side of a compound assignment. */ - class EvalCompoundAssignRhsInstruction extends Instruction, MkCompoundAssignRhsNode { + class EvalCompoundAssignRhsInstruction extends Instruction { CompoundAssignStmt assgn; - EvalCompoundAssignRhsInstruction() { this = MkCompoundAssignRhsNode(assgn) } + EvalCompoundAssignRhsInstruction() { this.isAdditional(assgn, "compound-rhs") } - /** Gets the underlying assignment of this instruction. */ CompoundAssignStmt getAssignment() { result = assgn } override Type getResultType() { result = assgn.getRhs().getType() } override ControlFlow::Root getRoot() { result.isRootOf(assgn) } - - override string toString() { result = assgn.toString() } - - override Location getLocation() { result = assgn.getLocation() } } - /** - * An instruction selecting one of multiple values returned by a function, or either the key - * or the value of the iterator in a range loop, or the result or success value from a type - * assertion. - */ - class ExtractTupleElementInstruction extends Instruction, MkExtractNode { + class ExtractTupleElementInstruction extends Instruction { AstNode s; int i; - ExtractTupleElementInstruction() { this = MkExtractNode(s, i) } + ExtractTupleElementInstruction() { + this.isAdditional(s, "extract:" + i.toString()) and + ( + exists(s.(Assignment).getLhs(i)) + or + exists(s.(ValueSpec).getNameExpr(i)) + or + s instanceof RangeStmt and i in [0, 1] + or + exists(s.(ReturnStmt).getEnclosingFunction().getType().(SignatureType).getResultType(i)) + or + exists( + s.(CallExpr) + .getArgument(0) + .stripParens() + .(CallExpr) + .getType() + .(TupleType) + .getComponentType(i) + ) + ) + } - /** Gets the instruction computing the tuple value from which one value is extracted. */ Instruction getBase() { exists(Expr baseExpr | baseExpr = s.(Assignment).getRhs() or @@ -716,14 +685,13 @@ module IR { result = evalExprInstruction(baseExpr) ) or - result = MkNextNode(s) + result.(GetNextEntryInstruction).isAdditional(s, "next") or result = evalExprInstruction(s.(ReturnStmt).getExpr()) or result = evalExprInstruction(s.(CallExpr).getArgument(0).stripParens()) } - /** Holds if this extracts the `idx`th value of the result of `base`. */ predicate extractsElement(Instruction base, int idx) { base = this.getBase() and idx = i } override Type getResultType() { @@ -738,51 +706,42 @@ module IR { rangeType.(PointerType).getBaseType().getUnderlyingType().(ArrayType).getElementType() or baseType = rangeType.(SliceType).getElementType() | - i = 0 and - result instanceof IntType + i = 0 and result instanceof IntType or - i = 1 and - result = baseType + i = 1 and result = baseType ) or rangeType instanceof StringType and ( - i = 0 and - result instanceof IntType + i = 0 and result instanceof IntType or result = Builtin::rune().getType() ) or exists(MapType map | map = rangeType | - i = 0 and - result = map.getKeyType() + i = 0 and result = map.getKeyType() or - i = 1 and - result = map.getValueType() + i = 1 and result = map.getValueType() ) or - i = 0 and - result = rangeType.(RecvChanType).getElementType() + i = 0 and result = rangeType.(RecvChanType).getElementType() or - i = 0 and - result = rangeType.(SendRecvChanType).getElementType() + i = 0 and result = rangeType.(SendRecvChanType).getElementType() ) } override ControlFlow::Root getRoot() { result.isRootOf(s) } - - override string toString() { result = s + "[" + i + "]" } - - override Location getLocation() { result = s.getLocation() } } - /** - * An instruction that computes the zero value for a variable or constant. - */ - class EvalImplicitInitInstruction extends Instruction, MkZeroInitNode { + class EvalImplicitInitInstruction extends Instruction { ValueEntity v; + int idx; + ValueSpec spec; - EvalImplicitInitInstruction() { this = MkZeroInitNode(v) } + EvalImplicitInitInstruction() { + this.isAdditional(spec, "zero-init:" + idx.toString()) and + spec.getNameExpr(idx) = v.getDeclaration() + } override Type getResultType() { result = v.getType() } @@ -814,104 +773,61 @@ module IR { override predicate isConst() { any() } override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "zero value for " + v } - - override Location getLocation() { result = v.getDeclaration().getLocation() } } - /** - * An instruction that corresponds to the declaration of a function. - */ - class DeclareFunctionInstruction extends Instruction, MkFuncDeclNode { + class DeclareFunctionInstruction extends Instruction { FuncDecl fd; - DeclareFunctionInstruction() { this = MkFuncDeclNode(fd) } + DeclareFunctionInstruction() { this.isIn(fd) and fd instanceof FuncDecl } override Type getResultType() { result = fd.getType() } - - override string toString() { result = fd.toString() } - - override Location getLocation() { result = fd.getLocation() } } - /** - * An instruction that corresponds to a `defer` statement. - */ - class DeferInstruction extends Instruction, MkDeferNode { + class DeferInstruction extends Instruction { DeferStmt defer; - DeferInstruction() { this = MkDeferNode(defer) } + DeferInstruction() { this.isIn(defer) } override ControlFlow::Root getRoot() { result.isRootOf(defer) } - - override string toString() { result = defer.toString() } - - override Location getLocation() { result = defer.getLocation() } } - /** - * An instruction that corresponds to a `go` statement. - */ - class GoInstruction extends Instruction, MkGoNode { + class GoInstruction extends Instruction { GoStmt go; - GoInstruction() { this = MkGoNode(go) } + GoInstruction() { this.isIn(go) } override ControlFlow::Root getRoot() { result.isRootOf(go) } - - override string toString() { result = go.toString() } - - override Location getLocation() { result = go.getLocation() } } - /** - * An instruction that corresponds to an increment or decrement statement. - */ - class IncDecInstruction extends WriteInstruction, MkIncDecNode { + class IncDecInstruction extends WriteInstruction { IncDecStmt ids; - IncDecInstruction() { this = MkIncDecNode(ids) } + IncDecInstruction() { this.isIn(ids) } - override Instruction getRhs() { result = MkIncDecRhs(ids) } + override Instruction getRhs() { + result.(EvalIncDecRhsInstruction).isAdditional(ids, "incdec-rhs") + } override ControlFlow::Root getRoot() { result.isRootOf(ids) } - - override string toString() { result = ids.toString() } - - override Location getLocation() { result = ids.getLocation() } } - /** - * An instruction that computes the (implicit) right-hand side of an increment or - * decrement statement. - */ - class EvalIncDecRhsInstruction extends Instruction, MkIncDecRhs { + class EvalIncDecRhsInstruction extends Instruction { IncDecStmt ids; - EvalIncDecRhsInstruction() { this = MkIncDecRhs(ids) } + EvalIncDecRhsInstruction() { this.isAdditional(ids, "incdec-rhs") } - /** Gets the corresponding increment or decrement statement. */ IncDecStmt getStmt() { result = ids } override Type getResultType() { result = ids.getOperand().getType() } override ControlFlow::Root getRoot() { result.isRootOf(ids) } - - override string toString() { result = "rhs of " + ids } - - override Location getLocation() { result = ids.getLocation() } } - /** - * An instruction computing the implicit operand `1` in an increment or decrement statement. - */ - class EvalImplicitOneInstruction extends Instruction, MkImplicitOne { + class EvalImplicitOneInstruction extends Instruction { IncDecStmt ids; - EvalImplicitOneInstruction() { this = MkImplicitOne(ids) } + EvalImplicitOneInstruction() { this.isAdditional(ids, "implicit-one") } - /** Gets the corresponding increment or decrement statement. */ IncDecStmt getStmt() { result = ids } override Type getResultType() { result = ids.getOperand().getType() } @@ -925,205 +841,184 @@ module IR { override predicate isConst() { any() } override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "1" } - - override Location getLocation() { result = ids.getLocation() } } - /** - * An instruction corresponding to a return from a function. - */ - class ReturnInstruction extends Instruction, MkReturnNode { + class ReturnInstruction extends Instruction { ReturnStmt ret; - ReturnInstruction() { this = MkReturnNode(ret) } + ReturnInstruction() { this.isAdditional(ret, "return") } - /** Gets the corresponding `ReturnStmt`. */ ReturnStmt getReturnStmt() { result = ret } - /** Holds if this statement returns multiple results. */ - predicate returnsMultipleResults() { exists(MkExtractNode(ret, _)) or ret.getNumExpr() > 1 } + predicate returnsMultipleResults() { + exists(ExtractTupleElementInstruction ext | ext.isAdditional(ret, _)) + or + ret.getNumExpr() > 1 + } - /** Gets the instruction whose result is the (unique) result returned by this statement. */ Instruction getResult() { not this.returnsMultipleResults() and result = evalExprInstruction(ret.getExpr()) } - /** Gets the instruction whose result is the `i`th result returned by this statement. */ Instruction getResult(int i) { - result = MkExtractNode(ret, i) + result.isAdditional(ret, _) and + result.(ExtractTupleElementInstruction).extractsElement(_, i) or - not exists(MkExtractNode(ret, _)) and + not exists(ExtractTupleElementInstruction ext | ext.isAdditional(ret, _)) and result = evalExprInstruction(ret.getExpr(i)) } override ControlFlow::Root getRoot() { result.isRootOf(ret) } - - override string toString() { result = ret.toString() } - - override Location getLocation() { result = ret.getLocation() } } - /** - * An instruction that represents the implicit assignment to a result variable - * performed by a return statement. - */ - class WriteResultInstruction extends WriteInstruction, MkResultWriteNode { + class WriteResultInstruction extends WriteInstruction { ResultVariable var; - int i; - ReturnInstruction ret; + int idx; + ReturnStmt retStmt; WriteResultInstruction() { - exists(ReturnStmt retstmt | - this = MkResultWriteNode(var, i, retstmt) and - ret = MkReturnNode(retstmt) - ) + this.isAdditional(retStmt, "result-write:" + idx.toString()) and + var = retStmt.getEnclosingFunction().getResultVar(idx) and + exists(retStmt.getAnExpr()) } - override Instruction getRhs() { result = ret.getResult(i) } + private ReturnInstruction getReturnInstruction() { + result.(ReturnInstruction).isAdditional(retStmt, "return") + } + + override Instruction getRhs() { result = this.getReturnInstruction().getResult(idx) } - /** Gets the result variable being assigned. */ ResultVariable getResultVariable() { result = var } override Type getResultType() { result = var.getType() } override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } - - override string toString() { result = "implicit write of " + var } - - override Location getLocation() { result = ret.getResult(i).getLocation() } } - /** - * An instruction that reads the final value of a result variable upon returning - * from a function. - */ - class ReadResultInstruction extends Instruction, MkResultReadNode { + class ReadResultInstruction extends Instruction { ResultVariable var; + int idx; + FuncDef fd; - ReadResultInstruction() { this = MkResultReadNode(var) } + ReadResultInstruction() { + this.isAdditional(fd, "result-read:" + idx.toString()) and + var = fd.getResultVar(idx) + } override predicate reads(ValueEntity v) { v = var } override Type getResultType() { result = var.getType() } override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } - - override string toString() { result = "implicit read of " + var } - - override Location getLocation() { result = var.getDeclaration().getLocation() } } - /** - * An instruction corresponding to a `select` statement. - */ - class SelectInstruction extends Instruction, MkSelectNode { - SelectStmt sel; - - SelectInstruction() { this = MkSelectNode(sel) } - - override ControlFlow::Root getRoot() { result.isRootOf(sel) } - - override string toString() { result = sel.toString() } - - override Location getLocation() { result = sel.getLocation() } - } - - /** - * An instruction corresponding to a send statement. - */ - class SendInstruction extends Instruction, MkSendNode { + class SendInstruction extends Instruction { SendStmt send; - SendInstruction() { this = MkSendNode(send) } + SendInstruction() { this.isAdditional(send, "send") } override ControlFlow::Root getRoot() { result.isRootOf(send) } - - override string toString() { result = send.toString() } - - override Location getLocation() { result = send.getLocation() } } - /** - * An instruction initializing a parameter to the corresponding argument. - */ - class InitParameterInstruction extends WriteInstruction, MkParameterInit { + class InitParameterInstruction extends WriteInstruction { Parameter parm; + int idx; + FuncDef fd; - InitParameterInstruction() { this = MkParameterInit(parm) } + InitParameterInstruction() { + this.isAdditional(fd, "param-init:" + idx.toString()) and + parm = fd.getParameter(idx) + } - override Instruction getRhs() { result = MkArgumentNode(parm) } + override Instruction getRhs() { + result.(ReadArgumentInstruction).isAdditional(fd, "arg:" + idx.toString()) + } override ControlFlow::Root getRoot() { result = parm.getFunction() } - - override string toString() { result = "initialization of " + parm } - - override Location getLocation() { result = parm.getDeclaration().getLocation() } } - /** - * An instruction reading the value of a function argument. - */ - class ReadArgumentInstruction extends Instruction, MkArgumentNode { + class ReadArgumentInstruction extends Instruction { Parameter parm; + int idx; + FuncDef fd; - ReadArgumentInstruction() { this = MkArgumentNode(parm) } + ReadArgumentInstruction() { + this.isAdditional(fd, "arg:" + idx.toString()) and + parm = fd.getParameter(idx) + } override Type getResultType() { result = parm.getType() } override ControlFlow::Root getRoot() { result = parm.getFunction() } + } - override string toString() { result = "argument corresponding to " + parm } + class InitResultInstruction extends WriteInstruction { + ResultVariable res; + int idx; + FuncDef fd; + + InitResultInstruction() { + this.isAdditional(fd, "result-init:" + idx.toString()) and + res = fd.getResultVar(idx) + } - override Location getLocation() { result = parm.getDeclaration().getLocation() } + override Instruction getRhs() { + result.(ResultZeroInitInstruction).isAdditional(fd, "result-zero-init:" + idx.toString()) + } + + override ControlFlow::Root getRoot() { result = res.getFunction() } } - /** - * An instruction initializing a result variable to its zero value. - */ - class InitResultInstruction extends WriteInstruction, MkResultInit { + private class ResultZeroInitInstruction extends Instruction { ResultVariable res; + int idx; + FuncDef fd; + + ResultZeroInitInstruction() { + this.isAdditional(fd, "result-zero-init:" + idx.toString()) and + res = fd.getResultVar(idx) + } - InitResultInstruction() { this = MkResultInit(res) } + override Type getResultType() { result = res.getType() } - override Instruction getRhs() { result = MkZeroInitNode(res) } + override ControlFlow::Root getRoot() { result.isRootOf(fd) } - override ControlFlow::Root getRoot() { result = res.getFunction() } + override int getIntValue() { + res.getType().getUnderlyingType() instanceof IntegerType and result = 0 + } - override string toString() { result = "initialization of " + res } + override float getFloatValue() { + res.getType().getUnderlyingType() instanceof FloatType and result = 0.0 + } - override Location getLocation() { result = res.getDeclaration().getLocation() } + override string getStringValue() { + res.getType().getUnderlyingType() instanceof StringType and result = "" + } + + override boolean getBoolValue() { + res.getType().getUnderlyingType() instanceof BoolType and result = false + } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } } - /** - * An instruction that gets the next key-value pair in a range loop. - */ - class GetNextEntryInstruction extends Instruction, MkNextNode { + class GetNextEntryInstruction extends Instruction { RangeStmt rs; - GetNextEntryInstruction() { this = MkNextNode(rs) } + GetNextEntryInstruction() { this.isAdditional(rs, "next") } - /** - * Gets the instruction computing the value whose key-value pairs this instruction reads. - */ Instruction getDomain() { result = evalExprInstruction(rs.getDomain()) } override ControlFlow::Root getRoot() { result.isRootOf(rs) } - - override string toString() { result = "next key-value pair in range" } - - override Location getLocation() { result = rs.getDomain().getLocation() } } - /** - * An instruction computing the implicit `true` value in an expression-less `switch` statement. - */ - class EvalImplicitTrueInstruction extends Instruction, MkImplicitTrue { - Stmt stmt; + class EvalImplicitTrueInstruction extends Instruction { + ExpressionSwitchStmt stmt; - EvalImplicitTrueInstruction() { this = MkImplicitTrue(stmt) } + EvalImplicitTrueInstruction() { this.isAdditional(stmt, "implicit-true") } override Type getResultType() { result instanceof BoolType } @@ -1136,91 +1031,37 @@ module IR { override predicate isConst() { any() } override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "true" } - - override Location getLocation() { result = stmt.getLocation() } } - /** - * An instruction corresponding to the implicit comparison or type check performed by an - * expression in a `case` clause. - * - * For example, consider this `switch` statement: - * - * ```go - * switch x { - * case 2, y+1: - * ... - * } - * ``` - * - * The expressions `2` and `y+1` are implicitly compared to `x`. These comparisons are - * represented by case instructions. - */ - class CaseInstruction extends Instruction, MkCaseCheckNode { + class CaseInstruction extends Instruction { CaseClause cc; int i; - CaseInstruction() { this = MkCaseCheckNode(cc, i) } + CaseInstruction() { + this.isAdditional(cc, "case-check:" + i.toString()) and + exists(cc.getExpr(i)) + } override ControlFlow::Root getRoot() { result.isRootOf(cc) } - - override string toString() { result = "case " + cc.getExpr(i) } - - override Location getLocation() { result = cc.getExpr(i).getLocation() } } - /** - * An instruction corresponding to the implicit declaration of the variable - * `lv` in case clause `cc` and its assignment of the value `switchExpr` from - * the guard. This only occurs in case clauses in a type switch statement - * which declares a variable in its guard. - * - * For example, consider this type switch statement: - * - * ```go - * switch y := x.(type) { - * case Type1: - * f(y) - * ... - * } - * ``` - * - * The `y` inside the case clause is actually a local variable with type - * `Type1` that is implicitly declared at the top of the case clause. In - * default clauses and case clauses which list more than one type, the type - * of the implicitly declared variable is the type of `switchExpr`. - */ - class TypeSwitchImplicitVariableInstruction extends Instruction, MkTypeSwitchImplicitVariable { + class TypeSwitchImplicitVariableInstruction extends Instruction { CaseClause cc; - LocalVariable lv; - Expr switchExpr; - TypeSwitchImplicitVariableInstruction() { - this = MkTypeSwitchImplicitVariable(cc, lv, switchExpr) - } + TypeSwitchImplicitVariableInstruction() { this.isAdditional(cc, "type-switch-var") } override predicate writes(ValueEntity v, Instruction rhs) { - v = lv and - rhs = evalExprInstruction(switchExpr) + v = cc.getImplicitlyDeclaredVariable() and + exists(TypeSwitchStmt ts | cc = ts.getACase() | rhs = evalExprInstruction(ts.getExpr())) } override ControlFlow::Root getRoot() { result.isRootOf(cc) } - - override string toString() { result = "implicit type switch variable declaration" } - - override Location getLocation() { result = cc.getLocation() } } - /** - * An instruction computing the implicit lower slice bound of zero in a slice expression without - * an explicit lower bound. - */ - class EvalImplicitLowerSliceBoundInstruction extends Instruction, MkImplicitLowerSliceBound { + class EvalImplicitLowerSliceBoundInstruction extends Instruction { SliceExpr slice; - EvalImplicitLowerSliceBoundInstruction() { this = MkImplicitLowerSliceBound(slice) } + EvalImplicitLowerSliceBoundInstruction() { this.isAdditional(slice, "implicit-low") } override Type getResultType() { result instanceof IntType } @@ -1233,58 +1074,33 @@ module IR { override predicate isConst() { any() } override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "0" } - - override Location getLocation() { result = slice.getLocation() } } - /** - * An instruction computing the implicit upper slice bound in a slice expression without an - * explicit upper bound. - */ - class EvalImplicitUpperSliceBoundInstruction extends Instruction, MkImplicitUpperSliceBound { + class EvalImplicitUpperSliceBoundInstruction extends Instruction { SliceExpr slice; - EvalImplicitUpperSliceBoundInstruction() { this = MkImplicitUpperSliceBound(slice) } + EvalImplicitUpperSliceBoundInstruction() { this.isAdditional(slice, "implicit-high") } override ControlFlow::Root getRoot() { result.isRootOf(slice) } override Type getResultType() { result instanceof IntType } - - override string toString() { result = "len" } - - override Location getLocation() { result = slice.getLocation() } } - /** - * An instruction computing the implicit maximum slice bound in a slice expression without an - * explicit maximum bound. - */ - class EvalImplicitMaxSliceBoundInstruction extends Instruction, MkImplicitMaxSliceBound { + class EvalImplicitMaxSliceBoundInstruction extends Instruction { SliceExpr slice; - EvalImplicitMaxSliceBoundInstruction() { this = MkImplicitMaxSliceBound(slice) } + EvalImplicitMaxSliceBoundInstruction() { this.isAdditional(slice, "implicit-max") } override ControlFlow::Root getRoot() { result.isRootOf(slice) } override Type getResultType() { result instanceof IntType } - - override string toString() { result = "cap" } - - override Location getLocation() { result = slice.getLocation() } } - /** - * An instruction implicitly dereferencing the base in a field or method reference through a - * pointer, or the base in an element or slice reference through a pointer. - */ - class EvalImplicitDerefInstruction extends Instruction, MkImplicitDeref { + class EvalImplicitDerefInstruction extends Instruction { Expr e; - EvalImplicitDerefInstruction() { this = MkImplicitDeref(e) } + EvalImplicitDerefInstruction() { this.isAdditional(e, "implicit-deref") } - /** Gets the operand that is being dereferenced. */ Expr getOperand() { result = e } override Type getResultType() { @@ -1292,13 +1108,38 @@ module IR { } override ControlFlow::Root getRoot() { result.isRootOf(e) } - - override string toString() { result = "implicit dereference" } - - override Location getLocation() { result = e.getLocation() } } /** A representation of the target of a write instruction. */ + newtype TWriteTarget = + MkLhs(ControlFlow::Node write, Expr lhs) { + exists(AstNode assgn, int i | write.isAdditional(assgn, "assign:" + i.toString()) | + lhs = assgn.(Assignment).getLhs(i).stripParens() + or + lhs = assgn.(ValueSpec).getNameExpr(i) + or + exists(RangeStmt rs | rs = assgn | + i = 0 and lhs = rs.getKey().stripParens() + or + i = 1 and lhs = rs.getValue().stripParens() + ) + ) + or + exists(IncDecStmt ids | write.isIn(ids) | lhs = ids.getOperand().stripParens()) + or + exists(FuncDef fd, int idx | + write.isAdditional(fd, "param-init:" + idx.toString()) and + lhs = fd.getParameter(idx).getDeclaration() + ) + or + exists(FuncDef fd, int idx | + write.isAdditional(fd, "result-init:" + idx.toString()) and + lhs = fd.getResultVar(idx).getDeclaration() + ) + } or + MkLiteralElementTarget(InitLiteralComponentInstruction elt) or + MkResultWriteTarget(WriteResultInstruction w) + class WriteTarget extends TWriteTarget { ControlFlow::Node w; @@ -1306,35 +1147,20 @@ module IR { this = MkLhs(w, _) or this = MkLiteralElementTarget(w) or this = MkResultWriteTarget(w) } - /** Gets the write instruction of which this is the target. */ WriteInstruction getWrite() { result = w } - /** Gets the name of the variable or field being written to, if any. */ string getName() { none() } - /** Gets the SSA variable being written to, if any. */ SsaVariable asSsaVariable() { this.getWrite() = result.getDefinition().(SsaExplicitDefinition).getInstruction() } - /** Holds if `e` is the variable or field being written to. */ predicate refersTo(ValueEntity e) { none() } - /** Gets a textual representation of this target. */ string toString() { result = "write target" } - /** Gets the source location for this element. */ Location getLocation() { none() } - /** - * DEPRECATED: Use `getLocation()` instead. - * - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). - */ deprecated predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { @@ -1349,7 +1175,6 @@ module IR { } } - /** A reference to a variable or constant, used as the target of a write. */ class VarOrConstTarget extends WriteTarget { Expr loc; @@ -1390,18 +1215,11 @@ module IR { ) } - /** Gets the variable this refers to, if any. */ Variable getVariable() { this.refersTo(result) } - /** Gets the constant this refers to, if any. */ Constant getConstant() { this.refersTo(result) } - - override string toString() { result = this.getName() } - - override Location getLocation() { result = loc.getLocation() } } - /** A reference to a field, used as the target of a write. */ class FieldTarget extends WriteTarget { FieldTarget() { exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.getBase() instanceof ValueExpr) @@ -1409,14 +1227,12 @@ module IR { w instanceof InitLiteralStructFieldInstruction } - /** Gets the instruction computing the base value on which this field is accessed. */ Instruction getBase() { exists(SelectorExpr sel | this = MkLhs(_, sel) | result = selectorBase(sel)) or result = w.(InitLiteralStructFieldInstruction).getBase() } - /** Get the type of the base of this field access, that is, the type that contains the field. */ Type getBaseType() { result = this.getBase().getResultType() } override predicate refersTo(ValueEntity e) { @@ -1427,27 +1243,9 @@ module IR { override string getName() { exists(Field f | this.refersTo(f) | result = f.getName()) } - /** Gets the field this refers to, if it can be determined. */ Field getField() { this.refersTo(result) } - - override string toString() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | - result = "field " + sel.getSelector().getName() - ) - or - result = "field " + w.(InitLiteralStructFieldInstruction).getFieldName() - } - - override Location getLocation() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | result = sel.getLocation()) - or - result = w.(InitLiteralStructFieldInstruction).getLocation() - } } - /** - * A reference to an element of an array, slice or map, used as the target of a write. - */ class ElementTarget extends WriteTarget { ElementTarget() { this = MkLhs(_, any(IndexExpr idx)) @@ -1455,32 +1253,19 @@ module IR { w instanceof InitLiteralElementInstruction } - /** Gets the instruction computing the base value of this element reference. */ Instruction getBase() { exists(IndexExpr idx | this = MkLhs(_, idx) | result = selectorBase(idx)) or result = w.(InitLiteralComponentInstruction).getBase() } - /** Gets the instruction computing the index of this element reference. */ Instruction getIndex() { exists(IndexExpr idx | this = MkLhs(_, idx) | result = evalExprInstruction(idx.getIndex())) or result = w.(InitLiteralElementInstruction).getIndex() } - - override string toString() { result = "element" } - - override Location getLocation() { - exists(IndexExpr idx | this = MkLhs(_, idx) | result = idx.getLocation()) - or - result = w.(InitLiteralElementInstruction).getLocation() - } } - /** - * A pointer dereference, used as the target of a write. - */ class PointerTarget extends WriteTarget { Expr lhs; @@ -1489,109 +1274,75 @@ module IR { (lhs instanceof StarExpr or lhs instanceof DerefExpr) } - /** Gets the instruction computing the pointer value being dereferenced. */ Instruction getBase() { exists(Expr base | base = lhs.(StarExpr).getBase() or base = lhs.(DerefExpr).getOperand() | result = evalExprInstruction(base) ) } - - override string toString() { result = lhs.toString() } - - override Location getLocation() { result = lhs.getLocation() } } /** * Gets the (final) instruction computing the value of `e`. - * - * Note that some expressions (such as type expressions or labels) have no corresponding - * instruction, so this predicate is undefined for them. - * - * Short-circuiting expressions that are purely used for control flow (meaning that their - * value is not stored in a variable or used to compute the value of a non-shortcircuiting - * expression) do not have a final instruction either. */ Instruction evalExprInstruction(Expr e) { - result = MkExprNode(e) or + result.(EvalInstruction).getExpr() = e + or result = evalExprInstruction(e.(ParenExpr).getExpr()) } - /** - * Gets the instruction corresponding to the initialization of `r`. - */ - InitParameterInstruction initRecvInstruction(ReceiverVariable r) { result = MkParameterInit(r) } + InitParameterInstruction initRecvInstruction(ReceiverVariable r) { + exists(FuncDef fd, int i | + fd.getParameter(i) = r and result.isAdditional(fd, "param-init:" + i.toString()) + ) + } - /** - * Gets the instruction corresponding to the initialization of `p`. - */ - InitParameterInstruction initParamInstruction(Parameter p) { result = MkParameterInit(p) } + InitParameterInstruction initParamInstruction(Parameter p) { + exists(FuncDef fd, int i | + fd.getParameter(i) = p and result.isAdditional(fd, "param-init:" + i.toString()) + ) + } - /** - * Gets the instruction corresponding to the `i`th assignment happening at - * `assgn` (0-based). - */ - AssignInstruction assignInstruction(Assignment assgn, int i) { result = MkAssignNode(assgn, i) } + AssignInstruction assignInstruction(Assignment assgn, int i) { + result.isAdditional(assgn, "assign:" + i.toString()) and + exists(assgn.getLhs(i)) + } - /** - * Gets the instruction corresponding to the `i`th initialization happening - * at `spec` (0-based). - */ - AssignInstruction initInstruction(ValueSpec spec, int i) { result = MkAssignNode(spec, i) } + AssignInstruction initInstruction(ValueSpec spec, int i) { + result.isAdditional(spec, "assign:" + i.toString()) and + exists(spec.getNameExpr(i)) + } - /** - * Gets the instruction corresponding to the assignment of the key variable - * of range statement `rs`. - */ - AssignInstruction assignKeyInstruction(RangeStmt rs) { result = MkAssignNode(rs, 0) } + AssignInstruction assignKeyInstruction(RangeStmt rs) { result.isAdditional(rs, "assign:0") } - /** - * Gets the instruction corresponding to the assignment of the value variable - * of range statement `rs`. - */ - AssignInstruction assignValueInstruction(RangeStmt rs) { result = MkAssignNode(rs, 1) } + AssignInstruction assignValueInstruction(RangeStmt rs) { result.isAdditional(rs, "assign:1") } - /** - * Gets the instruction corresponding to the implicit initialization of `v` - * to its zero value. - */ - EvalImplicitInitInstruction implicitInitInstruction(ValueEntity v) { result = MkZeroInitNode(v) } + EvalImplicitInitInstruction implicitInitInstruction(ValueEntity v) { + exists(ValueSpec spec, int i | + spec.getNameExpr(i) = v.getDeclaration() and + result.isAdditional(spec, "zero-init:" + i.toString()) + ) + } - /** - * Gets the instruction corresponding to the extraction of the `idx`th element - * of the tuple produced by `base`. - */ ExtractTupleElementInstruction extractTupleElement(Instruction base, int idx) { result.extractsElement(base, idx) } - /** - * Gets the instruction corresponding to the implicit lower bound of slice `e`, if any. - */ EvalImplicitLowerSliceBoundInstruction implicitLowerSliceBoundInstruction(SliceExpr e) { - result = MkImplicitLowerSliceBound(e) + result.isAdditional(e, "implicit-low") } - /** - * Gets the instruction corresponding to the implicit upper bound of slice `e`, if any. - */ EvalImplicitUpperSliceBoundInstruction implicitUpperSliceBoundInstruction(SliceExpr e) { - result = MkImplicitUpperSliceBound(e) + result.isAdditional(e, "implicit-high") } - /** - * Gets the instruction corresponding to the implicit maximum bound of slice `e`, if any. - */ EvalImplicitMaxSliceBoundInstruction implicitMaxSliceBoundInstruction(SliceExpr e) { - result = MkImplicitMaxSliceBound(e) + result.isAdditional(e, "implicit-max") } - /** - * Gets the implicit dereference instruction for `e`, where `e` is a pointer used as the base - * in a field/method access, element access, or slice expression. - */ - EvalImplicitDerefInstruction implicitDerefInstruction(Expr e) { result = MkImplicitDeref(e) } + EvalImplicitDerefInstruction implicitDerefInstruction(Expr e) { + result.isAdditional(e, "implicit-deref") + } - /** Gets the base of `insn`, if `insn` is an implicit field read. */ Instruction lookThroughImplicitFieldRead(Instruction insn) { result = insn.(ImplicitFieldReadInstruction).getBaseInstruction() } diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll.bak b/go/ql/lib/semmle/go/controlflow/IR.qll.bak new file mode 100644 index 000000000000..807fbf76d1fd --- /dev/null +++ b/go/ql/lib/semmle/go/controlflow/IR.qll.bak @@ -0,0 +1,1580 @@ +/** + * Provides classes and predicates for working with an intermediate representation (IR) of Go + * programs that is used as the foundation of the control flow and data flow graphs. + * + * In the IR, the program is represented as a set of instructions, which correspond to expressions + * and statements that compute a value or perform an operation (as opposed to providing syntactic + * structure or type information). + * + * Each instruction is also a control-flow node, but there are control-flow nodes that are not + * instructions (synthetic entry and exit nodes, as well as before/after nodes). + */ +overlay[local] +module; + +import go +private import ControlFlowGraphShared + +/** Provides predicates and classes for working with IR constructs. */ +module IR { + /** + * An IR instruction. + */ + class Instruction extends ControlFlow::Node { + Instruction() { + this.isIn(_) or + this.isAdditional(_, _) or + this.isAfterTrue(_) and not this.isIn(_) or + this.isAfterFalse(_) and not this.isIn(_) + } + + /** Holds if this instruction reads the value of variable or constant `v`. */ + predicate reads(ValueEntity v) { this.readsField(_, v) or this.readsMethod(_, v) } + + /** Holds if this instruction updates variable or constant `v` to the value of `rhs`. */ + predicate writes(ValueEntity v, Instruction rhs) { this.writesField(_, v, rhs) } + + /** Holds if this instruction reads the value of field `f` on the value of `base`. */ + predicate readsField(Instruction base, Field f) { none() } + + /** Holds if this instruction updates the value of field `f` on the value of `base`. */ + predicate writesField(Instruction base, Field f, Instruction rhs) { none() } + + /** Holds if this instruction looks up method `m` on the value of `receiver`. */ + predicate readsMethod(Instruction receiver, Method m) { none() } + + /** Holds if this instruction reads the value of element `index` on the value of `base`. */ + predicate readsElement(Instruction base, Instruction index) { none() } + + /** Holds if this instruction updates the value of element `index` on the value of `base`. */ + predicate writesElement(Instruction base, Instruction index) { none() } + + /** Gets the type of the result of this instruction, if any. */ + Type getResultType() { none() } + + /** Gets the float value of the result of this instruction, if it can be determined. */ + float getFloatValue() { none() } + + /** Gets the int value of the result of this instruction, if it can be determined. */ + int getIntValue() { none() } + + /** + * Holds if the complex value of the result of this instruction has real part `real` and + * imaginary part `imag`. + */ + predicate hasComplexValue(float real, float imag) { none() } + + /** Gets either `getFloatValue` or `getIntValue` */ + float getNumericValue() { result = this.getFloatValue() or result = this.getIntValue() } + + /** + * Gets the string representation of the exact value of the result of this instruction, + * if any. + * + * For example, for the constant 3.141592653589793238462, this will + * result in 1570796326794896619231/500000000000000000000 + */ + string getExactValue() { none() } + + /** Gets the string value of the result of this instruction, if it can be determined. */ + string getStringValue() { none() } + + /** Gets the Boolean value of the result of this instruction, if it can be determined. */ + boolean getBoolValue() { none() } + + /** Holds if the result of this instruction is known at compile time. */ + predicate isConst() { none() } + + /** + * Holds if the result of this instruction is known at compile time, and is guaranteed not to + * depend on the platform where it is evaluated. + */ + predicate isPlatformIndependentConstant() { none() } + + /** Gets a textual representation of the kind of this instruction. */ + string getInsnKind() { + this instanceof EvalInstruction and result = "expression" + or + this instanceof InitLiteralComponentInstruction and result = "element init" + or + this instanceof ImplicitLiteralElementIndexInstruction and result = "element index" + or + this instanceof AssignInstruction and result = "assignment" + or + this instanceof EvalCompoundAssignRhsInstruction and + result = "right-hand side of compound assignment" + or + this instanceof ExtractTupleElementInstruction and result = "tuple element extraction" + or + this instanceof EvalImplicitInitInstruction and result = "zero value" + or + this instanceof DeclareFunctionInstruction and result = "function declaration" + or + this instanceof DeferInstruction and result = "defer" + or + this instanceof GoInstruction and result = "go" + or + this instanceof ConditionGuardInstruction and result = "condition guard" + or + this instanceof IncDecInstruction and result = "increment/decrement" + or + this instanceof EvalIncDecRhsInstruction and + result = "right-hand side of increment/decrement" + or + this instanceof EvalImplicitOneInstruction and result = "implicit 1" + or + this instanceof ReturnInstruction and result = "return" + or + this instanceof WriteResultInstruction and result = "result write" + or + this instanceof ReadResultInstruction and result = "result read" + or + this instanceof SendInstruction and result = "send" + or + this instanceof InitParameterInstruction and result = "parameter initialization" + or + this instanceof ReadArgumentInstruction and result = "argument" + or + this instanceof InitResultInstruction and result = "result initialization" + or + this instanceof GetNextEntryInstruction and result = "next key-value pair" + or + this instanceof EvalImplicitTrueInstruction and result = "implicit true" + or + this instanceof CaseInstruction and result = "case" + or + this instanceof TypeSwitchImplicitVariableInstruction and + result = "type switch implicit variable declaration" + or + this instanceof EvalImplicitLowerSliceBoundInstruction and result = "implicit lower bound" + or + this instanceof EvalImplicitUpperSliceBoundInstruction and result = "implicit upper bound" + or + this instanceof EvalImplicitMaxSliceBoundInstruction and result = "implicit maximum" + or + this instanceof EvalImplicitDerefInstruction and result = "implicit dereference" + or + this instanceof ImplicitFieldReadInstruction and result = "implicit field selection" + } + } + + /** A condition guard instruction, representing a known boolean outcome for a condition. */ + private class ConditionGuardInstruction extends Instruction { + ConditionGuardInstruction() { + this.isAfterTrue(_) and not this.isIn(_) + or + this.isAfterFalse(_) and not this.isIn(_) + } + } + + /** + * An IR instruction representing the evaluation of an expression. + */ + class EvalInstruction extends Instruction { + Expr e; + + EvalInstruction() { this.isIn(e) and e instanceof Expr } + + /** Gets the expression underlying this instruction. */ + Expr getExpr() { result = e } + + override predicate reads(ValueEntity v) { e = v.getAReference() } + + override Type getResultType() { result = e.getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(e) } + + override float getFloatValue() { result = e.getFloatValue() } + + override int getIntValue() { result = e.getIntValue() } + + override predicate hasComplexValue(float real, float imag) { e.hasComplexValue(real, imag) } + + override string getExactValue() { result = e.getExactValue() } + + override string getStringValue() { result = e.getStringValue() } + + override boolean getBoolValue() { result = e.getBoolValue() } + + override predicate isConst() { e.isConst() } + + override predicate isPlatformIndependentConstant() { e.isPlatformIndependentConstant() } + + override string toString() { result = e.toString() } + + override Location getLocation() { result = e.getLocation() } + } + + /** + * An IR instruction that reads the value of a variable, constant, field or array element, + * or refers to a function. + */ + class ReadInstruction extends Instruction { + ReadInstruction() { + exists(Expr e | e = this.(EvalInstruction).getExpr() | + (e instanceof ValueName or e instanceof IndexExpr) and + e.(ReferenceExpr).isRvalue() + ) + or + this instanceof ReadResultInstruction + or + this instanceof ImplicitFieldReadInstruction + } + } + + /** + * Gets the effective base of a selector, index or slice expression, taking implicit dereferences + * and implicit field reads into account. + * + * For a selector expression `b.f`, this could be the implicit dereference `*b`, or the implicit + * field access `b.Embedded` if the field `f` is promoted from an embedded type `Embedded`, or a + * combination of both `*(b.Embedded)`, or simply `b` if neither case applies. + */ + private Instruction selectorBase(Expr e) { + exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1 | + result = fri + ) + or + not exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1) and + exists(Expr base | + base = e.(SelectorExpr).getBase() + or + base = e.(IndexExpr).getBase() + or + base = e.(SliceExpr).getBase() + | + result = MkImplicitDeref(base) + or + not exists(MkImplicitDeref(base)) and + result = evalExprInstruction(base) + ) + } + + /** + * An IR instruction that reads a component from a composite object. + * + * This is either a field of a struct, or an element of an array, map, slice or string. + */ + class ComponentReadInstruction extends ReadInstruction { + ComponentReadInstruction() { + exists(Expr e | e = this.(EvalInstruction).getExpr() | + e instanceof IndexExpr + or + e.(SelectorExpr).getBase() instanceof ValueExpr and + not e.(SelectorExpr).getSelector() = any(Method method).getAReference() + ) + or + this instanceof MkImplicitFieldSelection + } + + /** Gets the instruction computing the base value on which the field or element is read. */ + Instruction getBase() { + result = this.(ImplicitFieldReadInstruction).getBaseInstruction() + or + result = selectorBase(this.(EvalInstruction).getExpr()) + } + } + + /** + * An IR instruction that reads the value of a field. + * + * On databases with incomplete type information, method expressions may sometimes be + * misclassified as field reads. + */ + class FieldReadInstruction extends ComponentReadInstruction { + SelectorExpr e; + int index; + Field field; + + FieldReadInstruction() { + e = this.(EvalInstruction).getExpr() and + index = 0 and + field.getAReference() = e.getSelector() + or + this = MkImplicitFieldSelection(e, index, field) + } + + /** Gets the `SelectorExpr` of this field read. */ + SelectorExpr getSelectorExpr() { result = e } + + /** Gets the index of this field read. */ + int getIndex() { result = index } + + /** Gets the field being read. */ + Field getField() { result = field } + + Instruction getBaseInstruction() { + exists(ImplicitFieldReadInstruction fri | + fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) + | + result = fri + ) + or + not exists(ImplicitFieldReadInstruction fri | + fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) + ) and + ( + result = MkImplicitDeref(e.getBase()) + or + not exists(MkImplicitDeref(e.getBase())) and + result = evalExprInstruction(e.getBase()) + ) + } + + override predicate readsField(Instruction base, Field f) { + base = this.getBaseInstruction() and f = field + } + } + + /** + * An IR instruction for an implicit field read as part of reading a + * promoted field. + * + * If the field that is being implicitly read has a pointer type then this + * instruction represents an implicit dereference of it. + */ + class ImplicitFieldReadInstruction extends FieldReadInstruction, MkImplicitFieldSelection { + ImplicitFieldReadInstruction() { this = MkImplicitFieldSelection(e, index, field) } + + override predicate reads(ValueEntity v) { v = field } + + override Type getResultType() { result = lookThroughPointerType(field.getType()) } + + override ControlFlow::Root getRoot() { result.isRootOf(e) } + + override string toString() { result = "implicit read of field " + field.toString() } + + override Location getLocation() { result = e.getBase().getLocation() } + } + + /** + * An IR instruction that looks up a method. + */ + class MethodReadInstruction extends ReadInstruction, EvalInstruction { + Method method; + override SelectorExpr e; + + MethodReadInstruction() { e.getSelector() = method.getAReference() } + + /** Gets the instruction computing the receiver value on which the method is looked up. */ + Instruction getReceiver() { result = selectorBase(e) } + + /** Gets the method being looked up. */ + Method getMethod() { result = method } + + override predicate readsMethod(Instruction receiver, Method m) { + receiver = this.getReceiver() and m = this.getMethod() + } + } + + /** + * An IR instruction that reads an element of an array, slice, map or string. + */ + class ElementReadInstruction extends ComponentReadInstruction, EvalInstruction { + override IndexExpr e; + + /** Gets the instruction computing the index of the element being looked up. */ + Instruction getIndex() { result = evalExprInstruction(e.getIndex()) } + + override predicate readsElement(Instruction base, Instruction index) { + base = this.getBase() and index = this.getIndex() + } + } + + /** + * An IR instruction that constructs a slice. + */ + class SliceInstruction extends EvalInstruction { + override SliceExpr e; + + /** Gets the instruction computing the base value from which the slice is constructed. */ + Instruction getBase() { result = selectorBase(e) } + + /** Gets the instruction computing the lower bound of the slice. */ + Instruction getLow() { + result = evalExprInstruction(e.getLow()) or + result = implicitLowerSliceBoundInstruction(e) + } + + /** Gets the instruction computing the upper bound of the slice. */ + Instruction getHigh() { + result = evalExprInstruction(e.getHigh()) or + result = implicitUpperSliceBoundInstruction(e) + } + + /** Gets the instruction computing the capacity of the slice. */ + Instruction getMax() { + result = evalExprInstruction(e.getMax()) or + result = implicitMaxSliceBoundInstruction(e) + } + } + + /** + * An IR instruction that writes a memory location. + */ + class WriteInstruction extends Instruction { + WriteTarget lhs; + Boolean initialization; + + WriteInstruction() { + ( + lhs = MkLhs(this, _) + or + lhs = MkResultWriteTarget(this) + ) and + initialization = false + or + lhs = MkLiteralElementTarget(this) and initialization = true + } + + /** Gets the target to which this instruction writes. */ + WriteTarget getLhs() { result = lhs } + + /** Holds if this instruction initializes a literal. */ + predicate isInitialization() { initialization = true } + + /** Gets the instruction computing the value this instruction writes. */ + Instruction getRhs() { none() } + + override predicate writes(ValueEntity v, Instruction rhs) { + this.getLhs().refersTo(v) and + rhs = this.getRhs() + } + } + + /** + * An IR instruction that initializes a component of a composite literal. + */ + class InitLiteralComponentInstruction extends WriteInstruction, MkLiteralElementInitNode { + CompositeLit lit; + int i; + Expr elt; + + InitLiteralComponentInstruction() { + this = MkLiteralElementInitNode(elt) and elt = lit.getElement(i) + } + + /** Gets the instruction allocating the composite literal. */ + Instruction getBase() { result = evalExprInstruction(lit) } + + override Instruction getRhs() { + result = evalExprInstruction(elt) or + result = evalExprInstruction(elt.(KeyValueExpr).getValue()) + } + + override ControlFlow::Root getRoot() { result.isRootOf(elt) } + + override string toString() { result = "init of " + elt } + + override Location getLocation() { result = elt.getLocation() } + } + + /** + * An IR instruction that initializes a field of a struct literal. + */ + class InitLiteralStructFieldInstruction extends InitLiteralComponentInstruction { + override StructLit lit; + + /** Gets the name of the initialized field. */ + pragma[nomagic] + string getFieldName() { + if elt instanceof KeyValueExpr + then result = elt.(KeyValueExpr).getKey().(Ident).getName() + else pragma[only_bind_out](lit.getStructType()).hasOwnField(i, result, _, _) + } + + /** Gets the initialized field. */ + Field getField() { + result.getDeclaringType() = lit.getStructType() and + result.getName() = this.getFieldName() + } + } + + /** + * An IR instruction that initializes an element of an array, slice or map literal. + */ + class InitLiteralElementInstruction extends InitLiteralComponentInstruction { + Type literalType; + + InitLiteralElementInstruction() { + literalType = lit.getType().getUnderlyingType() and + ( + literalType instanceof ArrayType or + literalType instanceof SliceType or + literalType instanceof MapType + ) + } + + /** Gets the instruction computing the index of the initialized element. */ + Instruction getIndex() { + result = evalExprInstruction(elt.(KeyValueExpr).getKey()) + or + result = MkImplicitLiteralElementIndex(elt) + } + } + + /** + * An IR instruction that initializes an element of an array literal. + */ + class InitLiteralArrayElementInstruction extends InitLiteralElementInstruction { + override ArrayType literalType; + } + + /** + * An IR instruction that initializes an element of a slice literal. + */ + class InitLiteralSliceElementInstruction extends InitLiteralElementInstruction { + override SliceType literalType; + } + + /** + * An IR instruction that initializes an element of a map literal. + */ + class InitLiteralMapElementInstruction extends InitLiteralElementInstruction { + override MapType literalType; + } + + /** + * An IR instruction that writes to a field. + */ + class FieldWriteInstruction extends WriteInstruction { + override FieldTarget lhs; + + /** Gets the instruction computing the base value on which the field is written. */ + Instruction getBase() { result = lhs.getBase() } + + /** Gets the field being written. */ + Field getField() { result = lhs.getField() } + + override predicate writesField(Instruction base, Field f, Instruction rhs) { + this.getBase() = base and + this.getField() = f and + this.getRhs() = rhs + } + } + + /** + * An IR instruction that writes to an element of an array, slice, or map. + */ + class ElementWriteInstruction extends WriteInstruction { + override ElementTarget lhs; + + /** Gets the instruction computing the base value on which the field is written. */ + Instruction getBase() { result = lhs.getBase() } + + /** Gets the instruction computing the element index being written. */ + Instruction getIndex() { result = lhs.getIndex() } + + override predicate writesElement(Instruction base, Instruction index) { + this.getBase() = base and + this.getIndex() = index + } + } + + /** Holds if `lit` does not specify any explicit keys. */ + private predicate noExplicitKeys(CompositeLit lit) { + not lit.getAnElement() instanceof KeyValueExpr + } + + /** Gets the index of the `i`th element in (array or slice) literal `lit`. */ + private int getElementIndex(CompositeLit lit, int i) { + ( + lit.getType().getUnderlyingType() instanceof ArrayType or + lit.getType().getUnderlyingType() instanceof SliceType + ) and + exists(Expr elt | elt = lit.getElement(i) | + // short-circuit computation for literals without any explicit keys + noExplicitKeys(lit) and result = i + or + result = elt.(KeyValueExpr).getKey().getIntValue() + or + not elt instanceof KeyValueExpr and + ( + i = 0 and result = 0 + or + result = getElementIndex(lit, i - 1) + 1 + ) + ) + } + + /** + * An IR instruction computing the implicit index of an element in an array or slice literal. + */ + class ImplicitLiteralElementIndexInstruction extends Instruction, MkImplicitLiteralElementIndex { + Expr elt; + + ImplicitLiteralElementIndexInstruction() { this = MkImplicitLiteralElementIndex(elt) } + + override Type getResultType() { result instanceof IntType } + + override ControlFlow::Root getRoot() { result.isRootOf(elt) } + + override int getIntValue() { + exists(CompositeLit lit, int i | elt = lit.getElement(i) | result = getElementIndex(lit, i)) + } + + override string getStringValue() { none() } + + override string getExactValue() { result = this.getIntValue().toString() } + + override predicate isPlatformIndependentConstant() { any() } + + override predicate isConst() { any() } + + override string toString() { result = "element index" } + + override Location getLocation() { result = elt.getLocation() } + } + + /** + * An instruction assigning to a variable or field. + */ + class AssignInstruction extends WriteInstruction, MkAssignNode { + AstNode assgn; + int i; + + AssignInstruction() { this = MkAssignNode(assgn, i) } + + override Instruction getRhs() { + exists(SimpleAssignStmt a | a = assgn | + a.getNumLhs() = a.getNumRhs() and + result = evalExprInstruction(a.getRhs(i)) + ) + or + exists(ValueSpec spec | spec = assgn | + spec.getNumName() = spec.getNumInit() and + result = evalExprInstruction(spec.getInit(i)) + or + result = MkZeroInitNode(any(ValueEntity v | spec.getNameExpr(i) = v.getDeclaration())) + ) + or + result = MkCompoundAssignRhsNode(assgn) + or + result = MkExtractNode(assgn, i) + } + + override ControlFlow::Root getRoot() { result.isRootOf(assgn) } + + override string toString() { result = "assignment to " + this.getLhs() } + + override Location getLocation() { result = this.getLhs().getLocation() } + } + + /** An instruction computing the value of the right-hand side of a compound assignment. */ + class EvalCompoundAssignRhsInstruction extends Instruction, MkCompoundAssignRhsNode { + CompoundAssignStmt assgn; + + EvalCompoundAssignRhsInstruction() { this = MkCompoundAssignRhsNode(assgn) } + + /** Gets the underlying assignment of this instruction. */ + CompoundAssignStmt getAssignment() { result = assgn } + + override Type getResultType() { result = assgn.getRhs().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(assgn) } + + override string toString() { result = assgn.toString() } + + override Location getLocation() { result = assgn.getLocation() } + } + + /** + * An instruction selecting one of multiple values returned by a function, or either the key + * or the value of the iterator in a range loop, or the result or success value from a type + * assertion. + */ + class ExtractTupleElementInstruction extends Instruction, MkExtractNode { + AstNode s; + int i; + + ExtractTupleElementInstruction() { this = MkExtractNode(s, i) } + + /** Gets the instruction computing the tuple value from which one value is extracted. */ + Instruction getBase() { + exists(Expr baseExpr | + baseExpr = s.(Assignment).getRhs() or + baseExpr = s.(ValueSpec).getInit() + | + result = evalExprInstruction(baseExpr) + ) + or + result = MkNextNode(s) + or + result = evalExprInstruction(s.(ReturnStmt).getExpr()) + or + result = evalExprInstruction(s.(CallExpr).getArgument(0).stripParens()) + } + + /** Holds if this extracts the `idx`th value of the result of `base`. */ + predicate extractsElement(Instruction base, int idx) { base = this.getBase() and idx = i } + + override Type getResultType() { + exists(Expr e | this.getBase() = evalExprInstruction(e) | + result = e.getType().(TupleType).getComponentType(pragma[only_bind_into](i)) + ) + or + exists(Type rangeType | rangeType = s.(RangeStmt).getDomain().getType().getUnderlyingType() | + exists(Type baseType | + baseType = rangeType.(ArrayType).getElementType() or + baseType = + rangeType.(PointerType).getBaseType().getUnderlyingType().(ArrayType).getElementType() or + baseType = rangeType.(SliceType).getElementType() + | + i = 0 and + result instanceof IntType + or + i = 1 and + result = baseType + ) + or + rangeType instanceof StringType and + ( + i = 0 and + result instanceof IntType + or + result = Builtin::rune().getType() + ) + or + exists(MapType map | map = rangeType | + i = 0 and + result = map.getKeyType() + or + i = 1 and + result = map.getValueType() + ) + or + i = 0 and + result = rangeType.(RecvChanType).getElementType() + or + i = 0 and + result = rangeType.(SendRecvChanType).getElementType() + ) + } + + override ControlFlow::Root getRoot() { result.isRootOf(s) } + + override string toString() { result = s + "[" + i + "]" } + + override Location getLocation() { result = s.getLocation() } + } + + /** + * An instruction that computes the zero value for a variable or constant. + */ + class EvalImplicitInitInstruction extends Instruction, MkZeroInitNode { + ValueEntity v; + + EvalImplicitInitInstruction() { this = MkZeroInitNode(v) } + + override Type getResultType() { result = v.getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(v.getDeclaration()) } + + override int getIntValue() { + v.getType().getUnderlyingType() instanceof IntegerType and result = 0 + } + + override float getFloatValue() { + v.getType().getUnderlyingType() instanceof FloatType and result = 0.0 + } + + override string getStringValue() { + v.getType().getUnderlyingType() instanceof StringType and result = "" + } + + override boolean getBoolValue() { + v.getType().getUnderlyingType() instanceof BoolType and result = false + } + + override string getExactValue() { + result = this.getIntValue().toString() or + result = this.getFloatValue().toString() or + result = this.getStringValue().toString() or + result = this.getBoolValue().toString() + } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "zero value for " + v } + + override Location getLocation() { result = v.getDeclaration().getLocation() } + } + + /** + * An instruction that corresponds to the declaration of a function. + */ + class DeclareFunctionInstruction extends Instruction, MkFuncDeclNode { + FuncDecl fd; + + DeclareFunctionInstruction() { this = MkFuncDeclNode(fd) } + + override Type getResultType() { result = fd.getType() } + + override string toString() { result = fd.toString() } + + override Location getLocation() { result = fd.getLocation() } + } + + /** + * An instruction that corresponds to a `defer` statement. + */ + class DeferInstruction extends Instruction, MkDeferNode { + DeferStmt defer; + + DeferInstruction() { this = MkDeferNode(defer) } + + override ControlFlow::Root getRoot() { result.isRootOf(defer) } + + override string toString() { result = defer.toString() } + + override Location getLocation() { result = defer.getLocation() } + } + + /** + * An instruction that corresponds to a `go` statement. + */ + class GoInstruction extends Instruction, MkGoNode { + GoStmt go; + + GoInstruction() { this = MkGoNode(go) } + + override ControlFlow::Root getRoot() { result.isRootOf(go) } + + override string toString() { result = go.toString() } + + override Location getLocation() { result = go.getLocation() } + } + + /** + * An instruction that corresponds to an increment or decrement statement. + */ + class IncDecInstruction extends WriteInstruction, MkIncDecNode { + IncDecStmt ids; + + IncDecInstruction() { this = MkIncDecNode(ids) } + + override Instruction getRhs() { result = MkIncDecRhs(ids) } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override string toString() { result = ids.toString() } + + override Location getLocation() { result = ids.getLocation() } + } + + /** + * An instruction that computes the (implicit) right-hand side of an increment or + * decrement statement. + */ + class EvalIncDecRhsInstruction extends Instruction, MkIncDecRhs { + IncDecStmt ids; + + EvalIncDecRhsInstruction() { this = MkIncDecRhs(ids) } + + /** Gets the corresponding increment or decrement statement. */ + IncDecStmt getStmt() { result = ids } + + override Type getResultType() { result = ids.getOperand().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override string toString() { result = "rhs of " + ids } + + override Location getLocation() { result = ids.getLocation() } + } + + /** + * An instruction computing the implicit operand `1` in an increment or decrement statement. + */ + class EvalImplicitOneInstruction extends Instruction, MkImplicitOne { + IncDecStmt ids; + + EvalImplicitOneInstruction() { this = MkImplicitOne(ids) } + + /** Gets the corresponding increment or decrement statement. */ + IncDecStmt getStmt() { result = ids } + + override Type getResultType() { result = ids.getOperand().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override int getIntValue() { result = 1 } + + override string getExactValue() { result = "1" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "1" } + + override Location getLocation() { result = ids.getLocation() } + } + + /** + * An instruction corresponding to a return from a function. + */ + class ReturnInstruction extends Instruction, MkReturnNode { + ReturnStmt ret; + + ReturnInstruction() { this = MkReturnNode(ret) } + + /** Gets the corresponding `ReturnStmt`. */ + ReturnStmt getReturnStmt() { result = ret } + + /** Holds if this statement returns multiple results. */ + predicate returnsMultipleResults() { exists(MkExtractNode(ret, _)) or ret.getNumExpr() > 1 } + + /** Gets the instruction whose result is the (unique) result returned by this statement. */ + Instruction getResult() { + not this.returnsMultipleResults() and + result = evalExprInstruction(ret.getExpr()) + } + + /** Gets the instruction whose result is the `i`th result returned by this statement. */ + Instruction getResult(int i) { + result = MkExtractNode(ret, i) + or + not exists(MkExtractNode(ret, _)) and + result = evalExprInstruction(ret.getExpr(i)) + } + + override ControlFlow::Root getRoot() { result.isRootOf(ret) } + + override string toString() { result = ret.toString() } + + override Location getLocation() { result = ret.getLocation() } + } + + /** + * An instruction that represents the implicit assignment to a result variable + * performed by a return statement. + */ + class WriteResultInstruction extends WriteInstruction, MkResultWriteNode { + ResultVariable var; + int i; + ReturnInstruction ret; + + WriteResultInstruction() { + exists(ReturnStmt retstmt | + this = MkResultWriteNode(var, i, retstmt) and + ret = MkReturnNode(retstmt) + ) + } + + override Instruction getRhs() { result = ret.getResult(i) } + + /** Gets the result variable being assigned. */ + ResultVariable getResultVariable() { result = var } + + override Type getResultType() { result = var.getType() } + + override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } + + override string toString() { result = "implicit write of " + var } + + override Location getLocation() { result = ret.getResult(i).getLocation() } + } + + /** + * An instruction that reads the final value of a result variable upon returning + * from a function. + */ + class ReadResultInstruction extends Instruction, MkResultReadNode { + ResultVariable var; + + ReadResultInstruction() { this = MkResultReadNode(var) } + + override predicate reads(ValueEntity v) { v = var } + + override Type getResultType() { result = var.getType() } + + override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } + + override string toString() { result = "implicit read of " + var } + + override Location getLocation() { result = var.getDeclaration().getLocation() } + } + + /** + * An instruction corresponding to a `select` statement. + */ + class SelectInstruction extends Instruction, MkSelectNode { + SelectStmt sel; + + SelectInstruction() { this = MkSelectNode(sel) } + + override ControlFlow::Root getRoot() { result.isRootOf(sel) } + + override string toString() { result = sel.toString() } + + override Location getLocation() { result = sel.getLocation() } + } + + /** + * An instruction corresponding to a send statement. + */ + class SendInstruction extends Instruction, MkSendNode { + SendStmt send; + + SendInstruction() { this = MkSendNode(send) } + + override ControlFlow::Root getRoot() { result.isRootOf(send) } + + override string toString() { result = send.toString() } + + override Location getLocation() { result = send.getLocation() } + } + + /** + * An instruction initializing a parameter to the corresponding argument. + */ + class InitParameterInstruction extends WriteInstruction, MkParameterInit { + Parameter parm; + + InitParameterInstruction() { this = MkParameterInit(parm) } + + override Instruction getRhs() { result = MkArgumentNode(parm) } + + override ControlFlow::Root getRoot() { result = parm.getFunction() } + + override string toString() { result = "initialization of " + parm } + + override Location getLocation() { result = parm.getDeclaration().getLocation() } + } + + /** + * An instruction reading the value of a function argument. + */ + class ReadArgumentInstruction extends Instruction, MkArgumentNode { + Parameter parm; + + ReadArgumentInstruction() { this = MkArgumentNode(parm) } + + override Type getResultType() { result = parm.getType() } + + override ControlFlow::Root getRoot() { result = parm.getFunction() } + + override string toString() { result = "argument corresponding to " + parm } + + override Location getLocation() { result = parm.getDeclaration().getLocation() } + } + + /** + * An instruction initializing a result variable to its zero value. + */ + class InitResultInstruction extends WriteInstruction, MkResultInit { + ResultVariable res; + + InitResultInstruction() { this = MkResultInit(res) } + + override Instruction getRhs() { result = MkZeroInitNode(res) } + + override ControlFlow::Root getRoot() { result = res.getFunction() } + + override string toString() { result = "initialization of " + res } + + override Location getLocation() { result = res.getDeclaration().getLocation() } + } + + /** + * An instruction that gets the next key-value pair in a range loop. + */ + class GetNextEntryInstruction extends Instruction, MkNextNode { + RangeStmt rs; + + GetNextEntryInstruction() { this = MkNextNode(rs) } + + /** + * Gets the instruction computing the value whose key-value pairs this instruction reads. + */ + Instruction getDomain() { result = evalExprInstruction(rs.getDomain()) } + + override ControlFlow::Root getRoot() { result.isRootOf(rs) } + + override string toString() { result = "next key-value pair in range" } + + override Location getLocation() { result = rs.getDomain().getLocation() } + } + + /** + * An instruction computing the implicit `true` value in an expression-less `switch` statement. + */ + class EvalImplicitTrueInstruction extends Instruction, MkImplicitTrue { + Stmt stmt; + + EvalImplicitTrueInstruction() { this = MkImplicitTrue(stmt) } + + override Type getResultType() { result instanceof BoolType } + + override ControlFlow::Root getRoot() { result.isRootOf(stmt) } + + override boolean getBoolValue() { result = true } + + override string getExactValue() { result = "true" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "true" } + + override Location getLocation() { result = stmt.getLocation() } + } + + /** + * An instruction corresponding to the implicit comparison or type check performed by an + * expression in a `case` clause. + * + * For example, consider this `switch` statement: + * + * ```go + * switch x { + * case 2, y+1: + * ... + * } + * ``` + * + * The expressions `2` and `y+1` are implicitly compared to `x`. These comparisons are + * represented by case instructions. + */ + class CaseInstruction extends Instruction, MkCaseCheckNode { + CaseClause cc; + int i; + + CaseInstruction() { this = MkCaseCheckNode(cc, i) } + + override ControlFlow::Root getRoot() { result.isRootOf(cc) } + + override string toString() { result = "case " + cc.getExpr(i) } + + override Location getLocation() { result = cc.getExpr(i).getLocation() } + } + + /** + * An instruction corresponding to the implicit declaration of the variable + * `lv` in case clause `cc` and its assignment of the value `switchExpr` from + * the guard. This only occurs in case clauses in a type switch statement + * which declares a variable in its guard. + * + * For example, consider this type switch statement: + * + * ```go + * switch y := x.(type) { + * case Type1: + * f(y) + * ... + * } + * ``` + * + * The `y` inside the case clause is actually a local variable with type + * `Type1` that is implicitly declared at the top of the case clause. In + * default clauses and case clauses which list more than one type, the type + * of the implicitly declared variable is the type of `switchExpr`. + */ + class TypeSwitchImplicitVariableInstruction extends Instruction, MkTypeSwitchImplicitVariable { + CaseClause cc; + LocalVariable lv; + Expr switchExpr; + + TypeSwitchImplicitVariableInstruction() { + this = MkTypeSwitchImplicitVariable(cc, lv, switchExpr) + } + + override predicate writes(ValueEntity v, Instruction rhs) { + v = lv and + rhs = evalExprInstruction(switchExpr) + } + + override ControlFlow::Root getRoot() { result.isRootOf(cc) } + + override string toString() { result = "implicit type switch variable declaration" } + + override Location getLocation() { result = cc.getLocation() } + } + + /** + * An instruction computing the implicit lower slice bound of zero in a slice expression without + * an explicit lower bound. + */ + class EvalImplicitLowerSliceBoundInstruction extends Instruction, MkImplicitLowerSliceBound { + SliceExpr slice; + + EvalImplicitLowerSliceBoundInstruction() { this = MkImplicitLowerSliceBound(slice) } + + override Type getResultType() { result instanceof IntType } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override int getIntValue() { result = 0 } + + override string getExactValue() { result = "0" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "0" } + + override Location getLocation() { result = slice.getLocation() } + } + + /** + * An instruction computing the implicit upper slice bound in a slice expression without an + * explicit upper bound. + */ + class EvalImplicitUpperSliceBoundInstruction extends Instruction, MkImplicitUpperSliceBound { + SliceExpr slice; + + EvalImplicitUpperSliceBoundInstruction() { this = MkImplicitUpperSliceBound(slice) } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override Type getResultType() { result instanceof IntType } + + override string toString() { result = "len" } + + override Location getLocation() { result = slice.getLocation() } + } + + /** + * An instruction computing the implicit maximum slice bound in a slice expression without an + * explicit maximum bound. + */ + class EvalImplicitMaxSliceBoundInstruction extends Instruction, MkImplicitMaxSliceBound { + SliceExpr slice; + + EvalImplicitMaxSliceBoundInstruction() { this = MkImplicitMaxSliceBound(slice) } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override Type getResultType() { result instanceof IntType } + + override string toString() { result = "cap" } + + override Location getLocation() { result = slice.getLocation() } + } + + /** + * An instruction implicitly dereferencing the base in a field or method reference through a + * pointer, or the base in an element or slice reference through a pointer. + */ + class EvalImplicitDerefInstruction extends Instruction, MkImplicitDeref { + Expr e; + + EvalImplicitDerefInstruction() { this = MkImplicitDeref(e) } + + /** Gets the operand that is being dereferenced. */ + Expr getOperand() { result = e } + + override Type getResultType() { + result = e.getType().getUnderlyingType().(PointerType).getBaseType() + } + + override ControlFlow::Root getRoot() { result.isRootOf(e) } + + override string toString() { result = "implicit dereference" } + + override Location getLocation() { result = e.getLocation() } + } + + /** A representation of the target of a write instruction. */ + class WriteTarget extends TWriteTarget { + ControlFlow::Node w; + + WriteTarget() { + this = MkLhs(w, _) or this = MkLiteralElementTarget(w) or this = MkResultWriteTarget(w) + } + + /** Gets the write instruction of which this is the target. */ + WriteInstruction getWrite() { result = w } + + /** Gets the name of the variable or field being written to, if any. */ + string getName() { none() } + + /** Gets the SSA variable being written to, if any. */ + SsaVariable asSsaVariable() { + this.getWrite() = result.getDefinition().(SsaExplicitDefinition).getInstruction() + } + + /** Holds if `e` is the variable or field being written to. */ + predicate refersTo(ValueEntity e) { none() } + + /** Gets a textual representation of this target. */ + string toString() { result = "write target" } + + /** Gets the source location for this element. */ + Location getLocation() { none() } + + /** + * DEPRECATED: Use `getLocation()` instead. + * + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + deprecated predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + or + not exists(this.getLocation()) and + filepath = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } + } + + /** A reference to a variable or constant, used as the target of a write. */ + class VarOrConstTarget extends WriteTarget { + Expr loc; + + VarOrConstTarget() { + this = MkLhs(_, loc) and + ( + loc instanceof Ident + or + loc instanceof SelectorExpr and + not loc.(SelectorExpr).getBase() instanceof ReferenceExpr + ) + or + exists(WriteResultInstruction wr | + this = MkResultWriteTarget(wr) and + evalExprInstruction(loc) = wr.getRhs() + ) + } + + override predicate refersTo(ValueEntity e) { + this instanceof MkLhs and + pragma[only_bind_out](loc) = e.getAReference() + or + exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | + e = wr.getResultVariable() + ) + } + + override string getName() { + this = MkLhs(_, loc) and + ( + result = loc.(Ident).getName() + or + result = loc.(SelectorExpr).getSelector().getName() + ) + or + exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | + result = wr.getResultVariable().getName() + ) + } + + /** Gets the variable this refers to, if any. */ + Variable getVariable() { this.refersTo(result) } + + /** Gets the constant this refers to, if any. */ + Constant getConstant() { this.refersTo(result) } + + override string toString() { result = this.getName() } + + override Location getLocation() { result = loc.getLocation() } + } + + /** A reference to a field, used as the target of a write. */ + class FieldTarget extends WriteTarget { + FieldTarget() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.getBase() instanceof ValueExpr) + or + w instanceof InitLiteralStructFieldInstruction + } + + /** Gets the instruction computing the base value on which this field is accessed. */ + Instruction getBase() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | result = selectorBase(sel)) + or + result = w.(InitLiteralStructFieldInstruction).getBase() + } + + /** Get the type of the base of this field access, that is, the type that contains the field. */ + Type getBaseType() { result = this.getBase().getResultType() } + + override predicate refersTo(ValueEntity e) { + exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.uses(e)) + or + e = w.(InitLiteralStructFieldInstruction).getField() + } + + override string getName() { exists(Field f | this.refersTo(f) | result = f.getName()) } + + /** Gets the field this refers to, if it can be determined. */ + Field getField() { this.refersTo(result) } + + override string toString() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | + result = "field " + sel.getSelector().getName() + ) + or + result = "field " + w.(InitLiteralStructFieldInstruction).getFieldName() + } + + override Location getLocation() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | result = sel.getLocation()) + or + result = w.(InitLiteralStructFieldInstruction).getLocation() + } + } + + /** + * A reference to an element of an array, slice or map, used as the target of a write. + */ + class ElementTarget extends WriteTarget { + ElementTarget() { + this = MkLhs(_, any(IndexExpr idx)) + or + w instanceof InitLiteralElementInstruction + } + + /** Gets the instruction computing the base value of this element reference. */ + Instruction getBase() { + exists(IndexExpr idx | this = MkLhs(_, idx) | result = selectorBase(idx)) + or + result = w.(InitLiteralComponentInstruction).getBase() + } + + /** Gets the instruction computing the index of this element reference. */ + Instruction getIndex() { + exists(IndexExpr idx | this = MkLhs(_, idx) | result = evalExprInstruction(idx.getIndex())) + or + result = w.(InitLiteralElementInstruction).getIndex() + } + + override string toString() { result = "element" } + + override Location getLocation() { + exists(IndexExpr idx | this = MkLhs(_, idx) | result = idx.getLocation()) + or + result = w.(InitLiteralElementInstruction).getLocation() + } + } + + /** + * A pointer dereference, used as the target of a write. + */ + class PointerTarget extends WriteTarget { + Expr lhs; + + PointerTarget() { + this = MkLhs(_, lhs) and + (lhs instanceof StarExpr or lhs instanceof DerefExpr) + } + + /** Gets the instruction computing the pointer value being dereferenced. */ + Instruction getBase() { + exists(Expr base | base = lhs.(StarExpr).getBase() or base = lhs.(DerefExpr).getOperand() | + result = evalExprInstruction(base) + ) + } + + override string toString() { result = lhs.toString() } + + override Location getLocation() { result = lhs.getLocation() } + } + + /** + * Gets the (final) instruction computing the value of `e`. + * + * Note that some expressions (such as type expressions or labels) have no corresponding + * instruction, so this predicate is undefined for them. + * + * Short-circuiting expressions that are purely used for control flow (meaning that their + * value is not stored in a variable or used to compute the value of a non-shortcircuiting + * expression) do not have a final instruction either. + */ + Instruction evalExprInstruction(Expr e) { + result = MkExprNode(e) or + result = evalExprInstruction(e.(ParenExpr).getExpr()) + } + + /** + * Gets the instruction corresponding to the initialization of `r`. + */ + InitParameterInstruction initRecvInstruction(ReceiverVariable r) { result = MkParameterInit(r) } + + /** + * Gets the instruction corresponding to the initialization of `p`. + */ + InitParameterInstruction initParamInstruction(Parameter p) { result = MkParameterInit(p) } + + /** + * Gets the instruction corresponding to the `i`th assignment happening at + * `assgn` (0-based). + */ + AssignInstruction assignInstruction(Assignment assgn, int i) { result = MkAssignNode(assgn, i) } + + /** + * Gets the instruction corresponding to the `i`th initialization happening + * at `spec` (0-based). + */ + AssignInstruction initInstruction(ValueSpec spec, int i) { result = MkAssignNode(spec, i) } + + /** + * Gets the instruction corresponding to the assignment of the key variable + * of range statement `rs`. + */ + AssignInstruction assignKeyInstruction(RangeStmt rs) { result = MkAssignNode(rs, 0) } + + /** + * Gets the instruction corresponding to the assignment of the value variable + * of range statement `rs`. + */ + AssignInstruction assignValueInstruction(RangeStmt rs) { result = MkAssignNode(rs, 1) } + + /** + * Gets the instruction corresponding to the implicit initialization of `v` + * to its zero value. + */ + EvalImplicitInitInstruction implicitInitInstruction(ValueEntity v) { result = MkZeroInitNode(v) } + + /** + * Gets the instruction corresponding to the extraction of the `idx`th element + * of the tuple produced by `base`. + */ + ExtractTupleElementInstruction extractTupleElement(Instruction base, int idx) { + result.extractsElement(base, idx) + } + + /** + * Gets the instruction corresponding to the implicit lower bound of slice `e`, if any. + */ + EvalImplicitLowerSliceBoundInstruction implicitLowerSliceBoundInstruction(SliceExpr e) { + result = MkImplicitLowerSliceBound(e) + } + + /** + * Gets the instruction corresponding to the implicit upper bound of slice `e`, if any. + */ + EvalImplicitUpperSliceBoundInstruction implicitUpperSliceBoundInstruction(SliceExpr e) { + result = MkImplicitUpperSliceBound(e) + } + + /** + * Gets the instruction corresponding to the implicit maximum bound of slice `e`, if any. + */ + EvalImplicitMaxSliceBoundInstruction implicitMaxSliceBoundInstruction(SliceExpr e) { + result = MkImplicitMaxSliceBound(e) + } + + /** + * Gets the implicit dereference instruction for `e`, where `e` is a pointer used as the base + * in a field/method access, element access, or slice expression. + */ + EvalImplicitDerefInstruction implicitDerefInstruction(Expr e) { result = MkImplicitDeref(e) } + + /** Gets the base of `insn`, if `insn` is an implicit field read. */ + Instruction lookThroughImplicitFieldRead(Instruction insn) { + result = insn.(ImplicitFieldReadInstruction).getBaseInstruction() + } +} diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll.bak2 b/go/ql/lib/semmle/go/controlflow/IR.qll.bak2 new file mode 100644 index 000000000000..807fbf76d1fd --- /dev/null +++ b/go/ql/lib/semmle/go/controlflow/IR.qll.bak2 @@ -0,0 +1,1580 @@ +/** + * Provides classes and predicates for working with an intermediate representation (IR) of Go + * programs that is used as the foundation of the control flow and data flow graphs. + * + * In the IR, the program is represented as a set of instructions, which correspond to expressions + * and statements that compute a value or perform an operation (as opposed to providing syntactic + * structure or type information). + * + * Each instruction is also a control-flow node, but there are control-flow nodes that are not + * instructions (synthetic entry and exit nodes, as well as before/after nodes). + */ +overlay[local] +module; + +import go +private import ControlFlowGraphShared + +/** Provides predicates and classes for working with IR constructs. */ +module IR { + /** + * An IR instruction. + */ + class Instruction extends ControlFlow::Node { + Instruction() { + this.isIn(_) or + this.isAdditional(_, _) or + this.isAfterTrue(_) and not this.isIn(_) or + this.isAfterFalse(_) and not this.isIn(_) + } + + /** Holds if this instruction reads the value of variable or constant `v`. */ + predicate reads(ValueEntity v) { this.readsField(_, v) or this.readsMethod(_, v) } + + /** Holds if this instruction updates variable or constant `v` to the value of `rhs`. */ + predicate writes(ValueEntity v, Instruction rhs) { this.writesField(_, v, rhs) } + + /** Holds if this instruction reads the value of field `f` on the value of `base`. */ + predicate readsField(Instruction base, Field f) { none() } + + /** Holds if this instruction updates the value of field `f` on the value of `base`. */ + predicate writesField(Instruction base, Field f, Instruction rhs) { none() } + + /** Holds if this instruction looks up method `m` on the value of `receiver`. */ + predicate readsMethod(Instruction receiver, Method m) { none() } + + /** Holds if this instruction reads the value of element `index` on the value of `base`. */ + predicate readsElement(Instruction base, Instruction index) { none() } + + /** Holds if this instruction updates the value of element `index` on the value of `base`. */ + predicate writesElement(Instruction base, Instruction index) { none() } + + /** Gets the type of the result of this instruction, if any. */ + Type getResultType() { none() } + + /** Gets the float value of the result of this instruction, if it can be determined. */ + float getFloatValue() { none() } + + /** Gets the int value of the result of this instruction, if it can be determined. */ + int getIntValue() { none() } + + /** + * Holds if the complex value of the result of this instruction has real part `real` and + * imaginary part `imag`. + */ + predicate hasComplexValue(float real, float imag) { none() } + + /** Gets either `getFloatValue` or `getIntValue` */ + float getNumericValue() { result = this.getFloatValue() or result = this.getIntValue() } + + /** + * Gets the string representation of the exact value of the result of this instruction, + * if any. + * + * For example, for the constant 3.141592653589793238462, this will + * result in 1570796326794896619231/500000000000000000000 + */ + string getExactValue() { none() } + + /** Gets the string value of the result of this instruction, if it can be determined. */ + string getStringValue() { none() } + + /** Gets the Boolean value of the result of this instruction, if it can be determined. */ + boolean getBoolValue() { none() } + + /** Holds if the result of this instruction is known at compile time. */ + predicate isConst() { none() } + + /** + * Holds if the result of this instruction is known at compile time, and is guaranteed not to + * depend on the platform where it is evaluated. + */ + predicate isPlatformIndependentConstant() { none() } + + /** Gets a textual representation of the kind of this instruction. */ + string getInsnKind() { + this instanceof EvalInstruction and result = "expression" + or + this instanceof InitLiteralComponentInstruction and result = "element init" + or + this instanceof ImplicitLiteralElementIndexInstruction and result = "element index" + or + this instanceof AssignInstruction and result = "assignment" + or + this instanceof EvalCompoundAssignRhsInstruction and + result = "right-hand side of compound assignment" + or + this instanceof ExtractTupleElementInstruction and result = "tuple element extraction" + or + this instanceof EvalImplicitInitInstruction and result = "zero value" + or + this instanceof DeclareFunctionInstruction and result = "function declaration" + or + this instanceof DeferInstruction and result = "defer" + or + this instanceof GoInstruction and result = "go" + or + this instanceof ConditionGuardInstruction and result = "condition guard" + or + this instanceof IncDecInstruction and result = "increment/decrement" + or + this instanceof EvalIncDecRhsInstruction and + result = "right-hand side of increment/decrement" + or + this instanceof EvalImplicitOneInstruction and result = "implicit 1" + or + this instanceof ReturnInstruction and result = "return" + or + this instanceof WriteResultInstruction and result = "result write" + or + this instanceof ReadResultInstruction and result = "result read" + or + this instanceof SendInstruction and result = "send" + or + this instanceof InitParameterInstruction and result = "parameter initialization" + or + this instanceof ReadArgumentInstruction and result = "argument" + or + this instanceof InitResultInstruction and result = "result initialization" + or + this instanceof GetNextEntryInstruction and result = "next key-value pair" + or + this instanceof EvalImplicitTrueInstruction and result = "implicit true" + or + this instanceof CaseInstruction and result = "case" + or + this instanceof TypeSwitchImplicitVariableInstruction and + result = "type switch implicit variable declaration" + or + this instanceof EvalImplicitLowerSliceBoundInstruction and result = "implicit lower bound" + or + this instanceof EvalImplicitUpperSliceBoundInstruction and result = "implicit upper bound" + or + this instanceof EvalImplicitMaxSliceBoundInstruction and result = "implicit maximum" + or + this instanceof EvalImplicitDerefInstruction and result = "implicit dereference" + or + this instanceof ImplicitFieldReadInstruction and result = "implicit field selection" + } + } + + /** A condition guard instruction, representing a known boolean outcome for a condition. */ + private class ConditionGuardInstruction extends Instruction { + ConditionGuardInstruction() { + this.isAfterTrue(_) and not this.isIn(_) + or + this.isAfterFalse(_) and not this.isIn(_) + } + } + + /** + * An IR instruction representing the evaluation of an expression. + */ + class EvalInstruction extends Instruction { + Expr e; + + EvalInstruction() { this.isIn(e) and e instanceof Expr } + + /** Gets the expression underlying this instruction. */ + Expr getExpr() { result = e } + + override predicate reads(ValueEntity v) { e = v.getAReference() } + + override Type getResultType() { result = e.getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(e) } + + override float getFloatValue() { result = e.getFloatValue() } + + override int getIntValue() { result = e.getIntValue() } + + override predicate hasComplexValue(float real, float imag) { e.hasComplexValue(real, imag) } + + override string getExactValue() { result = e.getExactValue() } + + override string getStringValue() { result = e.getStringValue() } + + override boolean getBoolValue() { result = e.getBoolValue() } + + override predicate isConst() { e.isConst() } + + override predicate isPlatformIndependentConstant() { e.isPlatformIndependentConstant() } + + override string toString() { result = e.toString() } + + override Location getLocation() { result = e.getLocation() } + } + + /** + * An IR instruction that reads the value of a variable, constant, field or array element, + * or refers to a function. + */ + class ReadInstruction extends Instruction { + ReadInstruction() { + exists(Expr e | e = this.(EvalInstruction).getExpr() | + (e instanceof ValueName or e instanceof IndexExpr) and + e.(ReferenceExpr).isRvalue() + ) + or + this instanceof ReadResultInstruction + or + this instanceof ImplicitFieldReadInstruction + } + } + + /** + * Gets the effective base of a selector, index or slice expression, taking implicit dereferences + * and implicit field reads into account. + * + * For a selector expression `b.f`, this could be the implicit dereference `*b`, or the implicit + * field access `b.Embedded` if the field `f` is promoted from an embedded type `Embedded`, or a + * combination of both `*(b.Embedded)`, or simply `b` if neither case applies. + */ + private Instruction selectorBase(Expr e) { + exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1 | + result = fri + ) + or + not exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1) and + exists(Expr base | + base = e.(SelectorExpr).getBase() + or + base = e.(IndexExpr).getBase() + or + base = e.(SliceExpr).getBase() + | + result = MkImplicitDeref(base) + or + not exists(MkImplicitDeref(base)) and + result = evalExprInstruction(base) + ) + } + + /** + * An IR instruction that reads a component from a composite object. + * + * This is either a field of a struct, or an element of an array, map, slice or string. + */ + class ComponentReadInstruction extends ReadInstruction { + ComponentReadInstruction() { + exists(Expr e | e = this.(EvalInstruction).getExpr() | + e instanceof IndexExpr + or + e.(SelectorExpr).getBase() instanceof ValueExpr and + not e.(SelectorExpr).getSelector() = any(Method method).getAReference() + ) + or + this instanceof MkImplicitFieldSelection + } + + /** Gets the instruction computing the base value on which the field or element is read. */ + Instruction getBase() { + result = this.(ImplicitFieldReadInstruction).getBaseInstruction() + or + result = selectorBase(this.(EvalInstruction).getExpr()) + } + } + + /** + * An IR instruction that reads the value of a field. + * + * On databases with incomplete type information, method expressions may sometimes be + * misclassified as field reads. + */ + class FieldReadInstruction extends ComponentReadInstruction { + SelectorExpr e; + int index; + Field field; + + FieldReadInstruction() { + e = this.(EvalInstruction).getExpr() and + index = 0 and + field.getAReference() = e.getSelector() + or + this = MkImplicitFieldSelection(e, index, field) + } + + /** Gets the `SelectorExpr` of this field read. */ + SelectorExpr getSelectorExpr() { result = e } + + /** Gets the index of this field read. */ + int getIndex() { result = index } + + /** Gets the field being read. */ + Field getField() { result = field } + + Instruction getBaseInstruction() { + exists(ImplicitFieldReadInstruction fri | + fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) + | + result = fri + ) + or + not exists(ImplicitFieldReadInstruction fri | + fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) + ) and + ( + result = MkImplicitDeref(e.getBase()) + or + not exists(MkImplicitDeref(e.getBase())) and + result = evalExprInstruction(e.getBase()) + ) + } + + override predicate readsField(Instruction base, Field f) { + base = this.getBaseInstruction() and f = field + } + } + + /** + * An IR instruction for an implicit field read as part of reading a + * promoted field. + * + * If the field that is being implicitly read has a pointer type then this + * instruction represents an implicit dereference of it. + */ + class ImplicitFieldReadInstruction extends FieldReadInstruction, MkImplicitFieldSelection { + ImplicitFieldReadInstruction() { this = MkImplicitFieldSelection(e, index, field) } + + override predicate reads(ValueEntity v) { v = field } + + override Type getResultType() { result = lookThroughPointerType(field.getType()) } + + override ControlFlow::Root getRoot() { result.isRootOf(e) } + + override string toString() { result = "implicit read of field " + field.toString() } + + override Location getLocation() { result = e.getBase().getLocation() } + } + + /** + * An IR instruction that looks up a method. + */ + class MethodReadInstruction extends ReadInstruction, EvalInstruction { + Method method; + override SelectorExpr e; + + MethodReadInstruction() { e.getSelector() = method.getAReference() } + + /** Gets the instruction computing the receiver value on which the method is looked up. */ + Instruction getReceiver() { result = selectorBase(e) } + + /** Gets the method being looked up. */ + Method getMethod() { result = method } + + override predicate readsMethod(Instruction receiver, Method m) { + receiver = this.getReceiver() and m = this.getMethod() + } + } + + /** + * An IR instruction that reads an element of an array, slice, map or string. + */ + class ElementReadInstruction extends ComponentReadInstruction, EvalInstruction { + override IndexExpr e; + + /** Gets the instruction computing the index of the element being looked up. */ + Instruction getIndex() { result = evalExprInstruction(e.getIndex()) } + + override predicate readsElement(Instruction base, Instruction index) { + base = this.getBase() and index = this.getIndex() + } + } + + /** + * An IR instruction that constructs a slice. + */ + class SliceInstruction extends EvalInstruction { + override SliceExpr e; + + /** Gets the instruction computing the base value from which the slice is constructed. */ + Instruction getBase() { result = selectorBase(e) } + + /** Gets the instruction computing the lower bound of the slice. */ + Instruction getLow() { + result = evalExprInstruction(e.getLow()) or + result = implicitLowerSliceBoundInstruction(e) + } + + /** Gets the instruction computing the upper bound of the slice. */ + Instruction getHigh() { + result = evalExprInstruction(e.getHigh()) or + result = implicitUpperSliceBoundInstruction(e) + } + + /** Gets the instruction computing the capacity of the slice. */ + Instruction getMax() { + result = evalExprInstruction(e.getMax()) or + result = implicitMaxSliceBoundInstruction(e) + } + } + + /** + * An IR instruction that writes a memory location. + */ + class WriteInstruction extends Instruction { + WriteTarget lhs; + Boolean initialization; + + WriteInstruction() { + ( + lhs = MkLhs(this, _) + or + lhs = MkResultWriteTarget(this) + ) and + initialization = false + or + lhs = MkLiteralElementTarget(this) and initialization = true + } + + /** Gets the target to which this instruction writes. */ + WriteTarget getLhs() { result = lhs } + + /** Holds if this instruction initializes a literal. */ + predicate isInitialization() { initialization = true } + + /** Gets the instruction computing the value this instruction writes. */ + Instruction getRhs() { none() } + + override predicate writes(ValueEntity v, Instruction rhs) { + this.getLhs().refersTo(v) and + rhs = this.getRhs() + } + } + + /** + * An IR instruction that initializes a component of a composite literal. + */ + class InitLiteralComponentInstruction extends WriteInstruction, MkLiteralElementInitNode { + CompositeLit lit; + int i; + Expr elt; + + InitLiteralComponentInstruction() { + this = MkLiteralElementInitNode(elt) and elt = lit.getElement(i) + } + + /** Gets the instruction allocating the composite literal. */ + Instruction getBase() { result = evalExprInstruction(lit) } + + override Instruction getRhs() { + result = evalExprInstruction(elt) or + result = evalExprInstruction(elt.(KeyValueExpr).getValue()) + } + + override ControlFlow::Root getRoot() { result.isRootOf(elt) } + + override string toString() { result = "init of " + elt } + + override Location getLocation() { result = elt.getLocation() } + } + + /** + * An IR instruction that initializes a field of a struct literal. + */ + class InitLiteralStructFieldInstruction extends InitLiteralComponentInstruction { + override StructLit lit; + + /** Gets the name of the initialized field. */ + pragma[nomagic] + string getFieldName() { + if elt instanceof KeyValueExpr + then result = elt.(KeyValueExpr).getKey().(Ident).getName() + else pragma[only_bind_out](lit.getStructType()).hasOwnField(i, result, _, _) + } + + /** Gets the initialized field. */ + Field getField() { + result.getDeclaringType() = lit.getStructType() and + result.getName() = this.getFieldName() + } + } + + /** + * An IR instruction that initializes an element of an array, slice or map literal. + */ + class InitLiteralElementInstruction extends InitLiteralComponentInstruction { + Type literalType; + + InitLiteralElementInstruction() { + literalType = lit.getType().getUnderlyingType() and + ( + literalType instanceof ArrayType or + literalType instanceof SliceType or + literalType instanceof MapType + ) + } + + /** Gets the instruction computing the index of the initialized element. */ + Instruction getIndex() { + result = evalExprInstruction(elt.(KeyValueExpr).getKey()) + or + result = MkImplicitLiteralElementIndex(elt) + } + } + + /** + * An IR instruction that initializes an element of an array literal. + */ + class InitLiteralArrayElementInstruction extends InitLiteralElementInstruction { + override ArrayType literalType; + } + + /** + * An IR instruction that initializes an element of a slice literal. + */ + class InitLiteralSliceElementInstruction extends InitLiteralElementInstruction { + override SliceType literalType; + } + + /** + * An IR instruction that initializes an element of a map literal. + */ + class InitLiteralMapElementInstruction extends InitLiteralElementInstruction { + override MapType literalType; + } + + /** + * An IR instruction that writes to a field. + */ + class FieldWriteInstruction extends WriteInstruction { + override FieldTarget lhs; + + /** Gets the instruction computing the base value on which the field is written. */ + Instruction getBase() { result = lhs.getBase() } + + /** Gets the field being written. */ + Field getField() { result = lhs.getField() } + + override predicate writesField(Instruction base, Field f, Instruction rhs) { + this.getBase() = base and + this.getField() = f and + this.getRhs() = rhs + } + } + + /** + * An IR instruction that writes to an element of an array, slice, or map. + */ + class ElementWriteInstruction extends WriteInstruction { + override ElementTarget lhs; + + /** Gets the instruction computing the base value on which the field is written. */ + Instruction getBase() { result = lhs.getBase() } + + /** Gets the instruction computing the element index being written. */ + Instruction getIndex() { result = lhs.getIndex() } + + override predicate writesElement(Instruction base, Instruction index) { + this.getBase() = base and + this.getIndex() = index + } + } + + /** Holds if `lit` does not specify any explicit keys. */ + private predicate noExplicitKeys(CompositeLit lit) { + not lit.getAnElement() instanceof KeyValueExpr + } + + /** Gets the index of the `i`th element in (array or slice) literal `lit`. */ + private int getElementIndex(CompositeLit lit, int i) { + ( + lit.getType().getUnderlyingType() instanceof ArrayType or + lit.getType().getUnderlyingType() instanceof SliceType + ) and + exists(Expr elt | elt = lit.getElement(i) | + // short-circuit computation for literals without any explicit keys + noExplicitKeys(lit) and result = i + or + result = elt.(KeyValueExpr).getKey().getIntValue() + or + not elt instanceof KeyValueExpr and + ( + i = 0 and result = 0 + or + result = getElementIndex(lit, i - 1) + 1 + ) + ) + } + + /** + * An IR instruction computing the implicit index of an element in an array or slice literal. + */ + class ImplicitLiteralElementIndexInstruction extends Instruction, MkImplicitLiteralElementIndex { + Expr elt; + + ImplicitLiteralElementIndexInstruction() { this = MkImplicitLiteralElementIndex(elt) } + + override Type getResultType() { result instanceof IntType } + + override ControlFlow::Root getRoot() { result.isRootOf(elt) } + + override int getIntValue() { + exists(CompositeLit lit, int i | elt = lit.getElement(i) | result = getElementIndex(lit, i)) + } + + override string getStringValue() { none() } + + override string getExactValue() { result = this.getIntValue().toString() } + + override predicate isPlatformIndependentConstant() { any() } + + override predicate isConst() { any() } + + override string toString() { result = "element index" } + + override Location getLocation() { result = elt.getLocation() } + } + + /** + * An instruction assigning to a variable or field. + */ + class AssignInstruction extends WriteInstruction, MkAssignNode { + AstNode assgn; + int i; + + AssignInstruction() { this = MkAssignNode(assgn, i) } + + override Instruction getRhs() { + exists(SimpleAssignStmt a | a = assgn | + a.getNumLhs() = a.getNumRhs() and + result = evalExprInstruction(a.getRhs(i)) + ) + or + exists(ValueSpec spec | spec = assgn | + spec.getNumName() = spec.getNumInit() and + result = evalExprInstruction(spec.getInit(i)) + or + result = MkZeroInitNode(any(ValueEntity v | spec.getNameExpr(i) = v.getDeclaration())) + ) + or + result = MkCompoundAssignRhsNode(assgn) + or + result = MkExtractNode(assgn, i) + } + + override ControlFlow::Root getRoot() { result.isRootOf(assgn) } + + override string toString() { result = "assignment to " + this.getLhs() } + + override Location getLocation() { result = this.getLhs().getLocation() } + } + + /** An instruction computing the value of the right-hand side of a compound assignment. */ + class EvalCompoundAssignRhsInstruction extends Instruction, MkCompoundAssignRhsNode { + CompoundAssignStmt assgn; + + EvalCompoundAssignRhsInstruction() { this = MkCompoundAssignRhsNode(assgn) } + + /** Gets the underlying assignment of this instruction. */ + CompoundAssignStmt getAssignment() { result = assgn } + + override Type getResultType() { result = assgn.getRhs().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(assgn) } + + override string toString() { result = assgn.toString() } + + override Location getLocation() { result = assgn.getLocation() } + } + + /** + * An instruction selecting one of multiple values returned by a function, or either the key + * or the value of the iterator in a range loop, or the result or success value from a type + * assertion. + */ + class ExtractTupleElementInstruction extends Instruction, MkExtractNode { + AstNode s; + int i; + + ExtractTupleElementInstruction() { this = MkExtractNode(s, i) } + + /** Gets the instruction computing the tuple value from which one value is extracted. */ + Instruction getBase() { + exists(Expr baseExpr | + baseExpr = s.(Assignment).getRhs() or + baseExpr = s.(ValueSpec).getInit() + | + result = evalExprInstruction(baseExpr) + ) + or + result = MkNextNode(s) + or + result = evalExprInstruction(s.(ReturnStmt).getExpr()) + or + result = evalExprInstruction(s.(CallExpr).getArgument(0).stripParens()) + } + + /** Holds if this extracts the `idx`th value of the result of `base`. */ + predicate extractsElement(Instruction base, int idx) { base = this.getBase() and idx = i } + + override Type getResultType() { + exists(Expr e | this.getBase() = evalExprInstruction(e) | + result = e.getType().(TupleType).getComponentType(pragma[only_bind_into](i)) + ) + or + exists(Type rangeType | rangeType = s.(RangeStmt).getDomain().getType().getUnderlyingType() | + exists(Type baseType | + baseType = rangeType.(ArrayType).getElementType() or + baseType = + rangeType.(PointerType).getBaseType().getUnderlyingType().(ArrayType).getElementType() or + baseType = rangeType.(SliceType).getElementType() + | + i = 0 and + result instanceof IntType + or + i = 1 and + result = baseType + ) + or + rangeType instanceof StringType and + ( + i = 0 and + result instanceof IntType + or + result = Builtin::rune().getType() + ) + or + exists(MapType map | map = rangeType | + i = 0 and + result = map.getKeyType() + or + i = 1 and + result = map.getValueType() + ) + or + i = 0 and + result = rangeType.(RecvChanType).getElementType() + or + i = 0 and + result = rangeType.(SendRecvChanType).getElementType() + ) + } + + override ControlFlow::Root getRoot() { result.isRootOf(s) } + + override string toString() { result = s + "[" + i + "]" } + + override Location getLocation() { result = s.getLocation() } + } + + /** + * An instruction that computes the zero value for a variable or constant. + */ + class EvalImplicitInitInstruction extends Instruction, MkZeroInitNode { + ValueEntity v; + + EvalImplicitInitInstruction() { this = MkZeroInitNode(v) } + + override Type getResultType() { result = v.getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(v.getDeclaration()) } + + override int getIntValue() { + v.getType().getUnderlyingType() instanceof IntegerType and result = 0 + } + + override float getFloatValue() { + v.getType().getUnderlyingType() instanceof FloatType and result = 0.0 + } + + override string getStringValue() { + v.getType().getUnderlyingType() instanceof StringType and result = "" + } + + override boolean getBoolValue() { + v.getType().getUnderlyingType() instanceof BoolType and result = false + } + + override string getExactValue() { + result = this.getIntValue().toString() or + result = this.getFloatValue().toString() or + result = this.getStringValue().toString() or + result = this.getBoolValue().toString() + } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "zero value for " + v } + + override Location getLocation() { result = v.getDeclaration().getLocation() } + } + + /** + * An instruction that corresponds to the declaration of a function. + */ + class DeclareFunctionInstruction extends Instruction, MkFuncDeclNode { + FuncDecl fd; + + DeclareFunctionInstruction() { this = MkFuncDeclNode(fd) } + + override Type getResultType() { result = fd.getType() } + + override string toString() { result = fd.toString() } + + override Location getLocation() { result = fd.getLocation() } + } + + /** + * An instruction that corresponds to a `defer` statement. + */ + class DeferInstruction extends Instruction, MkDeferNode { + DeferStmt defer; + + DeferInstruction() { this = MkDeferNode(defer) } + + override ControlFlow::Root getRoot() { result.isRootOf(defer) } + + override string toString() { result = defer.toString() } + + override Location getLocation() { result = defer.getLocation() } + } + + /** + * An instruction that corresponds to a `go` statement. + */ + class GoInstruction extends Instruction, MkGoNode { + GoStmt go; + + GoInstruction() { this = MkGoNode(go) } + + override ControlFlow::Root getRoot() { result.isRootOf(go) } + + override string toString() { result = go.toString() } + + override Location getLocation() { result = go.getLocation() } + } + + /** + * An instruction that corresponds to an increment or decrement statement. + */ + class IncDecInstruction extends WriteInstruction, MkIncDecNode { + IncDecStmt ids; + + IncDecInstruction() { this = MkIncDecNode(ids) } + + override Instruction getRhs() { result = MkIncDecRhs(ids) } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override string toString() { result = ids.toString() } + + override Location getLocation() { result = ids.getLocation() } + } + + /** + * An instruction that computes the (implicit) right-hand side of an increment or + * decrement statement. + */ + class EvalIncDecRhsInstruction extends Instruction, MkIncDecRhs { + IncDecStmt ids; + + EvalIncDecRhsInstruction() { this = MkIncDecRhs(ids) } + + /** Gets the corresponding increment or decrement statement. */ + IncDecStmt getStmt() { result = ids } + + override Type getResultType() { result = ids.getOperand().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override string toString() { result = "rhs of " + ids } + + override Location getLocation() { result = ids.getLocation() } + } + + /** + * An instruction computing the implicit operand `1` in an increment or decrement statement. + */ + class EvalImplicitOneInstruction extends Instruction, MkImplicitOne { + IncDecStmt ids; + + EvalImplicitOneInstruction() { this = MkImplicitOne(ids) } + + /** Gets the corresponding increment or decrement statement. */ + IncDecStmt getStmt() { result = ids } + + override Type getResultType() { result = ids.getOperand().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override int getIntValue() { result = 1 } + + override string getExactValue() { result = "1" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "1" } + + override Location getLocation() { result = ids.getLocation() } + } + + /** + * An instruction corresponding to a return from a function. + */ + class ReturnInstruction extends Instruction, MkReturnNode { + ReturnStmt ret; + + ReturnInstruction() { this = MkReturnNode(ret) } + + /** Gets the corresponding `ReturnStmt`. */ + ReturnStmt getReturnStmt() { result = ret } + + /** Holds if this statement returns multiple results. */ + predicate returnsMultipleResults() { exists(MkExtractNode(ret, _)) or ret.getNumExpr() > 1 } + + /** Gets the instruction whose result is the (unique) result returned by this statement. */ + Instruction getResult() { + not this.returnsMultipleResults() and + result = evalExprInstruction(ret.getExpr()) + } + + /** Gets the instruction whose result is the `i`th result returned by this statement. */ + Instruction getResult(int i) { + result = MkExtractNode(ret, i) + or + not exists(MkExtractNode(ret, _)) and + result = evalExprInstruction(ret.getExpr(i)) + } + + override ControlFlow::Root getRoot() { result.isRootOf(ret) } + + override string toString() { result = ret.toString() } + + override Location getLocation() { result = ret.getLocation() } + } + + /** + * An instruction that represents the implicit assignment to a result variable + * performed by a return statement. + */ + class WriteResultInstruction extends WriteInstruction, MkResultWriteNode { + ResultVariable var; + int i; + ReturnInstruction ret; + + WriteResultInstruction() { + exists(ReturnStmt retstmt | + this = MkResultWriteNode(var, i, retstmt) and + ret = MkReturnNode(retstmt) + ) + } + + override Instruction getRhs() { result = ret.getResult(i) } + + /** Gets the result variable being assigned. */ + ResultVariable getResultVariable() { result = var } + + override Type getResultType() { result = var.getType() } + + override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } + + override string toString() { result = "implicit write of " + var } + + override Location getLocation() { result = ret.getResult(i).getLocation() } + } + + /** + * An instruction that reads the final value of a result variable upon returning + * from a function. + */ + class ReadResultInstruction extends Instruction, MkResultReadNode { + ResultVariable var; + + ReadResultInstruction() { this = MkResultReadNode(var) } + + override predicate reads(ValueEntity v) { v = var } + + override Type getResultType() { result = var.getType() } + + override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } + + override string toString() { result = "implicit read of " + var } + + override Location getLocation() { result = var.getDeclaration().getLocation() } + } + + /** + * An instruction corresponding to a `select` statement. + */ + class SelectInstruction extends Instruction, MkSelectNode { + SelectStmt sel; + + SelectInstruction() { this = MkSelectNode(sel) } + + override ControlFlow::Root getRoot() { result.isRootOf(sel) } + + override string toString() { result = sel.toString() } + + override Location getLocation() { result = sel.getLocation() } + } + + /** + * An instruction corresponding to a send statement. + */ + class SendInstruction extends Instruction, MkSendNode { + SendStmt send; + + SendInstruction() { this = MkSendNode(send) } + + override ControlFlow::Root getRoot() { result.isRootOf(send) } + + override string toString() { result = send.toString() } + + override Location getLocation() { result = send.getLocation() } + } + + /** + * An instruction initializing a parameter to the corresponding argument. + */ + class InitParameterInstruction extends WriteInstruction, MkParameterInit { + Parameter parm; + + InitParameterInstruction() { this = MkParameterInit(parm) } + + override Instruction getRhs() { result = MkArgumentNode(parm) } + + override ControlFlow::Root getRoot() { result = parm.getFunction() } + + override string toString() { result = "initialization of " + parm } + + override Location getLocation() { result = parm.getDeclaration().getLocation() } + } + + /** + * An instruction reading the value of a function argument. + */ + class ReadArgumentInstruction extends Instruction, MkArgumentNode { + Parameter parm; + + ReadArgumentInstruction() { this = MkArgumentNode(parm) } + + override Type getResultType() { result = parm.getType() } + + override ControlFlow::Root getRoot() { result = parm.getFunction() } + + override string toString() { result = "argument corresponding to " + parm } + + override Location getLocation() { result = parm.getDeclaration().getLocation() } + } + + /** + * An instruction initializing a result variable to its zero value. + */ + class InitResultInstruction extends WriteInstruction, MkResultInit { + ResultVariable res; + + InitResultInstruction() { this = MkResultInit(res) } + + override Instruction getRhs() { result = MkZeroInitNode(res) } + + override ControlFlow::Root getRoot() { result = res.getFunction() } + + override string toString() { result = "initialization of " + res } + + override Location getLocation() { result = res.getDeclaration().getLocation() } + } + + /** + * An instruction that gets the next key-value pair in a range loop. + */ + class GetNextEntryInstruction extends Instruction, MkNextNode { + RangeStmt rs; + + GetNextEntryInstruction() { this = MkNextNode(rs) } + + /** + * Gets the instruction computing the value whose key-value pairs this instruction reads. + */ + Instruction getDomain() { result = evalExprInstruction(rs.getDomain()) } + + override ControlFlow::Root getRoot() { result.isRootOf(rs) } + + override string toString() { result = "next key-value pair in range" } + + override Location getLocation() { result = rs.getDomain().getLocation() } + } + + /** + * An instruction computing the implicit `true` value in an expression-less `switch` statement. + */ + class EvalImplicitTrueInstruction extends Instruction, MkImplicitTrue { + Stmt stmt; + + EvalImplicitTrueInstruction() { this = MkImplicitTrue(stmt) } + + override Type getResultType() { result instanceof BoolType } + + override ControlFlow::Root getRoot() { result.isRootOf(stmt) } + + override boolean getBoolValue() { result = true } + + override string getExactValue() { result = "true" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "true" } + + override Location getLocation() { result = stmt.getLocation() } + } + + /** + * An instruction corresponding to the implicit comparison or type check performed by an + * expression in a `case` clause. + * + * For example, consider this `switch` statement: + * + * ```go + * switch x { + * case 2, y+1: + * ... + * } + * ``` + * + * The expressions `2` and `y+1` are implicitly compared to `x`. These comparisons are + * represented by case instructions. + */ + class CaseInstruction extends Instruction, MkCaseCheckNode { + CaseClause cc; + int i; + + CaseInstruction() { this = MkCaseCheckNode(cc, i) } + + override ControlFlow::Root getRoot() { result.isRootOf(cc) } + + override string toString() { result = "case " + cc.getExpr(i) } + + override Location getLocation() { result = cc.getExpr(i).getLocation() } + } + + /** + * An instruction corresponding to the implicit declaration of the variable + * `lv` in case clause `cc` and its assignment of the value `switchExpr` from + * the guard. This only occurs in case clauses in a type switch statement + * which declares a variable in its guard. + * + * For example, consider this type switch statement: + * + * ```go + * switch y := x.(type) { + * case Type1: + * f(y) + * ... + * } + * ``` + * + * The `y` inside the case clause is actually a local variable with type + * `Type1` that is implicitly declared at the top of the case clause. In + * default clauses and case clauses which list more than one type, the type + * of the implicitly declared variable is the type of `switchExpr`. + */ + class TypeSwitchImplicitVariableInstruction extends Instruction, MkTypeSwitchImplicitVariable { + CaseClause cc; + LocalVariable lv; + Expr switchExpr; + + TypeSwitchImplicitVariableInstruction() { + this = MkTypeSwitchImplicitVariable(cc, lv, switchExpr) + } + + override predicate writes(ValueEntity v, Instruction rhs) { + v = lv and + rhs = evalExprInstruction(switchExpr) + } + + override ControlFlow::Root getRoot() { result.isRootOf(cc) } + + override string toString() { result = "implicit type switch variable declaration" } + + override Location getLocation() { result = cc.getLocation() } + } + + /** + * An instruction computing the implicit lower slice bound of zero in a slice expression without + * an explicit lower bound. + */ + class EvalImplicitLowerSliceBoundInstruction extends Instruction, MkImplicitLowerSliceBound { + SliceExpr slice; + + EvalImplicitLowerSliceBoundInstruction() { this = MkImplicitLowerSliceBound(slice) } + + override Type getResultType() { result instanceof IntType } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override int getIntValue() { result = 0 } + + override string getExactValue() { result = "0" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "0" } + + override Location getLocation() { result = slice.getLocation() } + } + + /** + * An instruction computing the implicit upper slice bound in a slice expression without an + * explicit upper bound. + */ + class EvalImplicitUpperSliceBoundInstruction extends Instruction, MkImplicitUpperSliceBound { + SliceExpr slice; + + EvalImplicitUpperSliceBoundInstruction() { this = MkImplicitUpperSliceBound(slice) } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override Type getResultType() { result instanceof IntType } + + override string toString() { result = "len" } + + override Location getLocation() { result = slice.getLocation() } + } + + /** + * An instruction computing the implicit maximum slice bound in a slice expression without an + * explicit maximum bound. + */ + class EvalImplicitMaxSliceBoundInstruction extends Instruction, MkImplicitMaxSliceBound { + SliceExpr slice; + + EvalImplicitMaxSliceBoundInstruction() { this = MkImplicitMaxSliceBound(slice) } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override Type getResultType() { result instanceof IntType } + + override string toString() { result = "cap" } + + override Location getLocation() { result = slice.getLocation() } + } + + /** + * An instruction implicitly dereferencing the base in a field or method reference through a + * pointer, or the base in an element or slice reference through a pointer. + */ + class EvalImplicitDerefInstruction extends Instruction, MkImplicitDeref { + Expr e; + + EvalImplicitDerefInstruction() { this = MkImplicitDeref(e) } + + /** Gets the operand that is being dereferenced. */ + Expr getOperand() { result = e } + + override Type getResultType() { + result = e.getType().getUnderlyingType().(PointerType).getBaseType() + } + + override ControlFlow::Root getRoot() { result.isRootOf(e) } + + override string toString() { result = "implicit dereference" } + + override Location getLocation() { result = e.getLocation() } + } + + /** A representation of the target of a write instruction. */ + class WriteTarget extends TWriteTarget { + ControlFlow::Node w; + + WriteTarget() { + this = MkLhs(w, _) or this = MkLiteralElementTarget(w) or this = MkResultWriteTarget(w) + } + + /** Gets the write instruction of which this is the target. */ + WriteInstruction getWrite() { result = w } + + /** Gets the name of the variable or field being written to, if any. */ + string getName() { none() } + + /** Gets the SSA variable being written to, if any. */ + SsaVariable asSsaVariable() { + this.getWrite() = result.getDefinition().(SsaExplicitDefinition).getInstruction() + } + + /** Holds if `e` is the variable or field being written to. */ + predicate refersTo(ValueEntity e) { none() } + + /** Gets a textual representation of this target. */ + string toString() { result = "write target" } + + /** Gets the source location for this element. */ + Location getLocation() { none() } + + /** + * DEPRECATED: Use `getLocation()` instead. + * + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + deprecated predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + or + not exists(this.getLocation()) and + filepath = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } + } + + /** A reference to a variable or constant, used as the target of a write. */ + class VarOrConstTarget extends WriteTarget { + Expr loc; + + VarOrConstTarget() { + this = MkLhs(_, loc) and + ( + loc instanceof Ident + or + loc instanceof SelectorExpr and + not loc.(SelectorExpr).getBase() instanceof ReferenceExpr + ) + or + exists(WriteResultInstruction wr | + this = MkResultWriteTarget(wr) and + evalExprInstruction(loc) = wr.getRhs() + ) + } + + override predicate refersTo(ValueEntity e) { + this instanceof MkLhs and + pragma[only_bind_out](loc) = e.getAReference() + or + exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | + e = wr.getResultVariable() + ) + } + + override string getName() { + this = MkLhs(_, loc) and + ( + result = loc.(Ident).getName() + or + result = loc.(SelectorExpr).getSelector().getName() + ) + or + exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | + result = wr.getResultVariable().getName() + ) + } + + /** Gets the variable this refers to, if any. */ + Variable getVariable() { this.refersTo(result) } + + /** Gets the constant this refers to, if any. */ + Constant getConstant() { this.refersTo(result) } + + override string toString() { result = this.getName() } + + override Location getLocation() { result = loc.getLocation() } + } + + /** A reference to a field, used as the target of a write. */ + class FieldTarget extends WriteTarget { + FieldTarget() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.getBase() instanceof ValueExpr) + or + w instanceof InitLiteralStructFieldInstruction + } + + /** Gets the instruction computing the base value on which this field is accessed. */ + Instruction getBase() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | result = selectorBase(sel)) + or + result = w.(InitLiteralStructFieldInstruction).getBase() + } + + /** Get the type of the base of this field access, that is, the type that contains the field. */ + Type getBaseType() { result = this.getBase().getResultType() } + + override predicate refersTo(ValueEntity e) { + exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.uses(e)) + or + e = w.(InitLiteralStructFieldInstruction).getField() + } + + override string getName() { exists(Field f | this.refersTo(f) | result = f.getName()) } + + /** Gets the field this refers to, if it can be determined. */ + Field getField() { this.refersTo(result) } + + override string toString() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | + result = "field " + sel.getSelector().getName() + ) + or + result = "field " + w.(InitLiteralStructFieldInstruction).getFieldName() + } + + override Location getLocation() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | result = sel.getLocation()) + or + result = w.(InitLiteralStructFieldInstruction).getLocation() + } + } + + /** + * A reference to an element of an array, slice or map, used as the target of a write. + */ + class ElementTarget extends WriteTarget { + ElementTarget() { + this = MkLhs(_, any(IndexExpr idx)) + or + w instanceof InitLiteralElementInstruction + } + + /** Gets the instruction computing the base value of this element reference. */ + Instruction getBase() { + exists(IndexExpr idx | this = MkLhs(_, idx) | result = selectorBase(idx)) + or + result = w.(InitLiteralComponentInstruction).getBase() + } + + /** Gets the instruction computing the index of this element reference. */ + Instruction getIndex() { + exists(IndexExpr idx | this = MkLhs(_, idx) | result = evalExprInstruction(idx.getIndex())) + or + result = w.(InitLiteralElementInstruction).getIndex() + } + + override string toString() { result = "element" } + + override Location getLocation() { + exists(IndexExpr idx | this = MkLhs(_, idx) | result = idx.getLocation()) + or + result = w.(InitLiteralElementInstruction).getLocation() + } + } + + /** + * A pointer dereference, used as the target of a write. + */ + class PointerTarget extends WriteTarget { + Expr lhs; + + PointerTarget() { + this = MkLhs(_, lhs) and + (lhs instanceof StarExpr or lhs instanceof DerefExpr) + } + + /** Gets the instruction computing the pointer value being dereferenced. */ + Instruction getBase() { + exists(Expr base | base = lhs.(StarExpr).getBase() or base = lhs.(DerefExpr).getOperand() | + result = evalExprInstruction(base) + ) + } + + override string toString() { result = lhs.toString() } + + override Location getLocation() { result = lhs.getLocation() } + } + + /** + * Gets the (final) instruction computing the value of `e`. + * + * Note that some expressions (such as type expressions or labels) have no corresponding + * instruction, so this predicate is undefined for them. + * + * Short-circuiting expressions that are purely used for control flow (meaning that their + * value is not stored in a variable or used to compute the value of a non-shortcircuiting + * expression) do not have a final instruction either. + */ + Instruction evalExprInstruction(Expr e) { + result = MkExprNode(e) or + result = evalExprInstruction(e.(ParenExpr).getExpr()) + } + + /** + * Gets the instruction corresponding to the initialization of `r`. + */ + InitParameterInstruction initRecvInstruction(ReceiverVariable r) { result = MkParameterInit(r) } + + /** + * Gets the instruction corresponding to the initialization of `p`. + */ + InitParameterInstruction initParamInstruction(Parameter p) { result = MkParameterInit(p) } + + /** + * Gets the instruction corresponding to the `i`th assignment happening at + * `assgn` (0-based). + */ + AssignInstruction assignInstruction(Assignment assgn, int i) { result = MkAssignNode(assgn, i) } + + /** + * Gets the instruction corresponding to the `i`th initialization happening + * at `spec` (0-based). + */ + AssignInstruction initInstruction(ValueSpec spec, int i) { result = MkAssignNode(spec, i) } + + /** + * Gets the instruction corresponding to the assignment of the key variable + * of range statement `rs`. + */ + AssignInstruction assignKeyInstruction(RangeStmt rs) { result = MkAssignNode(rs, 0) } + + /** + * Gets the instruction corresponding to the assignment of the value variable + * of range statement `rs`. + */ + AssignInstruction assignValueInstruction(RangeStmt rs) { result = MkAssignNode(rs, 1) } + + /** + * Gets the instruction corresponding to the implicit initialization of `v` + * to its zero value. + */ + EvalImplicitInitInstruction implicitInitInstruction(ValueEntity v) { result = MkZeroInitNode(v) } + + /** + * Gets the instruction corresponding to the extraction of the `idx`th element + * of the tuple produced by `base`. + */ + ExtractTupleElementInstruction extractTupleElement(Instruction base, int idx) { + result.extractsElement(base, idx) + } + + /** + * Gets the instruction corresponding to the implicit lower bound of slice `e`, if any. + */ + EvalImplicitLowerSliceBoundInstruction implicitLowerSliceBoundInstruction(SliceExpr e) { + result = MkImplicitLowerSliceBound(e) + } + + /** + * Gets the instruction corresponding to the implicit upper bound of slice `e`, if any. + */ + EvalImplicitUpperSliceBoundInstruction implicitUpperSliceBoundInstruction(SliceExpr e) { + result = MkImplicitUpperSliceBound(e) + } + + /** + * Gets the instruction corresponding to the implicit maximum bound of slice `e`, if any. + */ + EvalImplicitMaxSliceBoundInstruction implicitMaxSliceBoundInstruction(SliceExpr e) { + result = MkImplicitMaxSliceBound(e) + } + + /** + * Gets the implicit dereference instruction for `e`, where `e` is a pointer used as the base + * in a field/method access, element access, or slice expression. + */ + EvalImplicitDerefInstruction implicitDerefInstruction(Expr e) { result = MkImplicitDeref(e) } + + /** Gets the base of `insn`, if `insn` is an implicit field read. */ + Instruction lookThroughImplicitFieldRead(Instruction insn) { + result = insn.(ImplicitFieldReadInstruction).getBaseInstruction() + } +} diff --git a/go/ql/lib/semmle/go/dataflow/SsaImpl.qll b/go/ql/lib/semmle/go/dataflow/SsaImpl.qll index 9648335a6dde..8a66103ba5e9 100644 --- a/go/ql/lib/semmle/go/dataflow/SsaImpl.qll +++ b/go/ql/lib/semmle/go/dataflow/SsaImpl.qll @@ -89,7 +89,7 @@ private module Internal { /** Holds if the `i`th node of `bb` in function `f` is an entry node. */ private predicate entryNode(FuncDef f, ReachableBasicBlock bb, int i) { f = bb.getScope() and - bb.getNode(i).isEntryNode() + bb.getNode(i).(ControlFlow::Node).isEntryNode() } /** diff --git a/go/ql/lib/semmle/go/frameworks/Revel.qll b/go/ql/lib/semmle/go/frameworks/Revel.qll index c6250c2f8a51..085db1d2b0a8 100644 --- a/go/ql/lib/semmle/go/frameworks/Revel.qll +++ b/go/ql/lib/semmle/go/frameworks/Revel.qll @@ -154,7 +154,7 @@ module Revel { private IR::EvalInstruction skipImplicitFieldReads(IR::Instruction insn) { result = insn or - result = skipImplicitFieldReads(insn.(IR::ImplicitFieldReadInstruction).getBase()) + result = skipImplicitFieldReads(insn.(IR::ImplicitFieldReadInstruction).getBaseInstruction()) } /** A call to `Controller.Render`. */ From 2bdbac9abe6cf5773e41146683a023496199e01d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:29:14 +0000 Subject: [PATCH 05/34] Continue shared CFG library migration: fix prologue/epilogue, implicit fields, cleanup Agent-Logs-Url: https://github.com/github/codeql/sessions/011f8465-5ad3-44e2-9d84-f5314ea1afb2 Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com> --- .../2026-03-30-shared-cfg-library.md | 4 + .../go/controlflow/ControlFlowGraphImpl.qll | 2133 ----------------- .../go/controlflow/ControlFlowGraphShared.qll | 149 +- go/ql/lib/semmle/go/controlflow/IR.qll.bak | 1580 ------------ go/ql/lib/semmle/go/controlflow/IR.qll.bak2 | 1580 ------------ 5 files changed, 146 insertions(+), 5300 deletions(-) create mode 100644 go/ql/lib/change-notes/2026-03-30-shared-cfg-library.md delete mode 100644 go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll delete mode 100644 go/ql/lib/semmle/go/controlflow/IR.qll.bak delete mode 100644 go/ql/lib/semmle/go/controlflow/IR.qll.bak2 diff --git a/go/ql/lib/change-notes/2026-03-30-shared-cfg-library.md b/go/ql/lib/change-notes/2026-03-30-shared-cfg-library.md new file mode 100644 index 000000000000..896f23a8a14d --- /dev/null +++ b/go/ql/lib/change-notes/2026-03-30-shared-cfg-library.md @@ -0,0 +1,4 @@ +--- +category: fix +--- +* The Go control flow graph implementation has been migrated to use the shared CFG library. This is an internal change with no user-visible API changes. diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll deleted file mode 100644 index a26ab3adaf5f..000000000000 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphImpl.qll +++ /dev/null @@ -1,2133 +0,0 @@ -/** - * INTERNAL: Analyses should use module `ControlFlowGraph` instead. - * - * Provides predicates for building intra-procedural CFGs. - */ -overlay[local] -module; - -import go - -/** A block statement that is not the body of a `switch` or `select` statement. */ -class PlainBlock extends BlockStmt { - PlainBlock() { - not this = any(SwitchStmt sw).getBody() and not this = any(SelectStmt sel).getBody() - } -} - -private predicate notBlankIdent(Expr e) { not e instanceof BlankIdent } - -private predicate pureLvalue(ReferenceExpr e) { not e.isRvalue() } - -/** - * Holds if `e` is a branch condition, including the LHS of a short-circuiting binary operator. - */ -private predicate isCondRoot(Expr e) { - e = any(LogicalBinaryExpr lbe).getLeftOperand() - or - e = any(ForStmt fs).getCond() - or - e = any(IfStmt is).getCond() - or - e = any(ExpressionSwitchStmt ess | not exists(ess.getExpr())).getACase().getAnExpr() -} - -/** - * Holds if `e` is a branch condition or part of a logical binary expression contributing to a - * branch condition. - * - * For example, in `v := (x && y) || (z && w)`, `x` and `(x && y)` and `z` are branch conditions - * (`isCondRoot` holds of them), whereas this predicate also holds of `y` (contributes to condition - * `x && y`) but not of `w` (contributes to the value `v`, but not to any branch condition). - * - * In the context `if (x && y) || (z && w)` then the whole `(x && y) || (z && w)` is a branch condition - * as well as `x` and `(x && y)` and `z` as previously, and this predicate holds of all their - * subexpressions. - */ -private predicate isCond(Expr e) { - isCondRoot(e) or - e = any(LogicalBinaryExpr lbe | isCond(lbe)).getRightOperand() or - e = any(ParenExpr par | isCond(par)).getExpr() -} - -/** - * Holds if `e` implicitly reads the embedded field `implicitField`. - * - * The `index` is the distance from the promoted field. For example, if `A` contains an embedded - * field `B`, `B` contains an embedded field `C` and `C` contains the non-embedded field `x`. - * Then `a.x` implicitly reads `C` with index 1 and `B` with index 2. - */ -private predicate implicitFieldSelectionForField(PromotedSelector e, int index, Field implicitField) { - exists(StructType baseType, PromotedField child, int implicitFieldDepth | - baseType = e.getSelectedStructType() and - ( - e.refersTo(child) - or - implicitFieldSelectionForField(e, implicitFieldDepth + 1, child) - ) - | - child = baseType.getFieldOfEmbedded(implicitField, _, implicitFieldDepth + 1, _) and - exists(PromotedField explicitField, int explicitFieldDepth | - e.refersTo(explicitField) and baseType.getFieldAtDepth(_, explicitFieldDepth) = explicitField - | - index = explicitFieldDepth - implicitFieldDepth - ) - ) -} - -private predicate implicitFieldSelectionForMethod(PromotedSelector e, int index, Field implicitField) { - exists(StructType baseType, PromotedMethod method, int mDepth, int implicitFieldDepth | - baseType = e.getSelectedStructType() and - e.refersTo(method) and - baseType.getMethodAtDepth(_, mDepth) = method and - index = mDepth - implicitFieldDepth - | - method = baseType.getMethodOfEmbedded(implicitField, _, implicitFieldDepth + 1) - or - exists(PromotedField child | - child = baseType.getFieldOfEmbedded(implicitField, _, implicitFieldDepth + 1, _) and - implicitFieldSelectionForMethod(e, implicitFieldDepth + 1, child) - ) - ) -} - -/** - * A node in the intra-procedural control-flow graph of a Go function or file. - * - * There are two kinds of control-flow nodes: - * - * 1. Instructions: these are nodes that correspond to expressions and statements - * that compute a value or perform an operation (as opposed to providing syntactic - * structure or type information). - * 2. Synthetic nodes: - * - Entry and exit nodes for each Go function and file that mark the beginning and the end, - * respectively, of the execution of the function and the loading of the file; - * - Skip nodes that are semantic no-ops, but make CFG construction easier. - */ -cached -newtype TControlFlowNode = - /** - * A control-flow node that represents the evaluation of an expression. - */ - MkExprNode(Expr e) { CFG::hasEvaluationNode(e) } or - /** - * A control-flow node that represents the initialization of an element of a composite literal. - */ - MkLiteralElementInitNode(Expr e) { e = any(CompositeLit lit).getAnElement() } or - /** - * A control-flow node that represents the implicit index of an element in a slice or array literal. - */ - MkImplicitLiteralElementIndex(Expr e) { - exists(CompositeLit lit | not lit instanceof StructLit | - e = lit.getAnElement() and - not e instanceof KeyValueExpr - ) - } or - /** - * A control-flow node that represents a (single) assignment. - * - * Assignments with multiple left-hand sides are split up into multiple assignment nodes, - * one for each left-hand side. Assignments to `_` are not represented in the control-flow graph. - */ - MkAssignNode(AstNode assgn, int i) { - // the `i`th assignment in a (possibly multi-)assignment - notBlankIdent(assgn.(Assignment).getLhs(i)) - or - // the `i`th name declared in a (possibly multi-)declaration specifier - notBlankIdent(assgn.(ValueSpec).getNameExpr(i)) - or - // the assignment to the "key" variable in a `range` statement - notBlankIdent(assgn.(RangeStmt).getKey()) and i = 0 - or - // the assignment to the "value" variable in a `range` statement - notBlankIdent(assgn.(RangeStmt).getValue()) and i = 1 - } or - /** - * A control-flow node that represents the implicit right-hand side of a compound assignment. - * - * For example, the compound assignment `x += 1` has an implicit right-hand side `x + 1`. - */ - MkCompoundAssignRhsNode(CompoundAssignStmt assgn) or - /** - * A control-flow node that represents the `i`th component of a tuple expression `s`. - */ - MkExtractNode(AstNode s, int i) { - // in an assignment `x, y, z = tuple` - exists(Assignment assgn | - s = assgn and - exists(assgn.getRhs()) and - assgn.getNumLhs() > 1 and - exists(assgn.getLhs(i)) - ) - or - // in a declaration `var x, y, z = tuple` - exists(ValueSpec spec | - s = spec and - exists(spec.getInit()) and - spec.getNumName() > 1 and - exists(spec.getNameExpr(i)) - ) - or - // in a `range` statement - exists(RangeStmt rs | s = rs | - exists(rs.getKey()) and i = 0 - or - exists(rs.getValue()) and i = 1 - ) - or - // in a return statement `return f()` where `f` has multiple return values - exists(ReturnStmt ret, SignatureType rettp | - s = ret and - // the return statement has a single expression - exists(ret.getExpr()) and - // but the enclosing function has multiple results - rettp = ret.getEnclosingFunction().getType() and - rettp.getNumResult() > 1 and - exists(rettp.getResultType(i)) - ) - or - // in a call `f(g())` where `g` has multiple return values - exists(CallExpr outer, CallExpr inner | s = outer | - inner = outer.getArgument(0).stripParens() and - outer.getNumArgument() = 1 and - exists(inner.getType().(TupleType).getComponentType(i)) - ) - } or - /** - * A control-flow node that represents the zero value to which a variable without an initializer - * expression is initialized. - */ - MkZeroInitNode(ValueEntity v) { - exists(ValueSpec spec | - not exists(spec.getAnInit()) and - spec.getNameExpr(_) = v.getDeclaration() - ) - or - exists(v.(ResultVariable).getFunction().getBody()) - } or - /** - * A control-flow node that represents a function declaration. - */ - MkFuncDeclNode(FuncDecl fd) or - /** - * A control-flow node that represents a `defer` statement. - */ - MkDeferNode(DeferStmt def) or - /** - * A control-flow node that represents a `go` statement. - */ - MkGoNode(GoStmt go) or - /** - * A control-flow node that represents the fact that `e` is known to evaluate to - * `outcome`. - */ - MkConditionGuardNode(Expr e, Boolean outcome) { isCondRoot(e) } or - /** - * A control-flow node that represents an increment or decrement statement. - */ - MkIncDecNode(IncDecStmt ids) or - /** - * A control-flow node that represents the implicit right-hand side of an increment or decrement statement. - */ - MkIncDecRhs(IncDecStmt ids) or - /** - * A control-flow node that represents the implicit operand 1 of an increment or decrement statement. - */ - MkImplicitOne(IncDecStmt ids) or - /** - * A control-flow node that represents a return from a function. - */ - MkReturnNode(ReturnStmt ret) or - /** - * A control-flow node that represents the implicit write to a named result variable in a return statement. - */ - MkResultWriteNode(ResultVariable var, int i, ReturnStmt ret) { - ret.getEnclosingFunction().getResultVar(i) = var and - exists(ret.getAnExpr()) - } or - /** - * A control-flow node that represents the implicit read of a named result variable upon returning from - * a function (after any deferred calls have been executed). - */ - MkResultReadNode(ResultVariable var) or - /** - * A control-flow node that represents a no-op. - * - * These control-flow nodes correspond to Go statements that have no runtime semantics other than potentially - * influencing control flow: the branching statements `continue`, `break`, `fallthrough` and `goto`; empty - * blocks; empty statements; and import and type declarations. - */ - MkSkipNode(AstNode skip) { - skip instanceof BranchStmt - or - skip instanceof EmptyStmt - or - skip.(PlainBlock).getNumStmt() = 0 - or - skip instanceof ImportDecl - or - skip instanceof TypeDecl - or - pureLvalue(skip) - or - skip.(CaseClause).getNumStmt() = 0 - or - skip.(CommClause).getNumStmt() = 0 - } or - /** - * A control-flow node that represents a `select` operation. - */ - MkSelectNode(SelectStmt sel) or - /** - * A control-flow node that represents a `send` operation. - */ - MkSendNode(SendStmt send) or - /** - * A control-flow node that represents the initialization of a parameter to its corresponding argument. - */ - MkParameterInit(Parameter parm) { exists(parm.getFunction().getBody()) } or - /** - * A control-flow node that represents the argument corresponding to a parameter. - */ - MkArgumentNode(Parameter parm) { exists(parm.getFunction().getBody()) } or - /** - * A control-flow node that represents the initialization of a result variable to its zero value. - */ - MkResultInit(ResultVariable rv) { exists(rv.getFunction().getBody()) } or - /** - * A control-flow node that represents the operation of retrieving the next (key, value) pair in a - * `range` statement, if any. - */ - MkNextNode(RangeStmt rs) or - /** - * A control-flow node that represents the implicit `true` expression in `switch { ... }`. - */ - MkImplicitTrue(ExpressionSwitchStmt stmt) { not exists(stmt.getExpr()) } or - /** - * A control-flow node that represents the implicit comparison or type check performed by - * the `i`th expression of a case clause `cc`. - */ - MkCaseCheckNode(CaseClause cc, int i) { exists(cc.getExpr(i)) } or - /** - * A control-flow node that represents the implicit declaration of the - * variable `lv` in case clause `cc` and its assignment of the value - * `switchExpr` from the guard. This only occurs in case clauses in a type - * switch statement which declares a variable in its guard. - */ - MkTypeSwitchImplicitVariable(CaseClause cc, LocalVariable lv, Expr switchExpr) { - exists(TypeSwitchStmt ts, DefineStmt ds | ds = ts.getAssign() | - cc = ts.getACase() and - lv = cc.getImplicitlyDeclaredVariable() and - switchExpr = ds.getRhs().(TypeAssertExpr).getExpr() - ) - } or - /** - * A control-flow node that represents the implicit lower bound of a slice expression. - */ - MkImplicitLowerSliceBound(SliceExpr sl) { not exists(sl.getLow()) } or - /** - * A control-flow node that represents the implicit upper bound of a simple slice expression. - */ - MkImplicitUpperSliceBound(SliceExpr sl) { not exists(sl.getHigh()) } or - /** - * A control-flow node that represents the implicit max bound of a simple slice expression. - */ - MkImplicitMaxSliceBound(SliceExpr sl) { not exists(sl.getMax()) } or - /** - * A control-flow node that represents the implicit dereference of the base in a field/method - * access, element access, or slice expression. - */ - MkImplicitDeref(Expr e) { - e.getType().getUnderlyingType() instanceof PointerType and - ( - exists(SelectorExpr sel | e = sel.getBase() | - // field accesses through a pointer always implicitly dereference - sel = any(Field f).getAReference() - or - // method accesses only dereference if the receiver is _not_ a pointer - exists(Method m, Type tp | - sel = m.getAReference() and - tp = m.getReceiver().getType().getUnderlyingType() and - not tp instanceof PointerType - ) - ) - or - e = any(IndexExpr ie).getBase() - or - e = any(SliceExpr se).getBase() - ) - } or - /** - * A control-flow node that represents the implicit selection of a field when - * accessing a promoted field. - * - * If that field has a pointer type then this control-flow node also - * represents an implicit dereference of it. - */ - MkImplicitFieldSelection(PromotedSelector e, int i, Field implicitField) { - implicitFieldSelectionForField(e, i, implicitField) or - implicitFieldSelectionForMethod(e, i, implicitField) - } or - /** - * A control-flow node that represents the start of the execution of a function or file. - */ - MkEntryNode(ControlFlow::Root root) or - /** - * A control-flow node that represents the end of the execution of a function or file. - */ - MkExitNode(ControlFlow::Root root) - -/** A representation of the target of a write. */ -newtype TWriteTarget = - /** A write target that is represented explicitly in the AST. */ - MkLhs(TControlFlowNode write, Expr lhs) { - exists(AstNode assgn, int i | write = MkAssignNode(assgn, i) | - lhs = assgn.(Assignment).getLhs(i).stripParens() - or - lhs = assgn.(ValueSpec).getNameExpr(i) - or - exists(RangeStmt rs | rs = assgn | - i = 0 and lhs = rs.getKey().stripParens() - or - i = 1 and lhs = rs.getValue().stripParens() - ) - ) - or - exists(IncDecStmt ids | write = MkIncDecNode(ids) | lhs = ids.getOperand().stripParens()) - or - exists(Parameter parm | write = MkParameterInit(parm) | lhs = parm.getDeclaration()) - or - exists(ResultVariable res | write = MkResultInit(res) | lhs = res.getDeclaration()) - } or - /** A write target for an element in a compound literal, viewed as a field write. */ - MkLiteralElementTarget(MkLiteralElementInitNode elt) or - /** A write target for a returned expression, viewed as a write to the corresponding result variable. */ - MkResultWriteTarget(MkResultWriteNode w) - -/** - * A control-flow node that represents a no-op. - * - * These control-flow nodes correspond to Go statements that have no runtime semantics other than - * potentially influencing control flow: the branching statements `continue`, `break`, - * `fallthrough` and `goto`; empty blocks; empty statements; and import and type declarations. - */ -class SkipNode extends ControlFlow::Node, MkSkipNode { - AstNode skip; - - SkipNode() { this = MkSkipNode(skip) } - - override ControlFlow::Root getRoot() { result.isRootOf(skip) } - - override string toString() { result = "skip" } - - override Location getLocation() { result = skip.getLocation() } -} - -/** - * A control-flow node that represents the start of the execution of a function or file. - */ -class EntryNode extends ControlFlow::Node, MkEntryNode { - ControlFlow::Root root; - - EntryNode() { this = MkEntryNode(root) } - - override ControlFlow::Root getRoot() { result = root } - - override string toString() { result = "entry" } - - override Location getLocation() { result = root.getLocation() } -} - -/** - * A control-flow node that represents the end of the execution of a function or file. - */ -class ExitNode extends ControlFlow::Node, MkExitNode { - ControlFlow::Root root; - - ExitNode() { this = MkExitNode(root) } - - override ControlFlow::Root getRoot() { result = root } - - override string toString() { result = "exit" } - - override Location getLocation() { result = root.getLocation() } -} - -/** - * Provides classes and predicates for computing the control-flow graph. - */ -cached -module CFG { - /** - * The target of a branch statement, which is either the label of a labeled statement or - * the special target `""` referring to the innermost enclosing loop or `switch`. - */ - private class BranchTarget extends string { - BranchTarget() { this = any(LabeledStmt ls).getLabel() or this = "" } - } - - private module BranchTarget { - /** Holds if this is the target of branch statement `stmt` or the label of compound statement `stmt`. */ - BranchTarget of(Stmt stmt) { - exists(BranchStmt bs | bs = stmt | - result = bs.getLabel() - or - not exists(bs.getLabel()) and result = "" - ) - or - exists(LabeledStmt ls | stmt = ls.getStmt() | result = ls.getLabel()) - or - (stmt instanceof LoopStmt or stmt instanceof SwitchStmt or stmt instanceof SelectStmt) and - result = "" - } - } - - private newtype TCompletion = - /** A completion indicating that an expression or statement was evaluated successfully. */ - Done() or - /** - * A completion indicating that an expression was successfully evaluated to Boolean value `b`. - * - * Note that many Boolean expressions are modeled as having completion `Done()` instead. - * Completion `Bool` is only used in contexts where the Boolean value can be determined. - */ - Bool(boolean b) { b = true or b = false } or - /** - * A completion indicating that execution of a (compound) statement ended with a `break` - * statement targeting the given label. - */ - Break(BranchTarget lbl) or - /** - * A completion indicating that execution of a (compound) statement ended with a `continue` - * statement targeting the given label. - */ - Continue(BranchTarget lbl) or - /** - * A completion indicating that execution of a (compound) statement ended with a `fallthrough` - * statement. - */ - Fallthrough() or - /** - * A completion indicating that execution of a (compound) statement ended with a `return` - * statement. - */ - Return() or - /** - * A completion indicating that execution of a statement or expression may have ended with - * a panic being raised. - */ - Panic() - - private Completion normalCompletion() { result.isNormal() } - - private class Completion extends TCompletion { - predicate isNormal() { this = Done() or this = Bool(_) } - - Boolean getOutcome() { this = Done() or this = Bool(result) } - - string toString() { - this = Done() and result = "normal" - or - exists(boolean b | this = Bool(b) | result = b.toString()) - or - exists(BranchTarget lbl | - this = Break(lbl) and result = "break " + lbl - or - this = Continue(lbl) and result = "continue " + lbl - ) - or - this = Fallthrough() and result = "fallthrough" - or - this = Return() and result = "return" - or - this = Panic() and result = "panic" - } - } - - /** - * Holds if `e` should have an evaluation node in the control-flow graph. - * - * Excluded expressions include those not evaluated at runtime (e.g. identifiers, type expressions) - * and some logical expressions that are expressed as control-flow edges rather than having a specific - * evaluation node. - */ - cached - predicate hasEvaluationNode(Expr e) { - // exclude expressions that do not denote a value - not e instanceof TypeExpr and - not e = any(FieldDecl f).getTag() and - not e instanceof KeyValueExpr and - not e = any(SelectorExpr sel).getSelector() and - not e = any(StructLit sl).getKey(_) and - not (e instanceof Ident and not e instanceof ReferenceExpr) and - not (e instanceof SelectorExpr and not e instanceof ReferenceExpr) and - not pureLvalue(e) and - // exclude parentheses, which are purely concrete syntax, and some logical binary expressions - // whose evaluation is implied by control-flow edges without requiring an evaluation node. - not isControlFlowStructural(e) and - // exclude expressions that are not evaluated at runtime - not e = any(ImportSpec is).getPathExpr() and - not e.getParent*() = any(ArrayTypeExpr ate).getLength() and - // sub-expressions of constant expressions are not evaluated (even if they don't look constant - // themselves) - not constRoot(e.getParent+()) - } - - /** - * Holds if `e` is an expression that purely serves grouping or control-flow purposes. - * - * Examples include parenthesized expressions and short-circuiting Boolean expressions used within - * a branch condition (`if` or `for` condition, or as part of a larger boolean expression, e.g. - * in `(x && y) || z`, the `&&` subexpression matches this predicate). - */ - private predicate isControlFlowStructural(Expr e) { - // Some logical binary operators do not need an evaluation node - // (for example, in `if x && y`, we evaluate `x` and then branch straight to either `y` or the - // `else` block, so there is no control-flow step where `x && y` is specifically calculated) - e instanceof LogicalBinaryExpr and - isCond(e) - or - // Purely concrete-syntactic structural expression: - e instanceof ParenExpr - } - - /** - * Gets a constant root, that is, an expression that is constant but whose parent expression is not. - * - * As an exception to the latter, for a control-flow structural expression such as `(c1)` or `c1 && c2` - * where `cn` are constants we still consider the `cn`s to be a constant roots, even though their parent - * expression is also constant. - */ - private predicate constRoot(Expr root) { - exists(Expr c | - c.isConst() and - not c.getParent().(Expr).isConst() and - root = stripStructural(c) - ) - } - - /** - * Strips off any control-flow structural components from `e`. - */ - private Expr stripStructural(Expr e) { - if isControlFlowStructural(e) then result = stripStructural(e.getAChildExpr()) else result = e - } - - private class ControlFlowTree extends AstNode { - predicate firstNode(ControlFlow::Node first) { none() } - - predicate lastNode(ControlFlow::Node last, Completion cmpl) { - // propagate abnormal completion from children - lastNode(this.getAChild(), last, cmpl) and - not cmpl.isNormal() - } - - /** - * Holds if `succ` is a successor of `pred`, ignoring the execution of any - * deferred functions when a function ends. - */ - pragma[nomagic] - predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - exists(int i | - lastNode(this.getChildTreeRanked(i), pred, normalCompletion()) and - firstNode(this.getChildTreeRanked(i + 1), succ) - ) - } - - /** Holds if `succ` is a successor of `pred`. */ - predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { this.succ0(pred, succ) } - - final ControlFlowTree getChildTreeRanked(int i) { - exists(int j | - result = this.getChildTree(j) and - j = rank[i + 1](int k | exists(this.getChildTree(k))) - ) - } - - ControlFlowTree getFirstChildTree() { result = this.getChildTreeRanked(0) } - - ControlFlowTree getLastChildTree() { - result = max(ControlFlowTree ch, int j | ch = this.getChildTree(j) | ch order by j) - } - - ControlFlowTree getChildTree(int i) { none() } - } - - private class AtomicTree extends ControlFlowTree { - ControlFlow::Node nd; - Completion cmpl; - - AtomicTree() { - exists(Expr e | - e = this and - e.isConst() and - nd = mkExprOrSkipNode(this) - | - if e.isPlatformIndependentConstant() and exists(e.getBoolValue()) - then cmpl = Bool(e.getBoolValue()) - else cmpl = Done() - ) - or - this instanceof Ident and - not this.(Expr).isConst() and - nd = mkExprOrSkipNode(this) and - cmpl = Done() - or - this instanceof BreakStmt and - nd = MkSkipNode(this) and - cmpl = Break(BranchTarget::of(this)) - or - this instanceof ContinueStmt and - nd = MkSkipNode(this) and - cmpl = Continue(BranchTarget::of(this)) - or - this instanceof Decl and - nd = MkSkipNode(this) and - cmpl = Done() - or - this instanceof EmptyStmt and - nd = MkSkipNode(this) and - cmpl = Done() - or - this instanceof FallthroughStmt and - nd = MkSkipNode(this) and - cmpl = Fallthrough() - or - this instanceof FuncLit and - nd = MkExprNode(this) and - cmpl = Done() - or - this instanceof PlainBlock and - nd = MkSkipNode(this) and - cmpl = Done() - or - this instanceof SelectorExpr and - not this.(SelectorExpr).getBase() instanceof ValueExpr and - nd = mkExprOrSkipNode(this) and - cmpl = Done() - or - this instanceof GenericFunctionInstantiationExpr and - nd = MkExprNode(this) and - cmpl = Done() - } - - override predicate firstNode(ControlFlow::Node first) { first = nd } - - override predicate lastNode(ControlFlow::Node last, Completion c) { last = nd and c = cmpl } - } - - abstract private class PostOrderTree extends ControlFlowTree { - abstract ControlFlow::Node getNode(); - - Completion getCompletion() { result = Done() } - - override predicate firstNode(ControlFlow::Node first) { - firstNode(this.getFirstChildTree(), first) - or - not exists(this.getChildTree(_)) and - first = this.getNode() - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - super.lastNode(last, cmpl) - or - last = this.getNode() and cmpl = this.getCompletion() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - super.succ0(pred, succ) - or - lastNode(this.getLastChildTree(), pred, normalCompletion()) and - succ = this.getNode() - } - } - - abstract private class PreOrderTree extends ControlFlowTree { - abstract ControlFlow::Node getNode(); - - override predicate firstNode(ControlFlow::Node first) { first = this.getNode() } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - super.lastNode(last, cmpl) - or - lastNode(this.getLastChildTree(), last, cmpl) - or - not exists(this.getChildTree(_)) and - last = this.getNode() and - cmpl = Done() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - super.succ0(pred, succ) - or - pred = this.getNode() and - firstNode(this.getFirstChildTree(), succ) - } - } - - private class WrapperTree extends ControlFlowTree { - WrapperTree() { - this instanceof ConstDecl or - this instanceof DeclStmt or - this instanceof ExprStmt or - this instanceof KeyValueExpr or - this instanceof LabeledStmt or - this instanceof ParenExpr or - this instanceof PlainBlock or - this instanceof VarDecl - } - - override predicate firstNode(ControlFlow::Node first) { - firstNode(this.getFirstChildTree(), first) - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - super.lastNode(last, cmpl) - or - lastNode(this.getLastChildTree(), last, cmpl) - or - exists(LoopStmt ls | this = ls.getBody() | - lastNode(this, last, Continue(BranchTarget::of(ls))) and - cmpl = Done() - ) - } - - override ControlFlowTree getChildTree(int i) { - i = 0 and result = this.(DeclStmt).getDecl() - or - i = 0 and result = this.(ExprStmt).getExpr() - or - result = this.(GenDecl).getSpec(i) - or - exists(KeyValueExpr kv | kv = this | - not kv.getLiteral() instanceof StructLit and - i = 0 and - result = kv.getKey() - or - i = 1 and result = kv.getValue() - ) - or - i = 0 and result = this.(LabeledStmt).getStmt() - or - i = 0 and result = this.(ParenExpr).getExpr() - or - result = this.(PlainBlock).getStmt(i) - } - } - - private class AssignmentTree extends ControlFlowTree { - AssignmentTree() { - this instanceof Assignment or - this instanceof ValueSpec - } - - Expr getLhs(int i) { - result = this.(Assignment).getLhs(i) or - result = this.(ValueSpec).getNameExpr(i) - } - - int getNumLhs() { - result = this.(Assignment).getNumLhs() or - result = this.(ValueSpec).getNumName() - } - - Expr getRhs(int i) { - result = this.(Assignment).getRhs(i) or - result = this.(ValueSpec).getInit(i) - } - - int getNumRhs() { - result = this.(Assignment).getNumRhs() or - result = this.(ValueSpec).getNumInit() - } - - predicate isExtractingAssign() { this.getNumRhs() = 1 and this.getNumLhs() > 1 } - - override predicate firstNode(ControlFlow::Node first) { - not this instanceof RecvStmt and - firstNode(this.getLhs(0), first) - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - ( - last = max(int i | | this.epilogueNode(i) order by i) - or - not exists(this.epilogueNode(_)) and - lastNode(this.getLastSubExprInEvalOrder(), last, normalCompletion()) - ) and - cmpl = Done() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - ControlFlowTree.super.succ0(pred, succ) - or - exists(int i | lastNode(this.getLhs(i), pred, normalCompletion()) | - firstNode(this.getLhs(i + 1), succ) - or - not this instanceof RecvStmt and - i = this.getNumLhs() - 1 and - ( - firstNode(this.getRhs(0), succ) - or - not exists(this.getRhs(_)) and - succ = this.epilogueNodeRanked(0) - ) - ) - or - exists(int i | - lastNode(this.getRhs(i), pred, normalCompletion()) and - firstNode(this.getRhs(i + 1), succ) - ) - or - not this instanceof RecvStmt and - lastNode(this.getRhs(this.getNumRhs() - 1), pred, normalCompletion()) and - succ = this.epilogueNodeRanked(0) - or - exists(int i | - pred = this.epilogueNodeRanked(i) and - succ = this.epilogueNodeRanked(i + 1) - ) - } - - ControlFlow::Node epilogueNodeRanked(int i) { - exists(int j | - result = this.epilogueNode(j) and - j = rank[i + 1](int k | exists(this.epilogueNode(k))) - ) - } - - private Expr getSubExprInEvalOrder(int evalOrder) { - if evalOrder < this.getNumLhs() - then result = this.getLhs(evalOrder) - else result = this.getRhs(evalOrder - this.getNumLhs()) - } - - private Expr getLastSubExprInEvalOrder() { - result = max(int i | | this.getSubExprInEvalOrder(i) order by i) - } - - private ControlFlow::Node epilogueNode(int i) { - i = -1 and - result = MkCompoundAssignRhsNode(this) - or - exists(int j | - result = MkExtractNode(this, j) and - i = 2 * j - or - result = MkZeroInitNode(any(ValueEntity v | this.getLhs(j) = v.getDeclaration())) and - i = 2 * j - or - result = MkAssignNode(this, j) and - i = 2 * j + 1 - ) - } - } - - private class BinaryExprTree extends PostOrderTree, BinaryExpr { - override ControlFlow::Node getNode() { result = MkExprNode(this) } - - private predicate equalityTestMayPanic() { - this instanceof EqualityTestExpr and - exists(Type t | - t = this.getAnOperand().getType().getUnderlyingType() and - ( - t instanceof InterfaceType or // panic due to comparison of incomparable interface values - t instanceof StructType or // may contain an interface-typed field - t instanceof ArrayType // may be an array of interface values - ) - ) - } - - override Completion getCompletion() { - result = PostOrderTree.super.getCompletion() - or - // runtime panic due to division by zero or comparison of incomparable interface values - (this instanceof DivExpr or this.equalityTestMayPanic()) and - not this.(Expr).isConst() and - result = Panic() - } - - override ControlFlowTree getChildTree(int i) { - i = 0 and result = this.getLeftOperand() - or - i = 1 and result = this.getRightOperand() - } - } - - private class LogicalBinaryExprTree extends BinaryExprTree, LogicalBinaryExpr { - boolean shortCircuit; - - LogicalBinaryExprTree() { - this instanceof LandExpr and shortCircuit = false - or - this instanceof LorExpr and shortCircuit = true - } - - private ControlFlow::Node getGuard(boolean outcome) { - result = MkConditionGuardNode(this.getLeftOperand(), outcome) - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - lastNode(this.getAnOperand(), last, cmpl) and - not cmpl.isNormal() - or - if isCond(this) - then ( - last = this.getGuard(shortCircuit) and - cmpl = Bool(shortCircuit) - or - lastNode(this.getRightOperand(), last, cmpl) - ) else ( - last = MkExprNode(this) and - cmpl = Done() - ) - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - exists(Completion lcmpl | - lastNode(this.getLeftOperand(), pred, lcmpl) and - succ = this.getGuard(lcmpl.getOutcome()) - ) - or - pred = this.getGuard(shortCircuit.booleanNot()) and - firstNode(this.getRightOperand(), succ) - or - not isCond(this) and - ( - pred = this.getGuard(shortCircuit) and - succ = MkExprNode(this) - or - exists(Completion rcmpl | - lastNode(this.getRightOperand(), pred, rcmpl) and - rcmpl.isNormal() and - succ = MkExprNode(this) - ) - ) - } - } - - private class CallExprTree extends PostOrderTree, CallExpr { - private predicate isSpecial() { - this = any(DeferStmt defer).getCall() or - this = any(GoStmt go).getCall() - } - - override ControlFlow::Node getNode() { - not this.isSpecial() and - result = MkExprNode(this) - } - - override Completion getCompletion() { - (not exists(this.getTarget()) or this.getTarget().mayReturnNormally()) and - result = Done() - or - (not exists(this.getTarget()) or this.getTarget().mayPanic()) and - result = Panic() - } - - override ControlFlowTree getChildTree(int i) { - i = 0 and result = this.getCalleeExpr() - or - result = this.getArgument(i - 1) and - // calls to `make` and `new` can have type expressions as arguments - not result instanceof TypeExpr - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - // interpose implicit argument destructuring nodes between last argument - // and call itself; this is for cases like `f(g())` where `g` has multiple - // results - exists(ControlFlow::Node mid | PostOrderTree.super.succ0(pred, mid) | - if mid = this.getNode() then succ = this.getEpilogueNode(0) else succ = mid - ) - or - exists(int i | - pred = this.getEpilogueNode(i) and - succ = this.getEpilogueNode(i + 1) - ) - } - - private ControlFlow::Node getEpilogueNode(int i) { - result = MkExtractNode(this, i) - or - i = max(int j | exists(MkExtractNode(this, j))) + 1 and - result = this.getNode() - or - not exists(MkExtractNode(this, _)) and - i = 0 and - result = this.getNode() - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - PostOrderTree.super.lastNode(last, cmpl) - or - this.isSpecial() and - lastNode(this.getLastChildTree(), last, cmpl) - } - } - - private class CaseClauseTree extends ControlFlowTree, CaseClause { - private ControlFlow::Node getExprStart(int i) { - firstNode(this.getExpr(i), result) - or - this.getExpr(i) instanceof TypeExpr and - result = MkCaseCheckNode(this, i) - } - - ControlFlow::Node getExprEnd(int i, Boolean outcome) { - exists(Expr e | e = this.getExpr(i) | - result = MkConditionGuardNode(e, outcome) - or - not exists(MkConditionGuardNode(e, _)) and - result = MkCaseCheckNode(this, i) - ) - } - - private ControlFlow::Node getBodyStart() { - firstNode(this.getStmt(0), result) or result = MkSkipNode(this) - } - - override predicate firstNode(ControlFlow::Node first) { - first = this.getExprStart(0) - or - not exists(this.getAnExpr()) and - first = MkTypeSwitchImplicitVariable(this, _, _) - or - not exists(this.getAnExpr()) and - not exists(MkTypeSwitchImplicitVariable(this, _, _)) and - first = this.getBodyStart() - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - // TODO: shouldn't be here - last = this.getExprEnd(this.getNumExpr() - 1, false) and - cmpl = Bool(false) - or - last = MkSkipNode(this) and - cmpl = Done() - or - lastNode(this.getStmt(this.getNumStmt() - 1), last, cmpl) - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - ControlFlowTree.super.succ0(pred, succ) - or - pred = MkTypeSwitchImplicitVariable(this, _, _) and - succ = this.getBodyStart() - or - exists(int i | - lastNode(this.getExpr(i), pred, normalCompletion()) and - succ = MkCaseCheckNode(this, i) - or - // visit guard node if there is one - pred = MkCaseCheckNode(this, i) and - succ = this.getExprEnd(i, _) and - succ != pred // this avoids self-loops if there isn't a guard node - or - pred = this.getExprEnd(i, false) and - succ = this.getExprStart(i + 1) - or - this.isPassingEdge(i, pred, succ, _) - ) - } - - predicate isPassingEdge(int i, ControlFlow::Node pred, ControlFlow::Node succ, Expr testExpr) { - pred = this.getExprEnd(i, true) and - testExpr = this.getExpr(i) and - ( - succ = MkTypeSwitchImplicitVariable(this, _, _) - or - not exists(MkTypeSwitchImplicitVariable(this, _, _)) and - succ = this.getBodyStart() - ) - } - - override ControlFlowTree getChildTree(int i) { result = this.getStmt(i) } - } - - private class CommClauseTree extends ControlFlowTree, CommClause { - override predicate firstNode(ControlFlow::Node first) { firstNode(this.getComm(), first) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - last = MkSkipNode(this) and - cmpl = Done() - or - lastNode(this.getStmt(this.getNumStmt() - 1), last, cmpl) - } - - override ControlFlowTree getChildTree(int i) { result = this.getStmt(i) } - } - - private class CompositeLiteralTree extends ControlFlowTree, CompositeLit { - private ControlFlow::Node getElementInit(int i) { - result = MkLiteralElementInitNode(this.getElement(i)) - } - - private ControlFlow::Node getElementStart(int i) { - exists(Expr elt | elt = this.getElement(i) | - result = MkImplicitLiteralElementIndex(elt) - or - (elt instanceof KeyValueExpr or this instanceof StructLit) and - firstNode(this.getElement(i), result) - ) - } - - override predicate firstNode(ControlFlow::Node first) { first = MkExprNode(this) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - last = this.getElementInit(this.getNumElement() - 1) and - cmpl = Done() - or - not exists(this.getElement(_)) and - last = MkExprNode(this) and - cmpl = Done() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - this.firstNode(pred) and - succ = this.getElementStart(0) - or - exists(int i | - pred = MkImplicitLiteralElementIndex(this.getElement(i)) and - firstNode(this.getElement(i), succ) - or - lastNode(this.getElement(i), pred, normalCompletion()) and - succ = this.getElementInit(i) - or - pred = this.getElementInit(i) and - succ = this.getElementStart(i + 1) - ) - } - } - - private class ConversionExprTree extends PostOrderTree, ConversionExpr { - override Completion getCompletion() { - // conversions of a slice to an array pointer are the only kind that may panic - this.getType().(PointerType).getBaseType() instanceof ArrayType and - result = Panic() - or - result = Done() - } - - override ControlFlow::Node getNode() { result = MkExprNode(this) } - - override ControlFlowTree getChildTree(int i) { i = 0 and result = this.getOperand() } - } - - private class DeferStmtTree extends PostOrderTree, DeferStmt { - override ControlFlow::Node getNode() { result = MkDeferNode(this) } - - override ControlFlowTree getChildTree(int i) { i = 0 and result = this.getCall() } - } - - private class FuncDeclTree extends PostOrderTree, FuncDecl { - override ControlFlow::Node getNode() { result = MkFuncDeclNode(this) } - - override ControlFlowTree getChildTree(int i) { i = 0 and result = this.getNameExpr() } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - // override to prevent panic propagation out of function declarations - last = this.getNode() and cmpl = Done() - } - } - - private class GoStmtTree extends PostOrderTree, GoStmt { - override ControlFlow::Node getNode() { result = MkGoNode(this) } - - override ControlFlowTree getChildTree(int i) { i = 0 and result = this.getCall() } - } - - private class IfStmtTree extends ControlFlowTree, IfStmt { - private ControlFlow::Node getGuard(boolean outcome) { - result = MkConditionGuardNode(this.getCond(), outcome) - } - - override predicate firstNode(ControlFlow::Node first) { - firstNode(this.getInit(), first) - or - not exists(this.getInit()) and - firstNode(this.getCond(), first) - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - lastNode(this.getThen(), last, cmpl) - or - lastNode(this.getElse(), last, cmpl) - or - not exists(this.getElse()) and - last = this.getGuard(false) and - cmpl = Done() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - lastNode(this.getInit(), pred, normalCompletion()) and - firstNode(this.getCond(), succ) - or - exists(Completion condCmpl | - lastNode(this.getCond(), pred, condCmpl) and - succ = MkConditionGuardNode(this.getCond(), condCmpl.getOutcome()) - ) - or - pred = this.getGuard(true) and - firstNode(this.getThen(), succ) - or - pred = this.getGuard(false) and - firstNode(this.getElse(), succ) - } - } - - private class IndexExprTree extends ControlFlowTree, IndexExpr { - override predicate firstNode(ControlFlow::Node first) { firstNode(this.getBase(), first) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - // panic due to `nil` dereference - last = MkImplicitDeref(this.getBase()) and - cmpl = Panic() - or - last = mkExprOrSkipNode(this) and - (cmpl = Done() or cmpl = Panic()) - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - lastNode(this.getBase(), pred, normalCompletion()) and - ( - succ = MkImplicitDeref(this.getBase()) - or - not exists(MkImplicitDeref(this.getBase())) and - firstNode(this.getIndex(), succ) - ) - or - pred = MkImplicitDeref(this.getBase()) and - firstNode(this.getIndex(), succ) - or - lastNode(this.getIndex(), pred, normalCompletion()) and - succ = mkExprOrSkipNode(this) - } - } - - private class LoopTree extends ControlFlowTree, LoopStmt { - BranchTarget getLabel() { result = BranchTarget::of(this) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - exists(Completion inner | lastNode(this.getBody(), last, inner) and not inner.isNormal() | - if inner = Break(this.getLabel()) - then cmpl = Done() - else ( - not inner = Continue(this.getLabel()) and - cmpl = inner - ) - ) - } - } - - private class FileTree extends ControlFlowTree, File { - FileTree() { exists(this.getADecl()) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { none() } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - ControlFlowTree.super.succ0(pred, succ) - or - pred = MkEntryNode(this) and - firstNode(this.getDecl(0), succ) - or - exists(int i, Completion inner | lastNode(this.getDecl(i), pred, inner) | - not inner.isNormal() - or - i = this.getNumDecl() - 1 - ) and - succ = MkExitNode(this) - } - - override ControlFlowTree getChildTree(int i) { result = this.getDecl(i) } - } - - private class ForTree extends LoopTree, ForStmt { - private ControlFlow::Node getGuard(boolean outcome) { - result = MkConditionGuardNode(this.getCond(), outcome) - } - - override predicate firstNode(ControlFlow::Node first) { - firstNode(this.getFirstChildTree(), first) - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - LoopTree.super.lastNode(last, cmpl) - or - lastNode(this.getInit(), last, cmpl) and - not cmpl.isNormal() - or - lastNode(this.getCond(), last, cmpl) and - not cmpl.isNormal() - or - lastNode(this.getPost(), last, cmpl) and - not cmpl.isNormal() - or - last = this.getGuard(false) and - cmpl = Done() - } - - override ControlFlowTree getChildTree(int i) { - i = 0 and result = this.getInit() - or - i = 1 and result = this.getCond() - or - i = 2 and result = this.getBody() - or - i = 3 and result = this.getPost() - or - i = 4 and result = this.getCond() - or - i = 5 and result = this.getBody() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - exists(int i, ControlFlowTree predTree, Completion cmpl | - predTree = this.getChildTreeRanked(i) and - lastNode(predTree, pred, cmpl) and - cmpl.isNormal() - | - if predTree = this.getCond() - then succ = this.getGuard(cmpl.getOutcome()) - else firstNode(this.getChildTreeRanked(i + 1), succ) - ) - or - pred = this.getGuard(true) and - firstNode(this.getBody(), succ) - } - } - - private class FuncDefTree extends ControlFlowTree, FuncDef { - FuncDefTree() { exists(this.getBody()) } - - pragma[noinline] - private MkEntryNode getEntry() { result = MkEntryNode(this) } - - private Parameter getParameterRanked(int i) { - result = rank[i + 1](Parameter p, int j | p = this.getParameter(j) | p order by j) - } - - private ControlFlow::Node getPrologueNode(int i) { - i = -1 and result = this.getEntry() - or - exists(int numParm, int numRes | - numParm = count(this.getParameter(_)) and - numRes = count(this.getResultVar(_)) - | - exists(int j, Parameter p | p = this.getParameterRanked(j) | - i = 2 * j and result = MkArgumentNode(p) - or - i = 2 * j + 1 and result = MkParameterInit(p) - ) - or - exists(int j, ResultVariable v | v = this.getResultVar(j) | - i = 2 * numParm + 2 * j and - result = MkZeroInitNode(v) - or - i = 2 * numParm + 2 * j + 1 and - result = MkResultInit(v) - ) - or - i = 2 * numParm + 2 * numRes and - firstNode(this.getBody(), result) - ) - } - - private ControlFlow::Node getEpilogueNode(int i) { - result = MkResultReadNode(this.getResultVar(i)) - or - i = count(this.getAResultVar()) and - result = MkExitNode(this) - } - - pragma[noinline] - private predicate firstDefer(ControlFlow::Node nd) { - exists(DeferStmt defer | - nd = MkExprNode(defer.getCall()) and - // `defer` can be the first `defer` statement executed - // there is always a predecessor node because the `defer`'s call is always - // evaluated before the defer statement itself - MkDeferNode(defer) = succ0(notDeferSucc0*(this.getEntry())) - ) - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { none() } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - exists(int i | - pred = this.getPrologueNode(i) and - succ = this.getPrologueNode(i + 1) - ) - or - exists(GotoStmt goto, LabeledStmt ls | - pred = MkSkipNode(goto) and - this = goto.getEnclosingFunction() and - this = ls.getEnclosingFunction() and - goto.getLabel() = ls.getLabel() and - firstNode(ls, succ) - ) - or - exists(int i | - pred = this.getEpilogueNode(i) and - succ = this.getEpilogueNode(i + 1) - ) - } - - override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { - this.succ0(pred, succ) - or - exists(Completion cmpl | - lastNode(this.getBody(), pred, cmpl) and - // last node of function body can be reached without going through a `defer` statement - pred = notDeferSucc0*(this.getEntry()) - | - // panic goes directly to exit, non-panic reads result variables first - if cmpl = Panic() then succ = MkExitNode(this) else succ = this.getEpilogueNode(0) - ) - or - lastNode(this.getBody(), pred, _) and - exists(DeferStmt defer | defer = this.getADeferStmt() | - succ = MkExprNode(defer.getCall()) and - // the last `DeferStmt` executed before pred is this `defer` - pred = notDeferSucc0*(MkDeferNode(defer)) - ) - or - exists(DeferStmt predDefer, DeferStmt succDefer | - predDefer = this.getADeferStmt() and - succDefer = this.getADeferStmt() - | - // reversed because `defer`s are executed in LIFO order - MkDeferNode(predDefer) = nextDefer(MkDeferNode(succDefer)) and - pred = MkExprNode(predDefer.getCall()) and - succ = MkExprNode(succDefer.getCall()) - ) - or - this.firstDefer(pred) and - ( - // conservatively assume that we might either panic (and hence skip the result reads) - // or not - succ = MkExitNode(this) - or - succ = this.getEpilogueNode(0) - ) - } - } - - private class GotoTree extends ControlFlowTree, GotoStmt { - override predicate firstNode(ControlFlow::Node first) { first = MkSkipNode(this) } - } - - private class IncDecTree extends ControlFlowTree, IncDecStmt { - override predicate firstNode(ControlFlow::Node first) { firstNode(this.getOperand(), first) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - last = MkIncDecNode(this) and - cmpl = Done() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - lastNode(this.getOperand(), pred, normalCompletion()) and - succ = MkImplicitOne(this) - or - pred = MkImplicitOne(this) and - succ = MkIncDecRhs(this) - or - pred = MkIncDecRhs(this) and - succ = MkIncDecNode(this) - } - } - - private class RangeTree extends LoopTree, RangeStmt { - override predicate firstNode(ControlFlow::Node first) { firstNode(this.getDomain(), first) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - LoopTree.super.lastNode(last, cmpl) - or - last = MkNextNode(this) and - cmpl = Done() - or - lastNode(this.getKey(), last, cmpl) and - not cmpl.isNormal() - or - lastNode(this.getValue(), last, cmpl) and - not cmpl.isNormal() - or - lastNode(this.getDomain(), last, cmpl) and - not cmpl.isNormal() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - lastNode(this.getDomain(), pred, normalCompletion()) and - succ = MkNextNode(this) - or - pred = MkNextNode(this) and - ( - firstNode(this.getKey(), succ) - or - not exists(this.getKey()) and - firstNode(this.getBody(), succ) - ) - or - lastNode(this.getKey(), pred, normalCompletion()) and - ( - firstNode(this.getValue(), succ) - or - not exists(this.getValue()) and - succ = MkExtractNode(this, 0) - ) - or - lastNode(this.getValue(), pred, normalCompletion()) and - succ = MkExtractNode(this, 0) - or - pred = MkExtractNode(this, 0) and - ( - if exists(this.getValue()) - then succ = MkExtractNode(this, 1) - else - if exists(MkAssignNode(this, 0)) - then succ = MkAssignNode(this, 0) - else - if exists(MkAssignNode(this, 1)) - then succ = MkAssignNode(this, 1) - else firstNode(this.getBody(), succ) - ) - or - pred = MkExtractNode(this, 1) and - ( - if exists(MkAssignNode(this, 0)) - then succ = MkAssignNode(this, 0) - else - if exists(MkAssignNode(this, 1)) - then succ = MkAssignNode(this, 1) - else firstNode(this.getBody(), succ) - ) - or - pred = MkAssignNode(this, 0) and - ( - if exists(MkAssignNode(this, 1)) - then succ = MkAssignNode(this, 1) - else firstNode(this.getBody(), succ) - ) - or - pred = MkAssignNode(this, 1) and - firstNode(this.getBody(), succ) - or - exists(Completion inner | - lastNode(this.getBody(), pred, inner) and - (inner.isNormal() or inner = Continue(BranchTarget::of(this))) and - succ = MkNextNode(this) - ) - } - } - - private class RecvStmtTree extends ControlFlowTree, RecvStmt { - override predicate firstNode(ControlFlow::Node first) { - firstNode(this.getExpr().getOperand(), first) - } - } - - private class ReturnStmtTree extends PostOrderTree, ReturnStmt { - override ControlFlow::Node getNode() { result = MkReturnNode(this) } - - override Completion getCompletion() { result = Return() } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - exists(int i | - lastNode(this.getExpr(i), pred, normalCompletion()) and - succ = this.complete(i) - or - pred = MkExtractNode(this, i) and - succ = this.after(i) - or - pred = MkResultWriteNode(_, i, this) and - succ = this.next(i) - ) - } - - private ControlFlow::Node complete(int i) { - result = MkExtractNode(this, i) - or - not exists(MkExtractNode(this, _)) and - result = this.after(i) - } - - private ControlFlow::Node after(int i) { - result = MkResultWriteNode(_, i, this) - or - not exists(MkResultWriteNode(_, i, this)) and - result = this.next(i) - } - - private ControlFlow::Node next(int i) { - firstNode(this.getExpr(i + 1), result) - or - exists(MkExtractNode(this, _)) and - result = this.complete(i + 1) - or - i + 1 = this.getEnclosingFunction().getType().getNumResult() and - result = this.getNode() - } - - override ControlFlowTree getChildTree(int i) { result = this.getExpr(i) } - } - - private class SelectStmtTree extends ControlFlowTree, SelectStmt { - private BranchTarget getLabel() { result = BranchTarget::of(this) } - - override predicate firstNode(ControlFlow::Node first) { - firstNode(this.getNonDefaultCommClause(0), first) - or - this.getNumNonDefaultCommClause() = 0 and - first = MkSelectNode(this) - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - exists(Completion inner | lastNode(this.getACommClause(), last, inner) | - if inner = Break(this.getLabel()) then cmpl = Done() else cmpl = inner - ) - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - ControlFlowTree.super.succ0(pred, succ) - or - exists(CommClause cc, int i, Stmt comm | - cc = this.getNonDefaultCommClause(i) and - comm = cc.getComm() and - ( - comm instanceof RecvStmt and - lastNode(comm.(RecvStmt).getExpr().getOperand(), pred, normalCompletion()) - or - comm instanceof SendStmt and - lastNode(comm.(SendStmt).getValue(), pred, normalCompletion()) - ) - | - firstNode(this.getNonDefaultCommClause(i + 1), succ) - or - i = this.getNumNonDefaultCommClause() - 1 and - succ = MkSelectNode(this) - ) - or - pred = MkSelectNode(this) and - exists(CommClause cc, Stmt comm | - cc = this.getNonDefaultCommClause(_) and comm = cc.getComm() - | - comm instanceof RecvStmt and - succ = MkExprNode(comm.(RecvStmt).getExpr()) - or - comm instanceof SendStmt and - succ = MkSendNode(comm) - ) - or - pred = MkSelectNode(this) and - exists(CommClause cc | cc = this.getDefaultCommClause() | - firstNode(cc.getStmt(0), succ) - or - succ = MkSkipNode(cc) - ) - or - exists(CommClause cc, RecvStmt recv | cc = this.getCommClause(_) and recv = cc.getComm() | - pred = MkExprNode(recv.getExpr()) and - ( - firstNode(recv.getLhs(0), succ) - or - not exists(recv.getLhs(0)) and - (firstNode(cc.getStmt(0), succ) or succ = MkSkipNode(cc)) - ) - or - lastNode(recv.getLhs(0), pred, normalCompletion()) and - not exists(recv.getLhs(1)) and - ( - succ = MkAssignNode(recv, 0) - or - not exists(MkAssignNode(recv, 0)) and - (firstNode(cc.getStmt(0), succ) or succ = MkSkipNode(cc)) - ) - or - lastNode(recv.getLhs(1), pred, normalCompletion()) and - succ = MkExtractNode(recv, 0) - or - ( - pred = MkAssignNode(recv, 0) and - not exists(MkExtractNode(recv, 1)) - or - pred = MkExtractNode(recv, 1) and - not exists(MkAssignNode(recv, 1)) - or - pred = MkAssignNode(recv, 1) - ) and - (firstNode(cc.getStmt(0), succ) or succ = MkSkipNode(cc)) - ) - or - exists(CommClause cc, SendStmt ss | - cc = this.getCommClause(_) and - ss = cc.getComm() and - pred = MkSendNode(ss) - | - firstNode(cc.getStmt(0), succ) - or - succ = MkSkipNode(cc) - ) - } - } - - private class SelectorExprTree extends ControlFlowTree, SelectorExpr { - SelectorExprTree() { this.getBase() instanceof ValueExpr } - - override predicate firstNode(ControlFlow::Node first) { firstNode(this.getBase(), first) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - // panic due to `nil` dereference - last = MkImplicitDeref(this.getBase()) and - cmpl = Panic() - or - last = mkExprOrSkipNode(this) and - cmpl = Done() - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - exists(int i | pred = this.getStepWithRank(i) and succ = this.getStepWithRank(i + 1)) - } - - private ControlFlow::Node getStepOrdered(int i) { - i = -2 and lastNode(this.getBase(), result, normalCompletion()) - or - i = -1 and result = MkImplicitDeref(this.getBase()) - or - exists(int maxIndex | - maxIndex = max(int k | k = 0 or exists(MkImplicitFieldSelection(this, k, _))) - | - result = MkImplicitFieldSelection(this, maxIndex - i, _) - or - i = maxIndex and - result = mkExprOrSkipNode(this) - ) - } - - private ControlFlow::Node getStepWithRank(int i) { - exists(int j | - result = this.getStepOrdered(j) and - j = rank[i + 1](int k | exists(this.getStepOrdered(k))) - ) - } - } - - private class SendStmtTree extends ControlFlowTree, SendStmt { - override predicate firstNode(ControlFlow::Node first) { firstNode(this.getChannel(), first) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - last = MkSendNode(this) and - (cmpl = Done() or cmpl = Panic()) - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - ControlFlowTree.super.succ0(pred, succ) - or - not this = any(CommClause cc).getComm() and - lastNode(this.getValue(), pred, normalCompletion()) and - succ = MkSendNode(this) - } - - override ControlFlowTree getChildTree(int i) { - i = 0 and result = this.getChannel() - or - i = 1 and result = this.getValue() - } - } - - private class SliceExprTree extends ControlFlowTree, SliceExpr { - override predicate firstNode(ControlFlow::Node first) { firstNode(this.getBase(), first) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - ControlFlowTree.super.lastNode(last, cmpl) - or - // panic due to `nil` dereference - last = MkImplicitDeref(this.getBase()) and - cmpl = Panic() - or - last = MkExprNode(this) and - (cmpl = Done() or cmpl = Panic()) - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - ControlFlowTree.super.succ0(pred, succ) - or - lastNode(this.getBase(), pred, normalCompletion()) and - ( - succ = MkImplicitDeref(this.getBase()) - or - not exists(MkImplicitDeref(this.getBase())) and - (firstNode(this.getLow(), succ) or succ = MkImplicitLowerSliceBound(this)) - ) - or - pred = MkImplicitDeref(this.getBase()) and - (firstNode(this.getLow(), succ) or succ = MkImplicitLowerSliceBound(this)) - or - (lastNode(this.getLow(), pred, normalCompletion()) or pred = MkImplicitLowerSliceBound(this)) and - (firstNode(this.getHigh(), succ) or succ = MkImplicitUpperSliceBound(this)) - or - (lastNode(this.getHigh(), pred, normalCompletion()) or pred = MkImplicitUpperSliceBound(this)) and - (firstNode(this.getMax(), succ) or succ = MkImplicitMaxSliceBound(this)) - or - (lastNode(this.getMax(), pred, normalCompletion()) or pred = MkImplicitMaxSliceBound(this)) and - succ = MkExprNode(this) - } - } - - private class StarExprTree extends PostOrderTree, StarExpr { - override ControlFlow::Node getNode() { result = mkExprOrSkipNode(this) } - - override Completion getCompletion() { result = Done() or result = Panic() } - - override ControlFlowTree getChildTree(int i) { i = 0 and result = this.getBase() } - } - - private class SwitchTree extends ControlFlowTree, SwitchStmt { - override predicate firstNode(ControlFlow::Node first) { - firstNode(this.getInit(), first) - or - not exists(this.getInit()) and - ( - firstNode(this.(ExpressionSwitchStmt).getExpr(), first) - or - first = MkImplicitTrue(this) - or - firstNode(this.(TypeSwitchStmt).getTest(), first) - ) - } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - lastNode(this.getInit(), last, cmpl) and - not cmpl.isNormal() - or - ( - lastNode(this.(ExpressionSwitchStmt).getExpr(), last, cmpl) - or - lastNode(this.(TypeSwitchStmt).getTest(), last, cmpl) - ) and - ( - not cmpl.isNormal() - or - not exists(this.getDefault()) - ) - or - last = MkImplicitTrue(this) and - cmpl = Bool(true) and - this.getNumCase() = 0 - or - exists(CaseClause cc, int i, Completion inner | - cc = this.getCase(i) and lastNode(cc, last, inner) - | - not exists(this.getDefault()) and - i = this.getNumCase() - 1 and - last = cc.(CaseClauseTree).getExprEnd(cc.getNumExpr() - 1, false) and - inner.isNormal() and - cmpl = inner - or - not last = cc.(CaseClauseTree).getExprEnd(_, _) and - inner.isNormal() and - cmpl = inner - or - if inner = Break(BranchTarget::of(this)) - then cmpl = Done() - else ( - not inner.isNormal() and inner != Fallthrough() and cmpl = inner - ) - ) - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - ControlFlowTree.super.succ0(pred, succ) - or - lastNode(this.getInit(), pred, normalCompletion()) and - ( - firstNode(this.(ExpressionSwitchStmt).getExpr(), succ) or - succ = MkImplicitTrue(this) or - firstNode(this.(TypeSwitchStmt).getTest(), succ) - ) - or - ( - lastNode(this.(ExpressionSwitchStmt).getExpr(), pred, normalCompletion()) or - pred = MkImplicitTrue(this) or - lastNode(this.(TypeSwitchStmt).getTest(), pred, normalCompletion()) - ) and - ( - firstNode(this.getNonDefaultCase(0), succ) - or - not exists(this.getANonDefaultCase()) and - firstNode(this.getDefault(), succ) - ) - or - exists(CaseClause cc, int i | - cc = this.getNonDefaultCase(i) and - lastNode(cc, pred, normalCompletion()) and - pred = cc.(CaseClauseTree).getExprEnd(_, false) - | - firstNode(this.getNonDefaultCase(i + 1), succ) - or - i = this.getNumNonDefaultCase() - 1 and - firstNode(this.getDefault(), succ) - ) - or - exists(CaseClause cc, int i, CaseClause next | - cc = this.getCase(i) and - lastNode(cc, pred, Fallthrough()) and - next = this.getCase(i + 1) - | - firstNode(next.getStmt(0), succ) - or - succ = MkSkipNode(next) - ) - } - } - - private class TypeAssertTree extends PostOrderTree, TypeAssertExpr { - override ControlFlow::Node getNode() { result = MkExprNode(this) } - - override Completion getCompletion() { - result = Done() - or - // panic due to type mismatch, but not if the assertion appears in an assignment or - // initialization with two variables or a type-switch - not exists(Assignment assgn | assgn.getNumLhs() = 2 and this = assgn.getRhs().stripParens()) and - not exists(ValueSpec vs | vs.getNumName() = 2 and this = vs.getInit().stripParens()) and - not exists(TypeSwitchStmt ts | this = ts.getExpr()) and - result = Panic() - } - - override ControlFlowTree getChildTree(int i) { i = 0 and result = this.getExpr() } - } - - private class UnaryExprTree extends ControlFlowTree, UnaryExpr { - override predicate firstNode(ControlFlow::Node first) { firstNode(this.getOperand(), first) } - - override predicate lastNode(ControlFlow::Node last, Completion cmpl) { - last = MkExprNode(this) and - ( - cmpl = Done() - or - this instanceof DerefExpr and cmpl = Panic() - ) - } - - pragma[nomagic] - override predicate succ0(ControlFlow::Node pred, ControlFlow::Node succ) { - ControlFlowTree.super.succ0(pred, succ) - or - not this = any(RecvStmt recv).getExpr() and - lastNode(this.getOperand(), pred, normalCompletion()) and - succ = MkExprNode(this) - } - } - - private ControlFlow::Node mkExprOrSkipNode(Expr e) { - result = MkExprNode(e) or - result = MkSkipNode(e) - } - - /** Holds if evaluation of `root` may start at `first`. */ - cached - predicate firstNode(ControlFlowTree root, ControlFlow::Node first) { root.firstNode(first) } - - /** Holds if evaluation of `root` may complete normally after `last`. */ - cached - predicate lastNode(ControlFlowTree root, ControlFlow::Node last) { - lastNode(root, last, normalCompletion()) - } - - private predicate lastNode(ControlFlowTree root, ControlFlow::Node last, Completion cmpl) { - root.lastNode(last, cmpl) - } - - /** Gets a successor of `nd` that is not a `defer` node */ - private ControlFlow::Node notDeferSucc0(ControlFlow::Node nd) { - not result = MkDeferNode(_) and - result = succ0(nd) - } - - /** Gets `defer` statements that can be the first defer statement after `nd` in the CFG */ - private ControlFlow::Node nextDefer(ControlFlow::Node nd) { - nd = MkDeferNode(_) and - result = MkDeferNode(_) and - ( - result = succ0(nd) - or - result = succ0(notDeferSucc0+(nd)) - ) - } - - /** - * Holds if the function `f` may return without panicking, exiting the process, or looping forever. - * - * This is defined conservatively, and so may also hold of a function that in fact - * cannot return normally, but never fails to hold of a function that can return normally. - */ - cached - predicate mayReturnNormally(ControlFlowTree root) { - exists(Completion cmpl | lastNode(root, _, cmpl) and cmpl != Panic()) - } - - /** - * Holds if `pred` is the node for the case `testExpr` in an expression - * switch statement which is switching on `switchExpr`, and `succ` is the - * node to be executed next if the case test succeeds. - */ - cached - predicate isSwitchCaseTestPassingEdge( - ControlFlow::Node pred, ControlFlow::Node succ, Expr switchExpr, Expr testExpr - ) { - exists(ExpressionSwitchStmt ess | ess.getExpr() = switchExpr | - ess.getACase().(CaseClauseTree).isPassingEdge(_, pred, succ, testExpr) - ) - } - - /** - * Gets a successor of `nd`, that is, a node that is executed after `nd`, - * ignoring the execution of any deferred functions when a function ends. - */ - pragma[nomagic] - private ControlFlow::Node succ0(ControlFlow::Node nd) { - any(ControlFlowTree tree).succ0(nd, result) - } - - /** Gets a successor of `nd`, that is, a node that is executed after `nd`. */ - cached - ControlFlow::Node succ(ControlFlow::Node nd) { any(ControlFlowTree tree).succ(nd, result) } -} diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 147468122fd5..70be225eff3d 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -66,7 +66,7 @@ module GoCfg { Callable() { exists(this.(Go::FuncDef).getBody()) } } - AstNode callableGetBody(Callable c) { result = c.(Go::FuncDef).getBody() } + AstNode callableGetBody(Callable c) { result = c } Callable getEnclosingCallable(AstNode node) { result = node and node instanceof Callable @@ -648,7 +648,8 @@ module GoCfg { sliceExprStep(n1, n2) or selectorExprStep(n1, n2) or compositeLitStep(n1, n2) or - sendStmtStep(n1, n2) + sendStmtStep(n1, n2) or + funcDefStep(n1, n2) } /** @@ -967,18 +968,57 @@ module GoCfg { private predicate selectorExprStep(PreControlFlowNode n1, PreControlFlowNode n2) { exists(Go::SelectorExpr sel | sel.getBase() instanceof Go::ValueExpr and - (implicitDerefCondition(sel.getBase()) or exists(Go::Field f | sel = f.getAReference())) and + ( + implicitDerefCondition(sel.getBase()) or + exists(Go::Field f | sel = f.getAReference()) or + implicitFieldSelection(sel, _, _) + ) and ( n1.isBefore(sel) and n2.isBefore(sel.getBase()) or + // After base (no implicit-deref) → first implicit-field or In(sel) + n1.isAfter(sel.getBase()) and + not implicitDerefCondition(sel.getBase()) and + ( + // Has implicit field reads: go to outermost (highest index) + exists(int maxIdx | + maxIdx = max(int i | implicitFieldSelection(sel, i, _)) and + n2.isAdditional(sel, "implicit-field:" + maxIdx.toString()) + ) + or + // No implicit field reads: go directly to In(sel) + not implicitFieldSelection(sel, _, _) and n2.isIn(sel) + ) + or + // After base (has implicit-deref) → implicit-deref node n1.isAfter(sel.getBase()) and + implicitDerefCondition(sel.getBase()) and + n2.isAdditional(sel.getBase(), "implicit-deref") + or + // After implicit-deref → first implicit-field or In(sel) + n1.isAdditional(sel.getBase(), "implicit-deref") and ( - if implicitDerefCondition(sel.getBase()) - then n2.isAdditional(sel.getBase(), "implicit-deref") - else n2.isIn(sel) + exists(int maxIdx | + maxIdx = max(int i | implicitFieldSelection(sel, i, _)) and + n2.isAdditional(sel, "implicit-field:" + maxIdx.toString()) + ) + or + not implicitFieldSelection(sel, _, _) and n2.isIn(sel) + ) + or + // Between implicit field reads: descend from index i to i-1 + exists(int i | + i > 1 and + implicitFieldSelection(sel, i, _) and + implicitFieldSelection(sel, i - 1, _) and + n1.isAdditional(sel, "implicit-field:" + i.toString()) and + n2.isAdditional(sel, "implicit-field:" + (i - 1).toString()) ) or - n1.isAdditional(sel.getBase(), "implicit-deref") and n2.isIn(sel) + // Last implicit field read (index 1) → In(sel) + implicitFieldSelection(sel, 1, _) and + n1.isAdditional(sel, "implicit-field:1") and + n2.isIn(sel) or n1.isIn(sel) and n2.isAfter(sel) ) @@ -1292,5 +1332,100 @@ module GoCfg { n1.isIn(s) and n2.isAfter(s) ) } + + /** + * Function definition prologue and epilogue: + * - Prologue: Before(fd) → arg:0 → param-init:0 → arg:1 → param-init:1 → ... + * → result-zero-init:0 → result-init:0 → ... → Before(body) + * - Epilogue: After(body) → result-read:0 → result-read:1 → ... → After(fd) + * + * `After(fd)` goes to `NormalExit(fd)` via the shared library's built-in step + * (since `callableGetBody(fd) = fd`). + */ + private predicate funcDefStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(Go::FuncDef fd | exists(fd.getBody()) | + // Before(fd) → first prologue node, or Before(body) if no prologue + n1.isBefore(fd) and + ( + // Has parameters: start with arg:0 + exists(fd.getParameter(0)) and n2.isAdditional(fd, "arg:0") + or + // No parameters, has result vars: start with result-zero-init:0 + not exists(fd.getParameter(_)) and + exists(fd.getResultVar(0)) and + n2.isAdditional(fd, "result-zero-init:0") + or + // No parameters and no result vars: go directly to Before(body) + not exists(fd.getParameter(_)) and + not exists(fd.getResultVar(_)) and + n2.isBefore(fd.getBody()) + ) + or + // arg:i → param-init:i (for each parameter) + exists(int i | exists(fd.getParameter(i)) | + n1.isAdditional(fd, "arg:" + i.toString()) and + n2.isAdditional(fd, "param-init:" + i.toString()) + ) + or + // param-init:i → next: arg:(i+1), or result-zero-init:0, or Before(body) + exists(int i | exists(fd.getParameter(i)) | + n1.isAdditional(fd, "param-init:" + i.toString()) and + ( + // Next parameter exists + exists(fd.getParameter(i + 1)) and + n2.isAdditional(fd, "arg:" + (i + 1).toString()) + or + // No next parameter, has result vars: go to result-zero-init:0 + not exists(fd.getParameter(i + 1)) and + exists(fd.getResultVar(0)) and + n2.isAdditional(fd, "result-zero-init:0") + or + // No next parameter and no result vars: go to Before(body) + not exists(fd.getParameter(i + 1)) and + not exists(fd.getResultVar(_)) and + n2.isBefore(fd.getBody()) + ) + ) + or + // result-zero-init:j → result-init:j (for each result variable) + exists(int j | exists(fd.getResultVar(j)) | + n1.isAdditional(fd, "result-zero-init:" + j.toString()) and + n2.isAdditional(fd, "result-init:" + j.toString()) + ) + or + // result-init:j → next: result-zero-init:(j+1), or Before(body) + exists(int j | exists(fd.getResultVar(j)) | + n1.isAdditional(fd, "result-init:" + j.toString()) and + ( + // Next result var exists + exists(fd.getResultVar(j + 1)) and + n2.isAdditional(fd, "result-zero-init:" + (j + 1).toString()) + or + // No next result var: go to Before(body) + not exists(fd.getResultVar(j + 1)) and + n2.isBefore(fd.getBody()) + ) + ) + or + // After(body) → first epilogue or After(fd) if no result vars + n1.isAfter(fd.getBody()) and + ( + exists(fd.getResultVar(0)) and n2.isAdditional(fd, "result-read:0") + or + not exists(fd.getResultVar(_)) and n2.isAfter(fd) + ) + or + // result-read:j → result-read:(j+1) or After(fd) + exists(int j | exists(fd.getResultVar(j)) | + n1.isAdditional(fd, "result-read:" + j.toString()) and + ( + exists(fd.getResultVar(j + 1)) and + n2.isAdditional(fd, "result-read:" + (j + 1).toString()) + or + not exists(fd.getResultVar(j + 1)) and n2.isAfter(fd) + ) + ) + ) + } } } diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll.bak b/go/ql/lib/semmle/go/controlflow/IR.qll.bak deleted file mode 100644 index 807fbf76d1fd..000000000000 --- a/go/ql/lib/semmle/go/controlflow/IR.qll.bak +++ /dev/null @@ -1,1580 +0,0 @@ -/** - * Provides classes and predicates for working with an intermediate representation (IR) of Go - * programs that is used as the foundation of the control flow and data flow graphs. - * - * In the IR, the program is represented as a set of instructions, which correspond to expressions - * and statements that compute a value or perform an operation (as opposed to providing syntactic - * structure or type information). - * - * Each instruction is also a control-flow node, but there are control-flow nodes that are not - * instructions (synthetic entry and exit nodes, as well as before/after nodes). - */ -overlay[local] -module; - -import go -private import ControlFlowGraphShared - -/** Provides predicates and classes for working with IR constructs. */ -module IR { - /** - * An IR instruction. - */ - class Instruction extends ControlFlow::Node { - Instruction() { - this.isIn(_) or - this.isAdditional(_, _) or - this.isAfterTrue(_) and not this.isIn(_) or - this.isAfterFalse(_) and not this.isIn(_) - } - - /** Holds if this instruction reads the value of variable or constant `v`. */ - predicate reads(ValueEntity v) { this.readsField(_, v) or this.readsMethod(_, v) } - - /** Holds if this instruction updates variable or constant `v` to the value of `rhs`. */ - predicate writes(ValueEntity v, Instruction rhs) { this.writesField(_, v, rhs) } - - /** Holds if this instruction reads the value of field `f` on the value of `base`. */ - predicate readsField(Instruction base, Field f) { none() } - - /** Holds if this instruction updates the value of field `f` on the value of `base`. */ - predicate writesField(Instruction base, Field f, Instruction rhs) { none() } - - /** Holds if this instruction looks up method `m` on the value of `receiver`. */ - predicate readsMethod(Instruction receiver, Method m) { none() } - - /** Holds if this instruction reads the value of element `index` on the value of `base`. */ - predicate readsElement(Instruction base, Instruction index) { none() } - - /** Holds if this instruction updates the value of element `index` on the value of `base`. */ - predicate writesElement(Instruction base, Instruction index) { none() } - - /** Gets the type of the result of this instruction, if any. */ - Type getResultType() { none() } - - /** Gets the float value of the result of this instruction, if it can be determined. */ - float getFloatValue() { none() } - - /** Gets the int value of the result of this instruction, if it can be determined. */ - int getIntValue() { none() } - - /** - * Holds if the complex value of the result of this instruction has real part `real` and - * imaginary part `imag`. - */ - predicate hasComplexValue(float real, float imag) { none() } - - /** Gets either `getFloatValue` or `getIntValue` */ - float getNumericValue() { result = this.getFloatValue() or result = this.getIntValue() } - - /** - * Gets the string representation of the exact value of the result of this instruction, - * if any. - * - * For example, for the constant 3.141592653589793238462, this will - * result in 1570796326794896619231/500000000000000000000 - */ - string getExactValue() { none() } - - /** Gets the string value of the result of this instruction, if it can be determined. */ - string getStringValue() { none() } - - /** Gets the Boolean value of the result of this instruction, if it can be determined. */ - boolean getBoolValue() { none() } - - /** Holds if the result of this instruction is known at compile time. */ - predicate isConst() { none() } - - /** - * Holds if the result of this instruction is known at compile time, and is guaranteed not to - * depend on the platform where it is evaluated. - */ - predicate isPlatformIndependentConstant() { none() } - - /** Gets a textual representation of the kind of this instruction. */ - string getInsnKind() { - this instanceof EvalInstruction and result = "expression" - or - this instanceof InitLiteralComponentInstruction and result = "element init" - or - this instanceof ImplicitLiteralElementIndexInstruction and result = "element index" - or - this instanceof AssignInstruction and result = "assignment" - or - this instanceof EvalCompoundAssignRhsInstruction and - result = "right-hand side of compound assignment" - or - this instanceof ExtractTupleElementInstruction and result = "tuple element extraction" - or - this instanceof EvalImplicitInitInstruction and result = "zero value" - or - this instanceof DeclareFunctionInstruction and result = "function declaration" - or - this instanceof DeferInstruction and result = "defer" - or - this instanceof GoInstruction and result = "go" - or - this instanceof ConditionGuardInstruction and result = "condition guard" - or - this instanceof IncDecInstruction and result = "increment/decrement" - or - this instanceof EvalIncDecRhsInstruction and - result = "right-hand side of increment/decrement" - or - this instanceof EvalImplicitOneInstruction and result = "implicit 1" - or - this instanceof ReturnInstruction and result = "return" - or - this instanceof WriteResultInstruction and result = "result write" - or - this instanceof ReadResultInstruction and result = "result read" - or - this instanceof SendInstruction and result = "send" - or - this instanceof InitParameterInstruction and result = "parameter initialization" - or - this instanceof ReadArgumentInstruction and result = "argument" - or - this instanceof InitResultInstruction and result = "result initialization" - or - this instanceof GetNextEntryInstruction and result = "next key-value pair" - or - this instanceof EvalImplicitTrueInstruction and result = "implicit true" - or - this instanceof CaseInstruction and result = "case" - or - this instanceof TypeSwitchImplicitVariableInstruction and - result = "type switch implicit variable declaration" - or - this instanceof EvalImplicitLowerSliceBoundInstruction and result = "implicit lower bound" - or - this instanceof EvalImplicitUpperSliceBoundInstruction and result = "implicit upper bound" - or - this instanceof EvalImplicitMaxSliceBoundInstruction and result = "implicit maximum" - or - this instanceof EvalImplicitDerefInstruction and result = "implicit dereference" - or - this instanceof ImplicitFieldReadInstruction and result = "implicit field selection" - } - } - - /** A condition guard instruction, representing a known boolean outcome for a condition. */ - private class ConditionGuardInstruction extends Instruction { - ConditionGuardInstruction() { - this.isAfterTrue(_) and not this.isIn(_) - or - this.isAfterFalse(_) and not this.isIn(_) - } - } - - /** - * An IR instruction representing the evaluation of an expression. - */ - class EvalInstruction extends Instruction { - Expr e; - - EvalInstruction() { this.isIn(e) and e instanceof Expr } - - /** Gets the expression underlying this instruction. */ - Expr getExpr() { result = e } - - override predicate reads(ValueEntity v) { e = v.getAReference() } - - override Type getResultType() { result = e.getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(e) } - - override float getFloatValue() { result = e.getFloatValue() } - - override int getIntValue() { result = e.getIntValue() } - - override predicate hasComplexValue(float real, float imag) { e.hasComplexValue(real, imag) } - - override string getExactValue() { result = e.getExactValue() } - - override string getStringValue() { result = e.getStringValue() } - - override boolean getBoolValue() { result = e.getBoolValue() } - - override predicate isConst() { e.isConst() } - - override predicate isPlatformIndependentConstant() { e.isPlatformIndependentConstant() } - - override string toString() { result = e.toString() } - - override Location getLocation() { result = e.getLocation() } - } - - /** - * An IR instruction that reads the value of a variable, constant, field or array element, - * or refers to a function. - */ - class ReadInstruction extends Instruction { - ReadInstruction() { - exists(Expr e | e = this.(EvalInstruction).getExpr() | - (e instanceof ValueName or e instanceof IndexExpr) and - e.(ReferenceExpr).isRvalue() - ) - or - this instanceof ReadResultInstruction - or - this instanceof ImplicitFieldReadInstruction - } - } - - /** - * Gets the effective base of a selector, index or slice expression, taking implicit dereferences - * and implicit field reads into account. - * - * For a selector expression `b.f`, this could be the implicit dereference `*b`, or the implicit - * field access `b.Embedded` if the field `f` is promoted from an embedded type `Embedded`, or a - * combination of both `*(b.Embedded)`, or simply `b` if neither case applies. - */ - private Instruction selectorBase(Expr e) { - exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1 | - result = fri - ) - or - not exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1) and - exists(Expr base | - base = e.(SelectorExpr).getBase() - or - base = e.(IndexExpr).getBase() - or - base = e.(SliceExpr).getBase() - | - result = MkImplicitDeref(base) - or - not exists(MkImplicitDeref(base)) and - result = evalExprInstruction(base) - ) - } - - /** - * An IR instruction that reads a component from a composite object. - * - * This is either a field of a struct, or an element of an array, map, slice or string. - */ - class ComponentReadInstruction extends ReadInstruction { - ComponentReadInstruction() { - exists(Expr e | e = this.(EvalInstruction).getExpr() | - e instanceof IndexExpr - or - e.(SelectorExpr).getBase() instanceof ValueExpr and - not e.(SelectorExpr).getSelector() = any(Method method).getAReference() - ) - or - this instanceof MkImplicitFieldSelection - } - - /** Gets the instruction computing the base value on which the field or element is read. */ - Instruction getBase() { - result = this.(ImplicitFieldReadInstruction).getBaseInstruction() - or - result = selectorBase(this.(EvalInstruction).getExpr()) - } - } - - /** - * An IR instruction that reads the value of a field. - * - * On databases with incomplete type information, method expressions may sometimes be - * misclassified as field reads. - */ - class FieldReadInstruction extends ComponentReadInstruction { - SelectorExpr e; - int index; - Field field; - - FieldReadInstruction() { - e = this.(EvalInstruction).getExpr() and - index = 0 and - field.getAReference() = e.getSelector() - or - this = MkImplicitFieldSelection(e, index, field) - } - - /** Gets the `SelectorExpr` of this field read. */ - SelectorExpr getSelectorExpr() { result = e } - - /** Gets the index of this field read. */ - int getIndex() { result = index } - - /** Gets the field being read. */ - Field getField() { result = field } - - Instruction getBaseInstruction() { - exists(ImplicitFieldReadInstruction fri | - fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) - | - result = fri - ) - or - not exists(ImplicitFieldReadInstruction fri | - fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) - ) and - ( - result = MkImplicitDeref(e.getBase()) - or - not exists(MkImplicitDeref(e.getBase())) and - result = evalExprInstruction(e.getBase()) - ) - } - - override predicate readsField(Instruction base, Field f) { - base = this.getBaseInstruction() and f = field - } - } - - /** - * An IR instruction for an implicit field read as part of reading a - * promoted field. - * - * If the field that is being implicitly read has a pointer type then this - * instruction represents an implicit dereference of it. - */ - class ImplicitFieldReadInstruction extends FieldReadInstruction, MkImplicitFieldSelection { - ImplicitFieldReadInstruction() { this = MkImplicitFieldSelection(e, index, field) } - - override predicate reads(ValueEntity v) { v = field } - - override Type getResultType() { result = lookThroughPointerType(field.getType()) } - - override ControlFlow::Root getRoot() { result.isRootOf(e) } - - override string toString() { result = "implicit read of field " + field.toString() } - - override Location getLocation() { result = e.getBase().getLocation() } - } - - /** - * An IR instruction that looks up a method. - */ - class MethodReadInstruction extends ReadInstruction, EvalInstruction { - Method method; - override SelectorExpr e; - - MethodReadInstruction() { e.getSelector() = method.getAReference() } - - /** Gets the instruction computing the receiver value on which the method is looked up. */ - Instruction getReceiver() { result = selectorBase(e) } - - /** Gets the method being looked up. */ - Method getMethod() { result = method } - - override predicate readsMethod(Instruction receiver, Method m) { - receiver = this.getReceiver() and m = this.getMethod() - } - } - - /** - * An IR instruction that reads an element of an array, slice, map or string. - */ - class ElementReadInstruction extends ComponentReadInstruction, EvalInstruction { - override IndexExpr e; - - /** Gets the instruction computing the index of the element being looked up. */ - Instruction getIndex() { result = evalExprInstruction(e.getIndex()) } - - override predicate readsElement(Instruction base, Instruction index) { - base = this.getBase() and index = this.getIndex() - } - } - - /** - * An IR instruction that constructs a slice. - */ - class SliceInstruction extends EvalInstruction { - override SliceExpr e; - - /** Gets the instruction computing the base value from which the slice is constructed. */ - Instruction getBase() { result = selectorBase(e) } - - /** Gets the instruction computing the lower bound of the slice. */ - Instruction getLow() { - result = evalExprInstruction(e.getLow()) or - result = implicitLowerSliceBoundInstruction(e) - } - - /** Gets the instruction computing the upper bound of the slice. */ - Instruction getHigh() { - result = evalExprInstruction(e.getHigh()) or - result = implicitUpperSliceBoundInstruction(e) - } - - /** Gets the instruction computing the capacity of the slice. */ - Instruction getMax() { - result = evalExprInstruction(e.getMax()) or - result = implicitMaxSliceBoundInstruction(e) - } - } - - /** - * An IR instruction that writes a memory location. - */ - class WriteInstruction extends Instruction { - WriteTarget lhs; - Boolean initialization; - - WriteInstruction() { - ( - lhs = MkLhs(this, _) - or - lhs = MkResultWriteTarget(this) - ) and - initialization = false - or - lhs = MkLiteralElementTarget(this) and initialization = true - } - - /** Gets the target to which this instruction writes. */ - WriteTarget getLhs() { result = lhs } - - /** Holds if this instruction initializes a literal. */ - predicate isInitialization() { initialization = true } - - /** Gets the instruction computing the value this instruction writes. */ - Instruction getRhs() { none() } - - override predicate writes(ValueEntity v, Instruction rhs) { - this.getLhs().refersTo(v) and - rhs = this.getRhs() - } - } - - /** - * An IR instruction that initializes a component of a composite literal. - */ - class InitLiteralComponentInstruction extends WriteInstruction, MkLiteralElementInitNode { - CompositeLit lit; - int i; - Expr elt; - - InitLiteralComponentInstruction() { - this = MkLiteralElementInitNode(elt) and elt = lit.getElement(i) - } - - /** Gets the instruction allocating the composite literal. */ - Instruction getBase() { result = evalExprInstruction(lit) } - - override Instruction getRhs() { - result = evalExprInstruction(elt) or - result = evalExprInstruction(elt.(KeyValueExpr).getValue()) - } - - override ControlFlow::Root getRoot() { result.isRootOf(elt) } - - override string toString() { result = "init of " + elt } - - override Location getLocation() { result = elt.getLocation() } - } - - /** - * An IR instruction that initializes a field of a struct literal. - */ - class InitLiteralStructFieldInstruction extends InitLiteralComponentInstruction { - override StructLit lit; - - /** Gets the name of the initialized field. */ - pragma[nomagic] - string getFieldName() { - if elt instanceof KeyValueExpr - then result = elt.(KeyValueExpr).getKey().(Ident).getName() - else pragma[only_bind_out](lit.getStructType()).hasOwnField(i, result, _, _) - } - - /** Gets the initialized field. */ - Field getField() { - result.getDeclaringType() = lit.getStructType() and - result.getName() = this.getFieldName() - } - } - - /** - * An IR instruction that initializes an element of an array, slice or map literal. - */ - class InitLiteralElementInstruction extends InitLiteralComponentInstruction { - Type literalType; - - InitLiteralElementInstruction() { - literalType = lit.getType().getUnderlyingType() and - ( - literalType instanceof ArrayType or - literalType instanceof SliceType or - literalType instanceof MapType - ) - } - - /** Gets the instruction computing the index of the initialized element. */ - Instruction getIndex() { - result = evalExprInstruction(elt.(KeyValueExpr).getKey()) - or - result = MkImplicitLiteralElementIndex(elt) - } - } - - /** - * An IR instruction that initializes an element of an array literal. - */ - class InitLiteralArrayElementInstruction extends InitLiteralElementInstruction { - override ArrayType literalType; - } - - /** - * An IR instruction that initializes an element of a slice literal. - */ - class InitLiteralSliceElementInstruction extends InitLiteralElementInstruction { - override SliceType literalType; - } - - /** - * An IR instruction that initializes an element of a map literal. - */ - class InitLiteralMapElementInstruction extends InitLiteralElementInstruction { - override MapType literalType; - } - - /** - * An IR instruction that writes to a field. - */ - class FieldWriteInstruction extends WriteInstruction { - override FieldTarget lhs; - - /** Gets the instruction computing the base value on which the field is written. */ - Instruction getBase() { result = lhs.getBase() } - - /** Gets the field being written. */ - Field getField() { result = lhs.getField() } - - override predicate writesField(Instruction base, Field f, Instruction rhs) { - this.getBase() = base and - this.getField() = f and - this.getRhs() = rhs - } - } - - /** - * An IR instruction that writes to an element of an array, slice, or map. - */ - class ElementWriteInstruction extends WriteInstruction { - override ElementTarget lhs; - - /** Gets the instruction computing the base value on which the field is written. */ - Instruction getBase() { result = lhs.getBase() } - - /** Gets the instruction computing the element index being written. */ - Instruction getIndex() { result = lhs.getIndex() } - - override predicate writesElement(Instruction base, Instruction index) { - this.getBase() = base and - this.getIndex() = index - } - } - - /** Holds if `lit` does not specify any explicit keys. */ - private predicate noExplicitKeys(CompositeLit lit) { - not lit.getAnElement() instanceof KeyValueExpr - } - - /** Gets the index of the `i`th element in (array or slice) literal `lit`. */ - private int getElementIndex(CompositeLit lit, int i) { - ( - lit.getType().getUnderlyingType() instanceof ArrayType or - lit.getType().getUnderlyingType() instanceof SliceType - ) and - exists(Expr elt | elt = lit.getElement(i) | - // short-circuit computation for literals without any explicit keys - noExplicitKeys(lit) and result = i - or - result = elt.(KeyValueExpr).getKey().getIntValue() - or - not elt instanceof KeyValueExpr and - ( - i = 0 and result = 0 - or - result = getElementIndex(lit, i - 1) + 1 - ) - ) - } - - /** - * An IR instruction computing the implicit index of an element in an array or slice literal. - */ - class ImplicitLiteralElementIndexInstruction extends Instruction, MkImplicitLiteralElementIndex { - Expr elt; - - ImplicitLiteralElementIndexInstruction() { this = MkImplicitLiteralElementIndex(elt) } - - override Type getResultType() { result instanceof IntType } - - override ControlFlow::Root getRoot() { result.isRootOf(elt) } - - override int getIntValue() { - exists(CompositeLit lit, int i | elt = lit.getElement(i) | result = getElementIndex(lit, i)) - } - - override string getStringValue() { none() } - - override string getExactValue() { result = this.getIntValue().toString() } - - override predicate isPlatformIndependentConstant() { any() } - - override predicate isConst() { any() } - - override string toString() { result = "element index" } - - override Location getLocation() { result = elt.getLocation() } - } - - /** - * An instruction assigning to a variable or field. - */ - class AssignInstruction extends WriteInstruction, MkAssignNode { - AstNode assgn; - int i; - - AssignInstruction() { this = MkAssignNode(assgn, i) } - - override Instruction getRhs() { - exists(SimpleAssignStmt a | a = assgn | - a.getNumLhs() = a.getNumRhs() and - result = evalExprInstruction(a.getRhs(i)) - ) - or - exists(ValueSpec spec | spec = assgn | - spec.getNumName() = spec.getNumInit() and - result = evalExprInstruction(spec.getInit(i)) - or - result = MkZeroInitNode(any(ValueEntity v | spec.getNameExpr(i) = v.getDeclaration())) - ) - or - result = MkCompoundAssignRhsNode(assgn) - or - result = MkExtractNode(assgn, i) - } - - override ControlFlow::Root getRoot() { result.isRootOf(assgn) } - - override string toString() { result = "assignment to " + this.getLhs() } - - override Location getLocation() { result = this.getLhs().getLocation() } - } - - /** An instruction computing the value of the right-hand side of a compound assignment. */ - class EvalCompoundAssignRhsInstruction extends Instruction, MkCompoundAssignRhsNode { - CompoundAssignStmt assgn; - - EvalCompoundAssignRhsInstruction() { this = MkCompoundAssignRhsNode(assgn) } - - /** Gets the underlying assignment of this instruction. */ - CompoundAssignStmt getAssignment() { result = assgn } - - override Type getResultType() { result = assgn.getRhs().getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(assgn) } - - override string toString() { result = assgn.toString() } - - override Location getLocation() { result = assgn.getLocation() } - } - - /** - * An instruction selecting one of multiple values returned by a function, or either the key - * or the value of the iterator in a range loop, or the result or success value from a type - * assertion. - */ - class ExtractTupleElementInstruction extends Instruction, MkExtractNode { - AstNode s; - int i; - - ExtractTupleElementInstruction() { this = MkExtractNode(s, i) } - - /** Gets the instruction computing the tuple value from which one value is extracted. */ - Instruction getBase() { - exists(Expr baseExpr | - baseExpr = s.(Assignment).getRhs() or - baseExpr = s.(ValueSpec).getInit() - | - result = evalExprInstruction(baseExpr) - ) - or - result = MkNextNode(s) - or - result = evalExprInstruction(s.(ReturnStmt).getExpr()) - or - result = evalExprInstruction(s.(CallExpr).getArgument(0).stripParens()) - } - - /** Holds if this extracts the `idx`th value of the result of `base`. */ - predicate extractsElement(Instruction base, int idx) { base = this.getBase() and idx = i } - - override Type getResultType() { - exists(Expr e | this.getBase() = evalExprInstruction(e) | - result = e.getType().(TupleType).getComponentType(pragma[only_bind_into](i)) - ) - or - exists(Type rangeType | rangeType = s.(RangeStmt).getDomain().getType().getUnderlyingType() | - exists(Type baseType | - baseType = rangeType.(ArrayType).getElementType() or - baseType = - rangeType.(PointerType).getBaseType().getUnderlyingType().(ArrayType).getElementType() or - baseType = rangeType.(SliceType).getElementType() - | - i = 0 and - result instanceof IntType - or - i = 1 and - result = baseType - ) - or - rangeType instanceof StringType and - ( - i = 0 and - result instanceof IntType - or - result = Builtin::rune().getType() - ) - or - exists(MapType map | map = rangeType | - i = 0 and - result = map.getKeyType() - or - i = 1 and - result = map.getValueType() - ) - or - i = 0 and - result = rangeType.(RecvChanType).getElementType() - or - i = 0 and - result = rangeType.(SendRecvChanType).getElementType() - ) - } - - override ControlFlow::Root getRoot() { result.isRootOf(s) } - - override string toString() { result = s + "[" + i + "]" } - - override Location getLocation() { result = s.getLocation() } - } - - /** - * An instruction that computes the zero value for a variable or constant. - */ - class EvalImplicitInitInstruction extends Instruction, MkZeroInitNode { - ValueEntity v; - - EvalImplicitInitInstruction() { this = MkZeroInitNode(v) } - - override Type getResultType() { result = v.getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(v.getDeclaration()) } - - override int getIntValue() { - v.getType().getUnderlyingType() instanceof IntegerType and result = 0 - } - - override float getFloatValue() { - v.getType().getUnderlyingType() instanceof FloatType and result = 0.0 - } - - override string getStringValue() { - v.getType().getUnderlyingType() instanceof StringType and result = "" - } - - override boolean getBoolValue() { - v.getType().getUnderlyingType() instanceof BoolType and result = false - } - - override string getExactValue() { - result = this.getIntValue().toString() or - result = this.getFloatValue().toString() or - result = this.getStringValue().toString() or - result = this.getBoolValue().toString() - } - - override predicate isConst() { any() } - - override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "zero value for " + v } - - override Location getLocation() { result = v.getDeclaration().getLocation() } - } - - /** - * An instruction that corresponds to the declaration of a function. - */ - class DeclareFunctionInstruction extends Instruction, MkFuncDeclNode { - FuncDecl fd; - - DeclareFunctionInstruction() { this = MkFuncDeclNode(fd) } - - override Type getResultType() { result = fd.getType() } - - override string toString() { result = fd.toString() } - - override Location getLocation() { result = fd.getLocation() } - } - - /** - * An instruction that corresponds to a `defer` statement. - */ - class DeferInstruction extends Instruction, MkDeferNode { - DeferStmt defer; - - DeferInstruction() { this = MkDeferNode(defer) } - - override ControlFlow::Root getRoot() { result.isRootOf(defer) } - - override string toString() { result = defer.toString() } - - override Location getLocation() { result = defer.getLocation() } - } - - /** - * An instruction that corresponds to a `go` statement. - */ - class GoInstruction extends Instruction, MkGoNode { - GoStmt go; - - GoInstruction() { this = MkGoNode(go) } - - override ControlFlow::Root getRoot() { result.isRootOf(go) } - - override string toString() { result = go.toString() } - - override Location getLocation() { result = go.getLocation() } - } - - /** - * An instruction that corresponds to an increment or decrement statement. - */ - class IncDecInstruction extends WriteInstruction, MkIncDecNode { - IncDecStmt ids; - - IncDecInstruction() { this = MkIncDecNode(ids) } - - override Instruction getRhs() { result = MkIncDecRhs(ids) } - - override ControlFlow::Root getRoot() { result.isRootOf(ids) } - - override string toString() { result = ids.toString() } - - override Location getLocation() { result = ids.getLocation() } - } - - /** - * An instruction that computes the (implicit) right-hand side of an increment or - * decrement statement. - */ - class EvalIncDecRhsInstruction extends Instruction, MkIncDecRhs { - IncDecStmt ids; - - EvalIncDecRhsInstruction() { this = MkIncDecRhs(ids) } - - /** Gets the corresponding increment or decrement statement. */ - IncDecStmt getStmt() { result = ids } - - override Type getResultType() { result = ids.getOperand().getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(ids) } - - override string toString() { result = "rhs of " + ids } - - override Location getLocation() { result = ids.getLocation() } - } - - /** - * An instruction computing the implicit operand `1` in an increment or decrement statement. - */ - class EvalImplicitOneInstruction extends Instruction, MkImplicitOne { - IncDecStmt ids; - - EvalImplicitOneInstruction() { this = MkImplicitOne(ids) } - - /** Gets the corresponding increment or decrement statement. */ - IncDecStmt getStmt() { result = ids } - - override Type getResultType() { result = ids.getOperand().getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(ids) } - - override int getIntValue() { result = 1 } - - override string getExactValue() { result = "1" } - - override predicate isConst() { any() } - - override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "1" } - - override Location getLocation() { result = ids.getLocation() } - } - - /** - * An instruction corresponding to a return from a function. - */ - class ReturnInstruction extends Instruction, MkReturnNode { - ReturnStmt ret; - - ReturnInstruction() { this = MkReturnNode(ret) } - - /** Gets the corresponding `ReturnStmt`. */ - ReturnStmt getReturnStmt() { result = ret } - - /** Holds if this statement returns multiple results. */ - predicate returnsMultipleResults() { exists(MkExtractNode(ret, _)) or ret.getNumExpr() > 1 } - - /** Gets the instruction whose result is the (unique) result returned by this statement. */ - Instruction getResult() { - not this.returnsMultipleResults() and - result = evalExprInstruction(ret.getExpr()) - } - - /** Gets the instruction whose result is the `i`th result returned by this statement. */ - Instruction getResult(int i) { - result = MkExtractNode(ret, i) - or - not exists(MkExtractNode(ret, _)) and - result = evalExprInstruction(ret.getExpr(i)) - } - - override ControlFlow::Root getRoot() { result.isRootOf(ret) } - - override string toString() { result = ret.toString() } - - override Location getLocation() { result = ret.getLocation() } - } - - /** - * An instruction that represents the implicit assignment to a result variable - * performed by a return statement. - */ - class WriteResultInstruction extends WriteInstruction, MkResultWriteNode { - ResultVariable var; - int i; - ReturnInstruction ret; - - WriteResultInstruction() { - exists(ReturnStmt retstmt | - this = MkResultWriteNode(var, i, retstmt) and - ret = MkReturnNode(retstmt) - ) - } - - override Instruction getRhs() { result = ret.getResult(i) } - - /** Gets the result variable being assigned. */ - ResultVariable getResultVariable() { result = var } - - override Type getResultType() { result = var.getType() } - - override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } - - override string toString() { result = "implicit write of " + var } - - override Location getLocation() { result = ret.getResult(i).getLocation() } - } - - /** - * An instruction that reads the final value of a result variable upon returning - * from a function. - */ - class ReadResultInstruction extends Instruction, MkResultReadNode { - ResultVariable var; - - ReadResultInstruction() { this = MkResultReadNode(var) } - - override predicate reads(ValueEntity v) { v = var } - - override Type getResultType() { result = var.getType() } - - override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } - - override string toString() { result = "implicit read of " + var } - - override Location getLocation() { result = var.getDeclaration().getLocation() } - } - - /** - * An instruction corresponding to a `select` statement. - */ - class SelectInstruction extends Instruction, MkSelectNode { - SelectStmt sel; - - SelectInstruction() { this = MkSelectNode(sel) } - - override ControlFlow::Root getRoot() { result.isRootOf(sel) } - - override string toString() { result = sel.toString() } - - override Location getLocation() { result = sel.getLocation() } - } - - /** - * An instruction corresponding to a send statement. - */ - class SendInstruction extends Instruction, MkSendNode { - SendStmt send; - - SendInstruction() { this = MkSendNode(send) } - - override ControlFlow::Root getRoot() { result.isRootOf(send) } - - override string toString() { result = send.toString() } - - override Location getLocation() { result = send.getLocation() } - } - - /** - * An instruction initializing a parameter to the corresponding argument. - */ - class InitParameterInstruction extends WriteInstruction, MkParameterInit { - Parameter parm; - - InitParameterInstruction() { this = MkParameterInit(parm) } - - override Instruction getRhs() { result = MkArgumentNode(parm) } - - override ControlFlow::Root getRoot() { result = parm.getFunction() } - - override string toString() { result = "initialization of " + parm } - - override Location getLocation() { result = parm.getDeclaration().getLocation() } - } - - /** - * An instruction reading the value of a function argument. - */ - class ReadArgumentInstruction extends Instruction, MkArgumentNode { - Parameter parm; - - ReadArgumentInstruction() { this = MkArgumentNode(parm) } - - override Type getResultType() { result = parm.getType() } - - override ControlFlow::Root getRoot() { result = parm.getFunction() } - - override string toString() { result = "argument corresponding to " + parm } - - override Location getLocation() { result = parm.getDeclaration().getLocation() } - } - - /** - * An instruction initializing a result variable to its zero value. - */ - class InitResultInstruction extends WriteInstruction, MkResultInit { - ResultVariable res; - - InitResultInstruction() { this = MkResultInit(res) } - - override Instruction getRhs() { result = MkZeroInitNode(res) } - - override ControlFlow::Root getRoot() { result = res.getFunction() } - - override string toString() { result = "initialization of " + res } - - override Location getLocation() { result = res.getDeclaration().getLocation() } - } - - /** - * An instruction that gets the next key-value pair in a range loop. - */ - class GetNextEntryInstruction extends Instruction, MkNextNode { - RangeStmt rs; - - GetNextEntryInstruction() { this = MkNextNode(rs) } - - /** - * Gets the instruction computing the value whose key-value pairs this instruction reads. - */ - Instruction getDomain() { result = evalExprInstruction(rs.getDomain()) } - - override ControlFlow::Root getRoot() { result.isRootOf(rs) } - - override string toString() { result = "next key-value pair in range" } - - override Location getLocation() { result = rs.getDomain().getLocation() } - } - - /** - * An instruction computing the implicit `true` value in an expression-less `switch` statement. - */ - class EvalImplicitTrueInstruction extends Instruction, MkImplicitTrue { - Stmt stmt; - - EvalImplicitTrueInstruction() { this = MkImplicitTrue(stmt) } - - override Type getResultType() { result instanceof BoolType } - - override ControlFlow::Root getRoot() { result.isRootOf(stmt) } - - override boolean getBoolValue() { result = true } - - override string getExactValue() { result = "true" } - - override predicate isConst() { any() } - - override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "true" } - - override Location getLocation() { result = stmt.getLocation() } - } - - /** - * An instruction corresponding to the implicit comparison or type check performed by an - * expression in a `case` clause. - * - * For example, consider this `switch` statement: - * - * ```go - * switch x { - * case 2, y+1: - * ... - * } - * ``` - * - * The expressions `2` and `y+1` are implicitly compared to `x`. These comparisons are - * represented by case instructions. - */ - class CaseInstruction extends Instruction, MkCaseCheckNode { - CaseClause cc; - int i; - - CaseInstruction() { this = MkCaseCheckNode(cc, i) } - - override ControlFlow::Root getRoot() { result.isRootOf(cc) } - - override string toString() { result = "case " + cc.getExpr(i) } - - override Location getLocation() { result = cc.getExpr(i).getLocation() } - } - - /** - * An instruction corresponding to the implicit declaration of the variable - * `lv` in case clause `cc` and its assignment of the value `switchExpr` from - * the guard. This only occurs in case clauses in a type switch statement - * which declares a variable in its guard. - * - * For example, consider this type switch statement: - * - * ```go - * switch y := x.(type) { - * case Type1: - * f(y) - * ... - * } - * ``` - * - * The `y` inside the case clause is actually a local variable with type - * `Type1` that is implicitly declared at the top of the case clause. In - * default clauses and case clauses which list more than one type, the type - * of the implicitly declared variable is the type of `switchExpr`. - */ - class TypeSwitchImplicitVariableInstruction extends Instruction, MkTypeSwitchImplicitVariable { - CaseClause cc; - LocalVariable lv; - Expr switchExpr; - - TypeSwitchImplicitVariableInstruction() { - this = MkTypeSwitchImplicitVariable(cc, lv, switchExpr) - } - - override predicate writes(ValueEntity v, Instruction rhs) { - v = lv and - rhs = evalExprInstruction(switchExpr) - } - - override ControlFlow::Root getRoot() { result.isRootOf(cc) } - - override string toString() { result = "implicit type switch variable declaration" } - - override Location getLocation() { result = cc.getLocation() } - } - - /** - * An instruction computing the implicit lower slice bound of zero in a slice expression without - * an explicit lower bound. - */ - class EvalImplicitLowerSliceBoundInstruction extends Instruction, MkImplicitLowerSliceBound { - SliceExpr slice; - - EvalImplicitLowerSliceBoundInstruction() { this = MkImplicitLowerSliceBound(slice) } - - override Type getResultType() { result instanceof IntType } - - override ControlFlow::Root getRoot() { result.isRootOf(slice) } - - override int getIntValue() { result = 0 } - - override string getExactValue() { result = "0" } - - override predicate isConst() { any() } - - override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "0" } - - override Location getLocation() { result = slice.getLocation() } - } - - /** - * An instruction computing the implicit upper slice bound in a slice expression without an - * explicit upper bound. - */ - class EvalImplicitUpperSliceBoundInstruction extends Instruction, MkImplicitUpperSliceBound { - SliceExpr slice; - - EvalImplicitUpperSliceBoundInstruction() { this = MkImplicitUpperSliceBound(slice) } - - override ControlFlow::Root getRoot() { result.isRootOf(slice) } - - override Type getResultType() { result instanceof IntType } - - override string toString() { result = "len" } - - override Location getLocation() { result = slice.getLocation() } - } - - /** - * An instruction computing the implicit maximum slice bound in a slice expression without an - * explicit maximum bound. - */ - class EvalImplicitMaxSliceBoundInstruction extends Instruction, MkImplicitMaxSliceBound { - SliceExpr slice; - - EvalImplicitMaxSliceBoundInstruction() { this = MkImplicitMaxSliceBound(slice) } - - override ControlFlow::Root getRoot() { result.isRootOf(slice) } - - override Type getResultType() { result instanceof IntType } - - override string toString() { result = "cap" } - - override Location getLocation() { result = slice.getLocation() } - } - - /** - * An instruction implicitly dereferencing the base in a field or method reference through a - * pointer, or the base in an element or slice reference through a pointer. - */ - class EvalImplicitDerefInstruction extends Instruction, MkImplicitDeref { - Expr e; - - EvalImplicitDerefInstruction() { this = MkImplicitDeref(e) } - - /** Gets the operand that is being dereferenced. */ - Expr getOperand() { result = e } - - override Type getResultType() { - result = e.getType().getUnderlyingType().(PointerType).getBaseType() - } - - override ControlFlow::Root getRoot() { result.isRootOf(e) } - - override string toString() { result = "implicit dereference" } - - override Location getLocation() { result = e.getLocation() } - } - - /** A representation of the target of a write instruction. */ - class WriteTarget extends TWriteTarget { - ControlFlow::Node w; - - WriteTarget() { - this = MkLhs(w, _) or this = MkLiteralElementTarget(w) or this = MkResultWriteTarget(w) - } - - /** Gets the write instruction of which this is the target. */ - WriteInstruction getWrite() { result = w } - - /** Gets the name of the variable or field being written to, if any. */ - string getName() { none() } - - /** Gets the SSA variable being written to, if any. */ - SsaVariable asSsaVariable() { - this.getWrite() = result.getDefinition().(SsaExplicitDefinition).getInstruction() - } - - /** Holds if `e` is the variable or field being written to. */ - predicate refersTo(ValueEntity e) { none() } - - /** Gets a textual representation of this target. */ - string toString() { result = "write target" } - - /** Gets the source location for this element. */ - Location getLocation() { none() } - - /** - * DEPRECATED: Use `getLocation()` instead. - * - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). - */ - deprecated predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - or - not exists(this.getLocation()) and - filepath = "" and - startline = 0 and - startcolumn = 0 and - endline = 0 and - endcolumn = 0 - } - } - - /** A reference to a variable or constant, used as the target of a write. */ - class VarOrConstTarget extends WriteTarget { - Expr loc; - - VarOrConstTarget() { - this = MkLhs(_, loc) and - ( - loc instanceof Ident - or - loc instanceof SelectorExpr and - not loc.(SelectorExpr).getBase() instanceof ReferenceExpr - ) - or - exists(WriteResultInstruction wr | - this = MkResultWriteTarget(wr) and - evalExprInstruction(loc) = wr.getRhs() - ) - } - - override predicate refersTo(ValueEntity e) { - this instanceof MkLhs and - pragma[only_bind_out](loc) = e.getAReference() - or - exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | - e = wr.getResultVariable() - ) - } - - override string getName() { - this = MkLhs(_, loc) and - ( - result = loc.(Ident).getName() - or - result = loc.(SelectorExpr).getSelector().getName() - ) - or - exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | - result = wr.getResultVariable().getName() - ) - } - - /** Gets the variable this refers to, if any. */ - Variable getVariable() { this.refersTo(result) } - - /** Gets the constant this refers to, if any. */ - Constant getConstant() { this.refersTo(result) } - - override string toString() { result = this.getName() } - - override Location getLocation() { result = loc.getLocation() } - } - - /** A reference to a field, used as the target of a write. */ - class FieldTarget extends WriteTarget { - FieldTarget() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.getBase() instanceof ValueExpr) - or - w instanceof InitLiteralStructFieldInstruction - } - - /** Gets the instruction computing the base value on which this field is accessed. */ - Instruction getBase() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | result = selectorBase(sel)) - or - result = w.(InitLiteralStructFieldInstruction).getBase() - } - - /** Get the type of the base of this field access, that is, the type that contains the field. */ - Type getBaseType() { result = this.getBase().getResultType() } - - override predicate refersTo(ValueEntity e) { - exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.uses(e)) - or - e = w.(InitLiteralStructFieldInstruction).getField() - } - - override string getName() { exists(Field f | this.refersTo(f) | result = f.getName()) } - - /** Gets the field this refers to, if it can be determined. */ - Field getField() { this.refersTo(result) } - - override string toString() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | - result = "field " + sel.getSelector().getName() - ) - or - result = "field " + w.(InitLiteralStructFieldInstruction).getFieldName() - } - - override Location getLocation() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | result = sel.getLocation()) - or - result = w.(InitLiteralStructFieldInstruction).getLocation() - } - } - - /** - * A reference to an element of an array, slice or map, used as the target of a write. - */ - class ElementTarget extends WriteTarget { - ElementTarget() { - this = MkLhs(_, any(IndexExpr idx)) - or - w instanceof InitLiteralElementInstruction - } - - /** Gets the instruction computing the base value of this element reference. */ - Instruction getBase() { - exists(IndexExpr idx | this = MkLhs(_, idx) | result = selectorBase(idx)) - or - result = w.(InitLiteralComponentInstruction).getBase() - } - - /** Gets the instruction computing the index of this element reference. */ - Instruction getIndex() { - exists(IndexExpr idx | this = MkLhs(_, idx) | result = evalExprInstruction(idx.getIndex())) - or - result = w.(InitLiteralElementInstruction).getIndex() - } - - override string toString() { result = "element" } - - override Location getLocation() { - exists(IndexExpr idx | this = MkLhs(_, idx) | result = idx.getLocation()) - or - result = w.(InitLiteralElementInstruction).getLocation() - } - } - - /** - * A pointer dereference, used as the target of a write. - */ - class PointerTarget extends WriteTarget { - Expr lhs; - - PointerTarget() { - this = MkLhs(_, lhs) and - (lhs instanceof StarExpr or lhs instanceof DerefExpr) - } - - /** Gets the instruction computing the pointer value being dereferenced. */ - Instruction getBase() { - exists(Expr base | base = lhs.(StarExpr).getBase() or base = lhs.(DerefExpr).getOperand() | - result = evalExprInstruction(base) - ) - } - - override string toString() { result = lhs.toString() } - - override Location getLocation() { result = lhs.getLocation() } - } - - /** - * Gets the (final) instruction computing the value of `e`. - * - * Note that some expressions (such as type expressions or labels) have no corresponding - * instruction, so this predicate is undefined for them. - * - * Short-circuiting expressions that are purely used for control flow (meaning that their - * value is not stored in a variable or used to compute the value of a non-shortcircuiting - * expression) do not have a final instruction either. - */ - Instruction evalExprInstruction(Expr e) { - result = MkExprNode(e) or - result = evalExprInstruction(e.(ParenExpr).getExpr()) - } - - /** - * Gets the instruction corresponding to the initialization of `r`. - */ - InitParameterInstruction initRecvInstruction(ReceiverVariable r) { result = MkParameterInit(r) } - - /** - * Gets the instruction corresponding to the initialization of `p`. - */ - InitParameterInstruction initParamInstruction(Parameter p) { result = MkParameterInit(p) } - - /** - * Gets the instruction corresponding to the `i`th assignment happening at - * `assgn` (0-based). - */ - AssignInstruction assignInstruction(Assignment assgn, int i) { result = MkAssignNode(assgn, i) } - - /** - * Gets the instruction corresponding to the `i`th initialization happening - * at `spec` (0-based). - */ - AssignInstruction initInstruction(ValueSpec spec, int i) { result = MkAssignNode(spec, i) } - - /** - * Gets the instruction corresponding to the assignment of the key variable - * of range statement `rs`. - */ - AssignInstruction assignKeyInstruction(RangeStmt rs) { result = MkAssignNode(rs, 0) } - - /** - * Gets the instruction corresponding to the assignment of the value variable - * of range statement `rs`. - */ - AssignInstruction assignValueInstruction(RangeStmt rs) { result = MkAssignNode(rs, 1) } - - /** - * Gets the instruction corresponding to the implicit initialization of `v` - * to its zero value. - */ - EvalImplicitInitInstruction implicitInitInstruction(ValueEntity v) { result = MkZeroInitNode(v) } - - /** - * Gets the instruction corresponding to the extraction of the `idx`th element - * of the tuple produced by `base`. - */ - ExtractTupleElementInstruction extractTupleElement(Instruction base, int idx) { - result.extractsElement(base, idx) - } - - /** - * Gets the instruction corresponding to the implicit lower bound of slice `e`, if any. - */ - EvalImplicitLowerSliceBoundInstruction implicitLowerSliceBoundInstruction(SliceExpr e) { - result = MkImplicitLowerSliceBound(e) - } - - /** - * Gets the instruction corresponding to the implicit upper bound of slice `e`, if any. - */ - EvalImplicitUpperSliceBoundInstruction implicitUpperSliceBoundInstruction(SliceExpr e) { - result = MkImplicitUpperSliceBound(e) - } - - /** - * Gets the instruction corresponding to the implicit maximum bound of slice `e`, if any. - */ - EvalImplicitMaxSliceBoundInstruction implicitMaxSliceBoundInstruction(SliceExpr e) { - result = MkImplicitMaxSliceBound(e) - } - - /** - * Gets the implicit dereference instruction for `e`, where `e` is a pointer used as the base - * in a field/method access, element access, or slice expression. - */ - EvalImplicitDerefInstruction implicitDerefInstruction(Expr e) { result = MkImplicitDeref(e) } - - /** Gets the base of `insn`, if `insn` is an implicit field read. */ - Instruction lookThroughImplicitFieldRead(Instruction insn) { - result = insn.(ImplicitFieldReadInstruction).getBaseInstruction() - } -} diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll.bak2 b/go/ql/lib/semmle/go/controlflow/IR.qll.bak2 deleted file mode 100644 index 807fbf76d1fd..000000000000 --- a/go/ql/lib/semmle/go/controlflow/IR.qll.bak2 +++ /dev/null @@ -1,1580 +0,0 @@ -/** - * Provides classes and predicates for working with an intermediate representation (IR) of Go - * programs that is used as the foundation of the control flow and data flow graphs. - * - * In the IR, the program is represented as a set of instructions, which correspond to expressions - * and statements that compute a value or perform an operation (as opposed to providing syntactic - * structure or type information). - * - * Each instruction is also a control-flow node, but there are control-flow nodes that are not - * instructions (synthetic entry and exit nodes, as well as before/after nodes). - */ -overlay[local] -module; - -import go -private import ControlFlowGraphShared - -/** Provides predicates and classes for working with IR constructs. */ -module IR { - /** - * An IR instruction. - */ - class Instruction extends ControlFlow::Node { - Instruction() { - this.isIn(_) or - this.isAdditional(_, _) or - this.isAfterTrue(_) and not this.isIn(_) or - this.isAfterFalse(_) and not this.isIn(_) - } - - /** Holds if this instruction reads the value of variable or constant `v`. */ - predicate reads(ValueEntity v) { this.readsField(_, v) or this.readsMethod(_, v) } - - /** Holds if this instruction updates variable or constant `v` to the value of `rhs`. */ - predicate writes(ValueEntity v, Instruction rhs) { this.writesField(_, v, rhs) } - - /** Holds if this instruction reads the value of field `f` on the value of `base`. */ - predicate readsField(Instruction base, Field f) { none() } - - /** Holds if this instruction updates the value of field `f` on the value of `base`. */ - predicate writesField(Instruction base, Field f, Instruction rhs) { none() } - - /** Holds if this instruction looks up method `m` on the value of `receiver`. */ - predicate readsMethod(Instruction receiver, Method m) { none() } - - /** Holds if this instruction reads the value of element `index` on the value of `base`. */ - predicate readsElement(Instruction base, Instruction index) { none() } - - /** Holds if this instruction updates the value of element `index` on the value of `base`. */ - predicate writesElement(Instruction base, Instruction index) { none() } - - /** Gets the type of the result of this instruction, if any. */ - Type getResultType() { none() } - - /** Gets the float value of the result of this instruction, if it can be determined. */ - float getFloatValue() { none() } - - /** Gets the int value of the result of this instruction, if it can be determined. */ - int getIntValue() { none() } - - /** - * Holds if the complex value of the result of this instruction has real part `real` and - * imaginary part `imag`. - */ - predicate hasComplexValue(float real, float imag) { none() } - - /** Gets either `getFloatValue` or `getIntValue` */ - float getNumericValue() { result = this.getFloatValue() or result = this.getIntValue() } - - /** - * Gets the string representation of the exact value of the result of this instruction, - * if any. - * - * For example, for the constant 3.141592653589793238462, this will - * result in 1570796326794896619231/500000000000000000000 - */ - string getExactValue() { none() } - - /** Gets the string value of the result of this instruction, if it can be determined. */ - string getStringValue() { none() } - - /** Gets the Boolean value of the result of this instruction, if it can be determined. */ - boolean getBoolValue() { none() } - - /** Holds if the result of this instruction is known at compile time. */ - predicate isConst() { none() } - - /** - * Holds if the result of this instruction is known at compile time, and is guaranteed not to - * depend on the platform where it is evaluated. - */ - predicate isPlatformIndependentConstant() { none() } - - /** Gets a textual representation of the kind of this instruction. */ - string getInsnKind() { - this instanceof EvalInstruction and result = "expression" - or - this instanceof InitLiteralComponentInstruction and result = "element init" - or - this instanceof ImplicitLiteralElementIndexInstruction and result = "element index" - or - this instanceof AssignInstruction and result = "assignment" - or - this instanceof EvalCompoundAssignRhsInstruction and - result = "right-hand side of compound assignment" - or - this instanceof ExtractTupleElementInstruction and result = "tuple element extraction" - or - this instanceof EvalImplicitInitInstruction and result = "zero value" - or - this instanceof DeclareFunctionInstruction and result = "function declaration" - or - this instanceof DeferInstruction and result = "defer" - or - this instanceof GoInstruction and result = "go" - or - this instanceof ConditionGuardInstruction and result = "condition guard" - or - this instanceof IncDecInstruction and result = "increment/decrement" - or - this instanceof EvalIncDecRhsInstruction and - result = "right-hand side of increment/decrement" - or - this instanceof EvalImplicitOneInstruction and result = "implicit 1" - or - this instanceof ReturnInstruction and result = "return" - or - this instanceof WriteResultInstruction and result = "result write" - or - this instanceof ReadResultInstruction and result = "result read" - or - this instanceof SendInstruction and result = "send" - or - this instanceof InitParameterInstruction and result = "parameter initialization" - or - this instanceof ReadArgumentInstruction and result = "argument" - or - this instanceof InitResultInstruction and result = "result initialization" - or - this instanceof GetNextEntryInstruction and result = "next key-value pair" - or - this instanceof EvalImplicitTrueInstruction and result = "implicit true" - or - this instanceof CaseInstruction and result = "case" - or - this instanceof TypeSwitchImplicitVariableInstruction and - result = "type switch implicit variable declaration" - or - this instanceof EvalImplicitLowerSliceBoundInstruction and result = "implicit lower bound" - or - this instanceof EvalImplicitUpperSliceBoundInstruction and result = "implicit upper bound" - or - this instanceof EvalImplicitMaxSliceBoundInstruction and result = "implicit maximum" - or - this instanceof EvalImplicitDerefInstruction and result = "implicit dereference" - or - this instanceof ImplicitFieldReadInstruction and result = "implicit field selection" - } - } - - /** A condition guard instruction, representing a known boolean outcome for a condition. */ - private class ConditionGuardInstruction extends Instruction { - ConditionGuardInstruction() { - this.isAfterTrue(_) and not this.isIn(_) - or - this.isAfterFalse(_) and not this.isIn(_) - } - } - - /** - * An IR instruction representing the evaluation of an expression. - */ - class EvalInstruction extends Instruction { - Expr e; - - EvalInstruction() { this.isIn(e) and e instanceof Expr } - - /** Gets the expression underlying this instruction. */ - Expr getExpr() { result = e } - - override predicate reads(ValueEntity v) { e = v.getAReference() } - - override Type getResultType() { result = e.getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(e) } - - override float getFloatValue() { result = e.getFloatValue() } - - override int getIntValue() { result = e.getIntValue() } - - override predicate hasComplexValue(float real, float imag) { e.hasComplexValue(real, imag) } - - override string getExactValue() { result = e.getExactValue() } - - override string getStringValue() { result = e.getStringValue() } - - override boolean getBoolValue() { result = e.getBoolValue() } - - override predicate isConst() { e.isConst() } - - override predicate isPlatformIndependentConstant() { e.isPlatformIndependentConstant() } - - override string toString() { result = e.toString() } - - override Location getLocation() { result = e.getLocation() } - } - - /** - * An IR instruction that reads the value of a variable, constant, field or array element, - * or refers to a function. - */ - class ReadInstruction extends Instruction { - ReadInstruction() { - exists(Expr e | e = this.(EvalInstruction).getExpr() | - (e instanceof ValueName or e instanceof IndexExpr) and - e.(ReferenceExpr).isRvalue() - ) - or - this instanceof ReadResultInstruction - or - this instanceof ImplicitFieldReadInstruction - } - } - - /** - * Gets the effective base of a selector, index or slice expression, taking implicit dereferences - * and implicit field reads into account. - * - * For a selector expression `b.f`, this could be the implicit dereference `*b`, or the implicit - * field access `b.Embedded` if the field `f` is promoted from an embedded type `Embedded`, or a - * combination of both `*(b.Embedded)`, or simply `b` if neither case applies. - */ - private Instruction selectorBase(Expr e) { - exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1 | - result = fri - ) - or - not exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = e and fri.getIndex() = 1) and - exists(Expr base | - base = e.(SelectorExpr).getBase() - or - base = e.(IndexExpr).getBase() - or - base = e.(SliceExpr).getBase() - | - result = MkImplicitDeref(base) - or - not exists(MkImplicitDeref(base)) and - result = evalExprInstruction(base) - ) - } - - /** - * An IR instruction that reads a component from a composite object. - * - * This is either a field of a struct, or an element of an array, map, slice or string. - */ - class ComponentReadInstruction extends ReadInstruction { - ComponentReadInstruction() { - exists(Expr e | e = this.(EvalInstruction).getExpr() | - e instanceof IndexExpr - or - e.(SelectorExpr).getBase() instanceof ValueExpr and - not e.(SelectorExpr).getSelector() = any(Method method).getAReference() - ) - or - this instanceof MkImplicitFieldSelection - } - - /** Gets the instruction computing the base value on which the field or element is read. */ - Instruction getBase() { - result = this.(ImplicitFieldReadInstruction).getBaseInstruction() - or - result = selectorBase(this.(EvalInstruction).getExpr()) - } - } - - /** - * An IR instruction that reads the value of a field. - * - * On databases with incomplete type information, method expressions may sometimes be - * misclassified as field reads. - */ - class FieldReadInstruction extends ComponentReadInstruction { - SelectorExpr e; - int index; - Field field; - - FieldReadInstruction() { - e = this.(EvalInstruction).getExpr() and - index = 0 and - field.getAReference() = e.getSelector() - or - this = MkImplicitFieldSelection(e, index, field) - } - - /** Gets the `SelectorExpr` of this field read. */ - SelectorExpr getSelectorExpr() { result = e } - - /** Gets the index of this field read. */ - int getIndex() { result = index } - - /** Gets the field being read. */ - Field getField() { result = field } - - Instruction getBaseInstruction() { - exists(ImplicitFieldReadInstruction fri | - fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) - | - result = fri - ) - or - not exists(ImplicitFieldReadInstruction fri | - fri.getSelectorExpr() = e and fri.getIndex() = pragma[only_bind_into](index + 1) - ) and - ( - result = MkImplicitDeref(e.getBase()) - or - not exists(MkImplicitDeref(e.getBase())) and - result = evalExprInstruction(e.getBase()) - ) - } - - override predicate readsField(Instruction base, Field f) { - base = this.getBaseInstruction() and f = field - } - } - - /** - * An IR instruction for an implicit field read as part of reading a - * promoted field. - * - * If the field that is being implicitly read has a pointer type then this - * instruction represents an implicit dereference of it. - */ - class ImplicitFieldReadInstruction extends FieldReadInstruction, MkImplicitFieldSelection { - ImplicitFieldReadInstruction() { this = MkImplicitFieldSelection(e, index, field) } - - override predicate reads(ValueEntity v) { v = field } - - override Type getResultType() { result = lookThroughPointerType(field.getType()) } - - override ControlFlow::Root getRoot() { result.isRootOf(e) } - - override string toString() { result = "implicit read of field " + field.toString() } - - override Location getLocation() { result = e.getBase().getLocation() } - } - - /** - * An IR instruction that looks up a method. - */ - class MethodReadInstruction extends ReadInstruction, EvalInstruction { - Method method; - override SelectorExpr e; - - MethodReadInstruction() { e.getSelector() = method.getAReference() } - - /** Gets the instruction computing the receiver value on which the method is looked up. */ - Instruction getReceiver() { result = selectorBase(e) } - - /** Gets the method being looked up. */ - Method getMethod() { result = method } - - override predicate readsMethod(Instruction receiver, Method m) { - receiver = this.getReceiver() and m = this.getMethod() - } - } - - /** - * An IR instruction that reads an element of an array, slice, map or string. - */ - class ElementReadInstruction extends ComponentReadInstruction, EvalInstruction { - override IndexExpr e; - - /** Gets the instruction computing the index of the element being looked up. */ - Instruction getIndex() { result = evalExprInstruction(e.getIndex()) } - - override predicate readsElement(Instruction base, Instruction index) { - base = this.getBase() and index = this.getIndex() - } - } - - /** - * An IR instruction that constructs a slice. - */ - class SliceInstruction extends EvalInstruction { - override SliceExpr e; - - /** Gets the instruction computing the base value from which the slice is constructed. */ - Instruction getBase() { result = selectorBase(e) } - - /** Gets the instruction computing the lower bound of the slice. */ - Instruction getLow() { - result = evalExprInstruction(e.getLow()) or - result = implicitLowerSliceBoundInstruction(e) - } - - /** Gets the instruction computing the upper bound of the slice. */ - Instruction getHigh() { - result = evalExprInstruction(e.getHigh()) or - result = implicitUpperSliceBoundInstruction(e) - } - - /** Gets the instruction computing the capacity of the slice. */ - Instruction getMax() { - result = evalExprInstruction(e.getMax()) or - result = implicitMaxSliceBoundInstruction(e) - } - } - - /** - * An IR instruction that writes a memory location. - */ - class WriteInstruction extends Instruction { - WriteTarget lhs; - Boolean initialization; - - WriteInstruction() { - ( - lhs = MkLhs(this, _) - or - lhs = MkResultWriteTarget(this) - ) and - initialization = false - or - lhs = MkLiteralElementTarget(this) and initialization = true - } - - /** Gets the target to which this instruction writes. */ - WriteTarget getLhs() { result = lhs } - - /** Holds if this instruction initializes a literal. */ - predicate isInitialization() { initialization = true } - - /** Gets the instruction computing the value this instruction writes. */ - Instruction getRhs() { none() } - - override predicate writes(ValueEntity v, Instruction rhs) { - this.getLhs().refersTo(v) and - rhs = this.getRhs() - } - } - - /** - * An IR instruction that initializes a component of a composite literal. - */ - class InitLiteralComponentInstruction extends WriteInstruction, MkLiteralElementInitNode { - CompositeLit lit; - int i; - Expr elt; - - InitLiteralComponentInstruction() { - this = MkLiteralElementInitNode(elt) and elt = lit.getElement(i) - } - - /** Gets the instruction allocating the composite literal. */ - Instruction getBase() { result = evalExprInstruction(lit) } - - override Instruction getRhs() { - result = evalExprInstruction(elt) or - result = evalExprInstruction(elt.(KeyValueExpr).getValue()) - } - - override ControlFlow::Root getRoot() { result.isRootOf(elt) } - - override string toString() { result = "init of " + elt } - - override Location getLocation() { result = elt.getLocation() } - } - - /** - * An IR instruction that initializes a field of a struct literal. - */ - class InitLiteralStructFieldInstruction extends InitLiteralComponentInstruction { - override StructLit lit; - - /** Gets the name of the initialized field. */ - pragma[nomagic] - string getFieldName() { - if elt instanceof KeyValueExpr - then result = elt.(KeyValueExpr).getKey().(Ident).getName() - else pragma[only_bind_out](lit.getStructType()).hasOwnField(i, result, _, _) - } - - /** Gets the initialized field. */ - Field getField() { - result.getDeclaringType() = lit.getStructType() and - result.getName() = this.getFieldName() - } - } - - /** - * An IR instruction that initializes an element of an array, slice or map literal. - */ - class InitLiteralElementInstruction extends InitLiteralComponentInstruction { - Type literalType; - - InitLiteralElementInstruction() { - literalType = lit.getType().getUnderlyingType() and - ( - literalType instanceof ArrayType or - literalType instanceof SliceType or - literalType instanceof MapType - ) - } - - /** Gets the instruction computing the index of the initialized element. */ - Instruction getIndex() { - result = evalExprInstruction(elt.(KeyValueExpr).getKey()) - or - result = MkImplicitLiteralElementIndex(elt) - } - } - - /** - * An IR instruction that initializes an element of an array literal. - */ - class InitLiteralArrayElementInstruction extends InitLiteralElementInstruction { - override ArrayType literalType; - } - - /** - * An IR instruction that initializes an element of a slice literal. - */ - class InitLiteralSliceElementInstruction extends InitLiteralElementInstruction { - override SliceType literalType; - } - - /** - * An IR instruction that initializes an element of a map literal. - */ - class InitLiteralMapElementInstruction extends InitLiteralElementInstruction { - override MapType literalType; - } - - /** - * An IR instruction that writes to a field. - */ - class FieldWriteInstruction extends WriteInstruction { - override FieldTarget lhs; - - /** Gets the instruction computing the base value on which the field is written. */ - Instruction getBase() { result = lhs.getBase() } - - /** Gets the field being written. */ - Field getField() { result = lhs.getField() } - - override predicate writesField(Instruction base, Field f, Instruction rhs) { - this.getBase() = base and - this.getField() = f and - this.getRhs() = rhs - } - } - - /** - * An IR instruction that writes to an element of an array, slice, or map. - */ - class ElementWriteInstruction extends WriteInstruction { - override ElementTarget lhs; - - /** Gets the instruction computing the base value on which the field is written. */ - Instruction getBase() { result = lhs.getBase() } - - /** Gets the instruction computing the element index being written. */ - Instruction getIndex() { result = lhs.getIndex() } - - override predicate writesElement(Instruction base, Instruction index) { - this.getBase() = base and - this.getIndex() = index - } - } - - /** Holds if `lit` does not specify any explicit keys. */ - private predicate noExplicitKeys(CompositeLit lit) { - not lit.getAnElement() instanceof KeyValueExpr - } - - /** Gets the index of the `i`th element in (array or slice) literal `lit`. */ - private int getElementIndex(CompositeLit lit, int i) { - ( - lit.getType().getUnderlyingType() instanceof ArrayType or - lit.getType().getUnderlyingType() instanceof SliceType - ) and - exists(Expr elt | elt = lit.getElement(i) | - // short-circuit computation for literals without any explicit keys - noExplicitKeys(lit) and result = i - or - result = elt.(KeyValueExpr).getKey().getIntValue() - or - not elt instanceof KeyValueExpr and - ( - i = 0 and result = 0 - or - result = getElementIndex(lit, i - 1) + 1 - ) - ) - } - - /** - * An IR instruction computing the implicit index of an element in an array or slice literal. - */ - class ImplicitLiteralElementIndexInstruction extends Instruction, MkImplicitLiteralElementIndex { - Expr elt; - - ImplicitLiteralElementIndexInstruction() { this = MkImplicitLiteralElementIndex(elt) } - - override Type getResultType() { result instanceof IntType } - - override ControlFlow::Root getRoot() { result.isRootOf(elt) } - - override int getIntValue() { - exists(CompositeLit lit, int i | elt = lit.getElement(i) | result = getElementIndex(lit, i)) - } - - override string getStringValue() { none() } - - override string getExactValue() { result = this.getIntValue().toString() } - - override predicate isPlatformIndependentConstant() { any() } - - override predicate isConst() { any() } - - override string toString() { result = "element index" } - - override Location getLocation() { result = elt.getLocation() } - } - - /** - * An instruction assigning to a variable or field. - */ - class AssignInstruction extends WriteInstruction, MkAssignNode { - AstNode assgn; - int i; - - AssignInstruction() { this = MkAssignNode(assgn, i) } - - override Instruction getRhs() { - exists(SimpleAssignStmt a | a = assgn | - a.getNumLhs() = a.getNumRhs() and - result = evalExprInstruction(a.getRhs(i)) - ) - or - exists(ValueSpec spec | spec = assgn | - spec.getNumName() = spec.getNumInit() and - result = evalExprInstruction(spec.getInit(i)) - or - result = MkZeroInitNode(any(ValueEntity v | spec.getNameExpr(i) = v.getDeclaration())) - ) - or - result = MkCompoundAssignRhsNode(assgn) - or - result = MkExtractNode(assgn, i) - } - - override ControlFlow::Root getRoot() { result.isRootOf(assgn) } - - override string toString() { result = "assignment to " + this.getLhs() } - - override Location getLocation() { result = this.getLhs().getLocation() } - } - - /** An instruction computing the value of the right-hand side of a compound assignment. */ - class EvalCompoundAssignRhsInstruction extends Instruction, MkCompoundAssignRhsNode { - CompoundAssignStmt assgn; - - EvalCompoundAssignRhsInstruction() { this = MkCompoundAssignRhsNode(assgn) } - - /** Gets the underlying assignment of this instruction. */ - CompoundAssignStmt getAssignment() { result = assgn } - - override Type getResultType() { result = assgn.getRhs().getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(assgn) } - - override string toString() { result = assgn.toString() } - - override Location getLocation() { result = assgn.getLocation() } - } - - /** - * An instruction selecting one of multiple values returned by a function, or either the key - * or the value of the iterator in a range loop, or the result or success value from a type - * assertion. - */ - class ExtractTupleElementInstruction extends Instruction, MkExtractNode { - AstNode s; - int i; - - ExtractTupleElementInstruction() { this = MkExtractNode(s, i) } - - /** Gets the instruction computing the tuple value from which one value is extracted. */ - Instruction getBase() { - exists(Expr baseExpr | - baseExpr = s.(Assignment).getRhs() or - baseExpr = s.(ValueSpec).getInit() - | - result = evalExprInstruction(baseExpr) - ) - or - result = MkNextNode(s) - or - result = evalExprInstruction(s.(ReturnStmt).getExpr()) - or - result = evalExprInstruction(s.(CallExpr).getArgument(0).stripParens()) - } - - /** Holds if this extracts the `idx`th value of the result of `base`. */ - predicate extractsElement(Instruction base, int idx) { base = this.getBase() and idx = i } - - override Type getResultType() { - exists(Expr e | this.getBase() = evalExprInstruction(e) | - result = e.getType().(TupleType).getComponentType(pragma[only_bind_into](i)) - ) - or - exists(Type rangeType | rangeType = s.(RangeStmt).getDomain().getType().getUnderlyingType() | - exists(Type baseType | - baseType = rangeType.(ArrayType).getElementType() or - baseType = - rangeType.(PointerType).getBaseType().getUnderlyingType().(ArrayType).getElementType() or - baseType = rangeType.(SliceType).getElementType() - | - i = 0 and - result instanceof IntType - or - i = 1 and - result = baseType - ) - or - rangeType instanceof StringType and - ( - i = 0 and - result instanceof IntType - or - result = Builtin::rune().getType() - ) - or - exists(MapType map | map = rangeType | - i = 0 and - result = map.getKeyType() - or - i = 1 and - result = map.getValueType() - ) - or - i = 0 and - result = rangeType.(RecvChanType).getElementType() - or - i = 0 and - result = rangeType.(SendRecvChanType).getElementType() - ) - } - - override ControlFlow::Root getRoot() { result.isRootOf(s) } - - override string toString() { result = s + "[" + i + "]" } - - override Location getLocation() { result = s.getLocation() } - } - - /** - * An instruction that computes the zero value for a variable or constant. - */ - class EvalImplicitInitInstruction extends Instruction, MkZeroInitNode { - ValueEntity v; - - EvalImplicitInitInstruction() { this = MkZeroInitNode(v) } - - override Type getResultType() { result = v.getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(v.getDeclaration()) } - - override int getIntValue() { - v.getType().getUnderlyingType() instanceof IntegerType and result = 0 - } - - override float getFloatValue() { - v.getType().getUnderlyingType() instanceof FloatType and result = 0.0 - } - - override string getStringValue() { - v.getType().getUnderlyingType() instanceof StringType and result = "" - } - - override boolean getBoolValue() { - v.getType().getUnderlyingType() instanceof BoolType and result = false - } - - override string getExactValue() { - result = this.getIntValue().toString() or - result = this.getFloatValue().toString() or - result = this.getStringValue().toString() or - result = this.getBoolValue().toString() - } - - override predicate isConst() { any() } - - override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "zero value for " + v } - - override Location getLocation() { result = v.getDeclaration().getLocation() } - } - - /** - * An instruction that corresponds to the declaration of a function. - */ - class DeclareFunctionInstruction extends Instruction, MkFuncDeclNode { - FuncDecl fd; - - DeclareFunctionInstruction() { this = MkFuncDeclNode(fd) } - - override Type getResultType() { result = fd.getType() } - - override string toString() { result = fd.toString() } - - override Location getLocation() { result = fd.getLocation() } - } - - /** - * An instruction that corresponds to a `defer` statement. - */ - class DeferInstruction extends Instruction, MkDeferNode { - DeferStmt defer; - - DeferInstruction() { this = MkDeferNode(defer) } - - override ControlFlow::Root getRoot() { result.isRootOf(defer) } - - override string toString() { result = defer.toString() } - - override Location getLocation() { result = defer.getLocation() } - } - - /** - * An instruction that corresponds to a `go` statement. - */ - class GoInstruction extends Instruction, MkGoNode { - GoStmt go; - - GoInstruction() { this = MkGoNode(go) } - - override ControlFlow::Root getRoot() { result.isRootOf(go) } - - override string toString() { result = go.toString() } - - override Location getLocation() { result = go.getLocation() } - } - - /** - * An instruction that corresponds to an increment or decrement statement. - */ - class IncDecInstruction extends WriteInstruction, MkIncDecNode { - IncDecStmt ids; - - IncDecInstruction() { this = MkIncDecNode(ids) } - - override Instruction getRhs() { result = MkIncDecRhs(ids) } - - override ControlFlow::Root getRoot() { result.isRootOf(ids) } - - override string toString() { result = ids.toString() } - - override Location getLocation() { result = ids.getLocation() } - } - - /** - * An instruction that computes the (implicit) right-hand side of an increment or - * decrement statement. - */ - class EvalIncDecRhsInstruction extends Instruction, MkIncDecRhs { - IncDecStmt ids; - - EvalIncDecRhsInstruction() { this = MkIncDecRhs(ids) } - - /** Gets the corresponding increment or decrement statement. */ - IncDecStmt getStmt() { result = ids } - - override Type getResultType() { result = ids.getOperand().getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(ids) } - - override string toString() { result = "rhs of " + ids } - - override Location getLocation() { result = ids.getLocation() } - } - - /** - * An instruction computing the implicit operand `1` in an increment or decrement statement. - */ - class EvalImplicitOneInstruction extends Instruction, MkImplicitOne { - IncDecStmt ids; - - EvalImplicitOneInstruction() { this = MkImplicitOne(ids) } - - /** Gets the corresponding increment or decrement statement. */ - IncDecStmt getStmt() { result = ids } - - override Type getResultType() { result = ids.getOperand().getType() } - - override ControlFlow::Root getRoot() { result.isRootOf(ids) } - - override int getIntValue() { result = 1 } - - override string getExactValue() { result = "1" } - - override predicate isConst() { any() } - - override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "1" } - - override Location getLocation() { result = ids.getLocation() } - } - - /** - * An instruction corresponding to a return from a function. - */ - class ReturnInstruction extends Instruction, MkReturnNode { - ReturnStmt ret; - - ReturnInstruction() { this = MkReturnNode(ret) } - - /** Gets the corresponding `ReturnStmt`. */ - ReturnStmt getReturnStmt() { result = ret } - - /** Holds if this statement returns multiple results. */ - predicate returnsMultipleResults() { exists(MkExtractNode(ret, _)) or ret.getNumExpr() > 1 } - - /** Gets the instruction whose result is the (unique) result returned by this statement. */ - Instruction getResult() { - not this.returnsMultipleResults() and - result = evalExprInstruction(ret.getExpr()) - } - - /** Gets the instruction whose result is the `i`th result returned by this statement. */ - Instruction getResult(int i) { - result = MkExtractNode(ret, i) - or - not exists(MkExtractNode(ret, _)) and - result = evalExprInstruction(ret.getExpr(i)) - } - - override ControlFlow::Root getRoot() { result.isRootOf(ret) } - - override string toString() { result = ret.toString() } - - override Location getLocation() { result = ret.getLocation() } - } - - /** - * An instruction that represents the implicit assignment to a result variable - * performed by a return statement. - */ - class WriteResultInstruction extends WriteInstruction, MkResultWriteNode { - ResultVariable var; - int i; - ReturnInstruction ret; - - WriteResultInstruction() { - exists(ReturnStmt retstmt | - this = MkResultWriteNode(var, i, retstmt) and - ret = MkReturnNode(retstmt) - ) - } - - override Instruction getRhs() { result = ret.getResult(i) } - - /** Gets the result variable being assigned. */ - ResultVariable getResultVariable() { result = var } - - override Type getResultType() { result = var.getType() } - - override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } - - override string toString() { result = "implicit write of " + var } - - override Location getLocation() { result = ret.getResult(i).getLocation() } - } - - /** - * An instruction that reads the final value of a result variable upon returning - * from a function. - */ - class ReadResultInstruction extends Instruction, MkResultReadNode { - ResultVariable var; - - ReadResultInstruction() { this = MkResultReadNode(var) } - - override predicate reads(ValueEntity v) { v = var } - - override Type getResultType() { result = var.getType() } - - override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } - - override string toString() { result = "implicit read of " + var } - - override Location getLocation() { result = var.getDeclaration().getLocation() } - } - - /** - * An instruction corresponding to a `select` statement. - */ - class SelectInstruction extends Instruction, MkSelectNode { - SelectStmt sel; - - SelectInstruction() { this = MkSelectNode(sel) } - - override ControlFlow::Root getRoot() { result.isRootOf(sel) } - - override string toString() { result = sel.toString() } - - override Location getLocation() { result = sel.getLocation() } - } - - /** - * An instruction corresponding to a send statement. - */ - class SendInstruction extends Instruction, MkSendNode { - SendStmt send; - - SendInstruction() { this = MkSendNode(send) } - - override ControlFlow::Root getRoot() { result.isRootOf(send) } - - override string toString() { result = send.toString() } - - override Location getLocation() { result = send.getLocation() } - } - - /** - * An instruction initializing a parameter to the corresponding argument. - */ - class InitParameterInstruction extends WriteInstruction, MkParameterInit { - Parameter parm; - - InitParameterInstruction() { this = MkParameterInit(parm) } - - override Instruction getRhs() { result = MkArgumentNode(parm) } - - override ControlFlow::Root getRoot() { result = parm.getFunction() } - - override string toString() { result = "initialization of " + parm } - - override Location getLocation() { result = parm.getDeclaration().getLocation() } - } - - /** - * An instruction reading the value of a function argument. - */ - class ReadArgumentInstruction extends Instruction, MkArgumentNode { - Parameter parm; - - ReadArgumentInstruction() { this = MkArgumentNode(parm) } - - override Type getResultType() { result = parm.getType() } - - override ControlFlow::Root getRoot() { result = parm.getFunction() } - - override string toString() { result = "argument corresponding to " + parm } - - override Location getLocation() { result = parm.getDeclaration().getLocation() } - } - - /** - * An instruction initializing a result variable to its zero value. - */ - class InitResultInstruction extends WriteInstruction, MkResultInit { - ResultVariable res; - - InitResultInstruction() { this = MkResultInit(res) } - - override Instruction getRhs() { result = MkZeroInitNode(res) } - - override ControlFlow::Root getRoot() { result = res.getFunction() } - - override string toString() { result = "initialization of " + res } - - override Location getLocation() { result = res.getDeclaration().getLocation() } - } - - /** - * An instruction that gets the next key-value pair in a range loop. - */ - class GetNextEntryInstruction extends Instruction, MkNextNode { - RangeStmt rs; - - GetNextEntryInstruction() { this = MkNextNode(rs) } - - /** - * Gets the instruction computing the value whose key-value pairs this instruction reads. - */ - Instruction getDomain() { result = evalExprInstruction(rs.getDomain()) } - - override ControlFlow::Root getRoot() { result.isRootOf(rs) } - - override string toString() { result = "next key-value pair in range" } - - override Location getLocation() { result = rs.getDomain().getLocation() } - } - - /** - * An instruction computing the implicit `true` value in an expression-less `switch` statement. - */ - class EvalImplicitTrueInstruction extends Instruction, MkImplicitTrue { - Stmt stmt; - - EvalImplicitTrueInstruction() { this = MkImplicitTrue(stmt) } - - override Type getResultType() { result instanceof BoolType } - - override ControlFlow::Root getRoot() { result.isRootOf(stmt) } - - override boolean getBoolValue() { result = true } - - override string getExactValue() { result = "true" } - - override predicate isConst() { any() } - - override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "true" } - - override Location getLocation() { result = stmt.getLocation() } - } - - /** - * An instruction corresponding to the implicit comparison or type check performed by an - * expression in a `case` clause. - * - * For example, consider this `switch` statement: - * - * ```go - * switch x { - * case 2, y+1: - * ... - * } - * ``` - * - * The expressions `2` and `y+1` are implicitly compared to `x`. These comparisons are - * represented by case instructions. - */ - class CaseInstruction extends Instruction, MkCaseCheckNode { - CaseClause cc; - int i; - - CaseInstruction() { this = MkCaseCheckNode(cc, i) } - - override ControlFlow::Root getRoot() { result.isRootOf(cc) } - - override string toString() { result = "case " + cc.getExpr(i) } - - override Location getLocation() { result = cc.getExpr(i).getLocation() } - } - - /** - * An instruction corresponding to the implicit declaration of the variable - * `lv` in case clause `cc` and its assignment of the value `switchExpr` from - * the guard. This only occurs in case clauses in a type switch statement - * which declares a variable in its guard. - * - * For example, consider this type switch statement: - * - * ```go - * switch y := x.(type) { - * case Type1: - * f(y) - * ... - * } - * ``` - * - * The `y` inside the case clause is actually a local variable with type - * `Type1` that is implicitly declared at the top of the case clause. In - * default clauses and case clauses which list more than one type, the type - * of the implicitly declared variable is the type of `switchExpr`. - */ - class TypeSwitchImplicitVariableInstruction extends Instruction, MkTypeSwitchImplicitVariable { - CaseClause cc; - LocalVariable lv; - Expr switchExpr; - - TypeSwitchImplicitVariableInstruction() { - this = MkTypeSwitchImplicitVariable(cc, lv, switchExpr) - } - - override predicate writes(ValueEntity v, Instruction rhs) { - v = lv and - rhs = evalExprInstruction(switchExpr) - } - - override ControlFlow::Root getRoot() { result.isRootOf(cc) } - - override string toString() { result = "implicit type switch variable declaration" } - - override Location getLocation() { result = cc.getLocation() } - } - - /** - * An instruction computing the implicit lower slice bound of zero in a slice expression without - * an explicit lower bound. - */ - class EvalImplicitLowerSliceBoundInstruction extends Instruction, MkImplicitLowerSliceBound { - SliceExpr slice; - - EvalImplicitLowerSliceBoundInstruction() { this = MkImplicitLowerSliceBound(slice) } - - override Type getResultType() { result instanceof IntType } - - override ControlFlow::Root getRoot() { result.isRootOf(slice) } - - override int getIntValue() { result = 0 } - - override string getExactValue() { result = "0" } - - override predicate isConst() { any() } - - override predicate isPlatformIndependentConstant() { any() } - - override string toString() { result = "0" } - - override Location getLocation() { result = slice.getLocation() } - } - - /** - * An instruction computing the implicit upper slice bound in a slice expression without an - * explicit upper bound. - */ - class EvalImplicitUpperSliceBoundInstruction extends Instruction, MkImplicitUpperSliceBound { - SliceExpr slice; - - EvalImplicitUpperSliceBoundInstruction() { this = MkImplicitUpperSliceBound(slice) } - - override ControlFlow::Root getRoot() { result.isRootOf(slice) } - - override Type getResultType() { result instanceof IntType } - - override string toString() { result = "len" } - - override Location getLocation() { result = slice.getLocation() } - } - - /** - * An instruction computing the implicit maximum slice bound in a slice expression without an - * explicit maximum bound. - */ - class EvalImplicitMaxSliceBoundInstruction extends Instruction, MkImplicitMaxSliceBound { - SliceExpr slice; - - EvalImplicitMaxSliceBoundInstruction() { this = MkImplicitMaxSliceBound(slice) } - - override ControlFlow::Root getRoot() { result.isRootOf(slice) } - - override Type getResultType() { result instanceof IntType } - - override string toString() { result = "cap" } - - override Location getLocation() { result = slice.getLocation() } - } - - /** - * An instruction implicitly dereferencing the base in a field or method reference through a - * pointer, or the base in an element or slice reference through a pointer. - */ - class EvalImplicitDerefInstruction extends Instruction, MkImplicitDeref { - Expr e; - - EvalImplicitDerefInstruction() { this = MkImplicitDeref(e) } - - /** Gets the operand that is being dereferenced. */ - Expr getOperand() { result = e } - - override Type getResultType() { - result = e.getType().getUnderlyingType().(PointerType).getBaseType() - } - - override ControlFlow::Root getRoot() { result.isRootOf(e) } - - override string toString() { result = "implicit dereference" } - - override Location getLocation() { result = e.getLocation() } - } - - /** A representation of the target of a write instruction. */ - class WriteTarget extends TWriteTarget { - ControlFlow::Node w; - - WriteTarget() { - this = MkLhs(w, _) or this = MkLiteralElementTarget(w) or this = MkResultWriteTarget(w) - } - - /** Gets the write instruction of which this is the target. */ - WriteInstruction getWrite() { result = w } - - /** Gets the name of the variable or field being written to, if any. */ - string getName() { none() } - - /** Gets the SSA variable being written to, if any. */ - SsaVariable asSsaVariable() { - this.getWrite() = result.getDefinition().(SsaExplicitDefinition).getInstruction() - } - - /** Holds if `e` is the variable or field being written to. */ - predicate refersTo(ValueEntity e) { none() } - - /** Gets a textual representation of this target. */ - string toString() { result = "write target" } - - /** Gets the source location for this element. */ - Location getLocation() { none() } - - /** - * DEPRECATED: Use `getLocation()` instead. - * - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). - */ - deprecated predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - or - not exists(this.getLocation()) and - filepath = "" and - startline = 0 and - startcolumn = 0 and - endline = 0 and - endcolumn = 0 - } - } - - /** A reference to a variable or constant, used as the target of a write. */ - class VarOrConstTarget extends WriteTarget { - Expr loc; - - VarOrConstTarget() { - this = MkLhs(_, loc) and - ( - loc instanceof Ident - or - loc instanceof SelectorExpr and - not loc.(SelectorExpr).getBase() instanceof ReferenceExpr - ) - or - exists(WriteResultInstruction wr | - this = MkResultWriteTarget(wr) and - evalExprInstruction(loc) = wr.getRhs() - ) - } - - override predicate refersTo(ValueEntity e) { - this instanceof MkLhs and - pragma[only_bind_out](loc) = e.getAReference() - or - exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | - e = wr.getResultVariable() - ) - } - - override string getName() { - this = MkLhs(_, loc) and - ( - result = loc.(Ident).getName() - or - result = loc.(SelectorExpr).getSelector().getName() - ) - or - exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | - result = wr.getResultVariable().getName() - ) - } - - /** Gets the variable this refers to, if any. */ - Variable getVariable() { this.refersTo(result) } - - /** Gets the constant this refers to, if any. */ - Constant getConstant() { this.refersTo(result) } - - override string toString() { result = this.getName() } - - override Location getLocation() { result = loc.getLocation() } - } - - /** A reference to a field, used as the target of a write. */ - class FieldTarget extends WriteTarget { - FieldTarget() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.getBase() instanceof ValueExpr) - or - w instanceof InitLiteralStructFieldInstruction - } - - /** Gets the instruction computing the base value on which this field is accessed. */ - Instruction getBase() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | result = selectorBase(sel)) - or - result = w.(InitLiteralStructFieldInstruction).getBase() - } - - /** Get the type of the base of this field access, that is, the type that contains the field. */ - Type getBaseType() { result = this.getBase().getResultType() } - - override predicate refersTo(ValueEntity e) { - exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.uses(e)) - or - e = w.(InitLiteralStructFieldInstruction).getField() - } - - override string getName() { exists(Field f | this.refersTo(f) | result = f.getName()) } - - /** Gets the field this refers to, if it can be determined. */ - Field getField() { this.refersTo(result) } - - override string toString() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | - result = "field " + sel.getSelector().getName() - ) - or - result = "field " + w.(InitLiteralStructFieldInstruction).getFieldName() - } - - override Location getLocation() { - exists(SelectorExpr sel | this = MkLhs(_, sel) | result = sel.getLocation()) - or - result = w.(InitLiteralStructFieldInstruction).getLocation() - } - } - - /** - * A reference to an element of an array, slice or map, used as the target of a write. - */ - class ElementTarget extends WriteTarget { - ElementTarget() { - this = MkLhs(_, any(IndexExpr idx)) - or - w instanceof InitLiteralElementInstruction - } - - /** Gets the instruction computing the base value of this element reference. */ - Instruction getBase() { - exists(IndexExpr idx | this = MkLhs(_, idx) | result = selectorBase(idx)) - or - result = w.(InitLiteralComponentInstruction).getBase() - } - - /** Gets the instruction computing the index of this element reference. */ - Instruction getIndex() { - exists(IndexExpr idx | this = MkLhs(_, idx) | result = evalExprInstruction(idx.getIndex())) - or - result = w.(InitLiteralElementInstruction).getIndex() - } - - override string toString() { result = "element" } - - override Location getLocation() { - exists(IndexExpr idx | this = MkLhs(_, idx) | result = idx.getLocation()) - or - result = w.(InitLiteralElementInstruction).getLocation() - } - } - - /** - * A pointer dereference, used as the target of a write. - */ - class PointerTarget extends WriteTarget { - Expr lhs; - - PointerTarget() { - this = MkLhs(_, lhs) and - (lhs instanceof StarExpr or lhs instanceof DerefExpr) - } - - /** Gets the instruction computing the pointer value being dereferenced. */ - Instruction getBase() { - exists(Expr base | base = lhs.(StarExpr).getBase() or base = lhs.(DerefExpr).getOperand() | - result = evalExprInstruction(base) - ) - } - - override string toString() { result = lhs.toString() } - - override Location getLocation() { result = lhs.getLocation() } - } - - /** - * Gets the (final) instruction computing the value of `e`. - * - * Note that some expressions (such as type expressions or labels) have no corresponding - * instruction, so this predicate is undefined for them. - * - * Short-circuiting expressions that are purely used for control flow (meaning that their - * value is not stored in a variable or used to compute the value of a non-shortcircuiting - * expression) do not have a final instruction either. - */ - Instruction evalExprInstruction(Expr e) { - result = MkExprNode(e) or - result = evalExprInstruction(e.(ParenExpr).getExpr()) - } - - /** - * Gets the instruction corresponding to the initialization of `r`. - */ - InitParameterInstruction initRecvInstruction(ReceiverVariable r) { result = MkParameterInit(r) } - - /** - * Gets the instruction corresponding to the initialization of `p`. - */ - InitParameterInstruction initParamInstruction(Parameter p) { result = MkParameterInit(p) } - - /** - * Gets the instruction corresponding to the `i`th assignment happening at - * `assgn` (0-based). - */ - AssignInstruction assignInstruction(Assignment assgn, int i) { result = MkAssignNode(assgn, i) } - - /** - * Gets the instruction corresponding to the `i`th initialization happening - * at `spec` (0-based). - */ - AssignInstruction initInstruction(ValueSpec spec, int i) { result = MkAssignNode(spec, i) } - - /** - * Gets the instruction corresponding to the assignment of the key variable - * of range statement `rs`. - */ - AssignInstruction assignKeyInstruction(RangeStmt rs) { result = MkAssignNode(rs, 0) } - - /** - * Gets the instruction corresponding to the assignment of the value variable - * of range statement `rs`. - */ - AssignInstruction assignValueInstruction(RangeStmt rs) { result = MkAssignNode(rs, 1) } - - /** - * Gets the instruction corresponding to the implicit initialization of `v` - * to its zero value. - */ - EvalImplicitInitInstruction implicitInitInstruction(ValueEntity v) { result = MkZeroInitNode(v) } - - /** - * Gets the instruction corresponding to the extraction of the `idx`th element - * of the tuple produced by `base`. - */ - ExtractTupleElementInstruction extractTupleElement(Instruction base, int idx) { - result.extractsElement(base, idx) - } - - /** - * Gets the instruction corresponding to the implicit lower bound of slice `e`, if any. - */ - EvalImplicitLowerSliceBoundInstruction implicitLowerSliceBoundInstruction(SliceExpr e) { - result = MkImplicitLowerSliceBound(e) - } - - /** - * Gets the instruction corresponding to the implicit upper bound of slice `e`, if any. - */ - EvalImplicitUpperSliceBoundInstruction implicitUpperSliceBoundInstruction(SliceExpr e) { - result = MkImplicitUpperSliceBound(e) - } - - /** - * Gets the instruction corresponding to the implicit maximum bound of slice `e`, if any. - */ - EvalImplicitMaxSliceBoundInstruction implicitMaxSliceBoundInstruction(SliceExpr e) { - result = MkImplicitMaxSliceBound(e) - } - - /** - * Gets the implicit dereference instruction for `e`, where `e` is a pointer used as the base - * in a field/method access, element access, or slice expression. - */ - EvalImplicitDerefInstruction implicitDerefInstruction(Expr e) { result = MkImplicitDeref(e) } - - /** Gets the base of `insn`, if `insn` is an implicit field read. */ - Instruction lookThroughImplicitFieldRead(Instruction insn) { - result = insn.(ImplicitFieldReadInstruction).getBaseInstruction() - } -} From f02e1520932ccd11639677740524d6526efd9fe8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:33:25 +0000 Subject: [PATCH 06/34] Fix overlay annotation in ControlFlowGraphShared.qll Agent-Logs-Url: https://github.com/github/codeql/sessions/011f8465-5ad3-44e2-9d84-f5314ea1afb2 Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com> --- go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 70be225eff3d..389274be2156 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -4,7 +4,7 @@ * Everything is wrapped in `GoCfg` to avoid name conflicts with the existing * CFG implementation during the transition. */ -overlay[local?] +overlay[local] module; private import codeql.controlflow.ControlFlowGraph as CfgLib From 478b75e0542c93364f624e2366937b94c2140ce2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:07:37 +0000 Subject: [PATCH 07/34] Fix compiler errors and warnings: remove redundant type checks and isIn guards, mark implicitFieldSelection as additional Agent-Logs-Url: https://github.com/github/codeql/sessions/8d96128c-44db-4ba1-9eca-1a5b37f54442 Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com> --- .../semmle/go/controlflow/ControlFlowGraphShared.qll | 2 +- go/ql/lib/semmle/go/controlflow/IR.qll | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 389274be2156..c92ede1e9b94 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -524,7 +524,7 @@ module GoCfg { private predicate notBlankIdent(Go::Expr e) { not e instanceof Go::BlankIdent } /** Helper: implicit field selection for promoted selectors */ - predicate implicitFieldSelection(Ast::AstNode e, int index, Go::Field implicitField) { + additional predicate implicitFieldSelection(Ast::AstNode e, int index, Go::Field implicitField) { exists(Go::StructType baseType, Go::PromotedField child, int implicitFieldDepth | baseType = e.(Go::PromotedSelector).getSelectedStructType() and ( diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll b/go/ql/lib/semmle/go/controlflow/IR.qll index 0dd57d11c7d5..0a2b07859ff7 100644 --- a/go/ql/lib/semmle/go/controlflow/IR.qll +++ b/go/ql/lib/semmle/go/controlflow/IR.qll @@ -26,9 +26,9 @@ module IR { or this.isAdditional(_, _) or - this.isAfterTrue(_) and not this.isIn(_) + this.isAfterTrue(_) or - this.isAfterFalse(_) and not this.isIn(_) + this.isAfterFalse(_) } /** Holds if this instruction reads the value of variable or constant `v`. */ @@ -164,9 +164,9 @@ module IR { /** A condition guard instruction, representing a known boolean outcome for a condition. */ private class ConditionGuardInstruction extends Instruction { ConditionGuardInstruction() { - this.isAfterTrue(_) and not this.isIn(_) + this.isAfterTrue(_) or - this.isAfterFalse(_) and not this.isIn(_) + this.isAfterFalse(_) } } @@ -176,7 +176,7 @@ module IR { class EvalInstruction extends Instruction { Expr e; - EvalInstruction() { this.isIn(e) and e instanceof Expr } + EvalInstruction() { this.isIn(e) } /** Gets the expression underlying this instruction. */ Expr getExpr() { result = e } @@ -778,7 +778,7 @@ module IR { class DeclareFunctionInstruction extends Instruction { FuncDecl fd; - DeclareFunctionInstruction() { this.isIn(fd) and fd instanceof FuncDecl } + DeclareFunctionInstruction() { this.isIn(fd) } override Type getResultType() { result = fd.getType() } } From 84b03b2b1a5f65ce739e443afec5cbb696749572 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:16:17 +0000 Subject: [PATCH 08/34] Add File as a CFG root to support file-level declarations Agent-Logs-Url: https://github.com/github/codeql/sessions/2867f173-c3f1-4564-9885-3ff4517bbf30 Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com> --- .../go/controlflow/ControlFlowGraph.qll | 21 +++++++++++++------ .../go/controlflow/ControlFlowGraphShared.qll | 11 +++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll index 49d11e3c561d..0bbb3045d2df 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll @@ -9,17 +9,26 @@ private import ControlFlowGraphShared /** Provides helper predicates for mapping between CFG nodes and the AST. */ module ControlFlow { - /** A function with which a CFG is associated. */ + /** A file or function with which a CFG is associated. */ class Root extends AstNode { - Root() { exists(this.(FuncDef).getBody()) } + Root() { + exists(this.(FuncDef).getBody()) + or + exists(this.(File).getADecl()) + } - /** Holds if `nd` belongs to this function. */ - predicate isRootOf(AstNode nd) { this = nd.getEnclosingFunction() } + /** Holds if `nd` belongs to this file or function. */ + predicate isRootOf(AstNode nd) { + this = nd.getEnclosingFunction() + or + not exists(nd.getEnclosingFunction()) and + this = nd.getFile() + } - /** Gets the synthetic entry node of the CFG for this function. */ + /** Gets the synthetic entry node of the CFG for this file or function. */ EntryNode getEntryNode() { result = ControlFlow::entryNode(this) } - /** Gets the synthetic exit node of the CFG for this function. */ + /** Gets the synthetic exit node of the CFG for this file or function. */ ExitNode getExitNode() { result = ControlFlow::exitNode(this) } } diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index c92ede1e9b94..00f366ea38c6 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -63,7 +63,11 @@ module GoCfg { } class Callable extends AstNode { - Callable() { exists(this.(Go::FuncDef).getBody()) } + Callable() { + exists(this.(Go::FuncDef).getBody()) + or + exists(this.(Go::File).getADecl()) + } } AstNode callableGetBody(Callable c) { result = c } @@ -72,6 +76,11 @@ module GoCfg { result = node and node instanceof Callable or not node instanceof Callable and result = node.getEnclosingFunction() + or + not node instanceof Callable and + not exists(node.getEnclosingFunction()) and + result = node.getFile() and + result instanceof Callable } class Stmt = Go::Stmt; From 392b9733bfcf8a6f0dcb8af25a08d14b9a3dc1bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:29:39 +0000 Subject: [PATCH 09/34] Add missing QLdoc comments to IR.qll classes, predicates, and newtype branches Agent-Logs-Url: https://github.com/github/codeql/sessions/12c0f9c8-1b85-4db4-b26f-8dbce818aa5a Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com> --- go/ql/lib/semmle/go/controlflow/IR.qll | 162 +++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll b/go/ql/lib/semmle/go/controlflow/IR.qll index 0a2b07859ff7..22300f3688ba 100644 --- a/go/ql/lib/semmle/go/controlflow/IR.qll +++ b/go/ql/lib/semmle/go/controlflow/IR.qll @@ -340,6 +340,7 @@ module IR { /** Gets the field being read. */ Field getField() { result = fld } + /** Gets the instruction computing the base value on which the field is read. */ Instruction getBaseInstruction() { exists(ImplicitFieldReadInstruction fri | fri.getSelectorExpr() = sel and fri.getIndex() = pragma[only_bind_into](idx + 1) @@ -374,8 +375,10 @@ module IR { MethodReadInstruction() { e.getSelector() = method.getAReference() } + /** Gets the instruction computing the receiver value on which the method is looked up. */ Instruction getReceiver() { result = selectorBase(e) } + /** Gets the method being looked up. */ Method getMethod() { result = method } override predicate readsMethod(Instruction receiver, Method m) { @@ -389,6 +392,7 @@ module IR { class ElementReadInstruction extends ComponentReadInstruction, EvalInstruction { override IndexExpr e; + /** Gets the instruction computing the index of the element being looked up. */ Instruction getIndex() { result = evalExprInstruction(e.getIndex()) } override predicate readsElement(Instruction base, Instruction index) { @@ -402,18 +406,22 @@ module IR { class SliceInstruction extends EvalInstruction { override SliceExpr e; + /** Gets the instruction computing the base value from which the slice is constructed. */ Instruction getBase() { result = selectorBase(e) } + /** Gets the instruction computing the lower bound of the slice. */ Instruction getLow() { result = evalExprInstruction(e.getLow()) or result = implicitLowerSliceBoundInstruction(e) } + /** Gets the instruction computing the upper bound of the slice. */ Instruction getHigh() { result = evalExprInstruction(e.getHigh()) or result = implicitUpperSliceBoundInstruction(e) } + /** Gets the instruction computing the capacity of the slice. */ Instruction getMax() { result = evalExprInstruction(e.getMax()) or result = implicitMaxSliceBoundInstruction(e) @@ -438,10 +446,13 @@ module IR { lhs = MkLiteralElementTarget(this) and initialization = true } + /** Gets the target to which this instruction writes. */ WriteTarget getLhs() { result = lhs } + /** Holds if this instruction initializes a literal. */ predicate isInitialization() { initialization = true } + /** Gets the instruction computing the value this instruction writes. */ Instruction getRhs() { none() } override predicate writes(ValueEntity v, Instruction rhs) { @@ -463,6 +474,7 @@ module IR { elt = lit.getElement(litIdx) } + /** Gets the instruction allocating the composite literal. */ Instruction getBase() { result = evalExprInstruction(lit) } override Instruction getRhs() { @@ -479,6 +491,7 @@ module IR { class InitLiteralStructFieldInstruction extends InitLiteralComponentInstruction { override StructLit lit; + /** Gets the name of the initialized field. */ pragma[nomagic] string getFieldName() { if elt instanceof KeyValueExpr @@ -486,6 +499,7 @@ module IR { else pragma[only_bind_out](lit.getStructType()).hasOwnField(litIdx, result, _, _) } + /** Gets the initialized field. */ Field getField() { result.getDeclaringType() = lit.getStructType() and result.getName() = this.getFieldName() @@ -507,6 +521,7 @@ module IR { ) } + /** Gets the instruction computing the index of the initialized element. */ Instruction getIndex() { result = evalExprInstruction(elt.(KeyValueExpr).getKey()) or @@ -514,23 +529,29 @@ module IR { } } + /** An IR instruction that initializes an element of an array literal. */ class InitLiteralArrayElementInstruction extends InitLiteralElementInstruction { override ArrayType literalType; } + /** An IR instruction that initializes an element of a slice literal. */ class InitLiteralSliceElementInstruction extends InitLiteralElementInstruction { override SliceType literalType; } + /** An IR instruction that initializes an element of a map literal. */ class InitLiteralMapElementInstruction extends InitLiteralElementInstruction { override MapType literalType; } + /** An IR instruction that writes to a field. */ class FieldWriteInstruction extends WriteInstruction { override FieldTarget lhs; + /** Gets the instruction computing the base value on which the field is written. */ Instruction getBase() { result = lhs.getBase() } + /** Gets the field being written. */ Field getField() { result = lhs.getField() } override predicate writesField(Instruction base, Field f, Instruction rhs) { @@ -538,11 +559,14 @@ module IR { } } + /** An IR instruction that writes to an element of an array, slice, or map. */ class ElementWriteInstruction extends WriteInstruction { override ElementTarget lhs; + /** Gets the instruction computing the base value on which the element is written. */ Instruction getBase() { result = lhs.getBase() } + /** Gets the instruction computing the element index being written. */ Instruction getIndex() { result = lhs.getIndex() } override predicate writesElement(Instruction base, Instruction index) { @@ -638,11 +662,15 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(assgn) } } + /** + * An instruction that computes the (implicit) right-hand side of a compound assignment. + */ class EvalCompoundAssignRhsInstruction extends Instruction { CompoundAssignStmt assgn; EvalCompoundAssignRhsInstruction() { this.isAdditional(assgn, "compound-rhs") } + /** Gets the corresponding compound assignment statement. */ CompoundAssignStmt getAssignment() { result = assgn } override Type getResultType() { result = assgn.getRhs().getType() } @@ -650,6 +678,7 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(assgn) } } + /** An instruction extracting a component of a tuple value. */ class ExtractTupleElementInstruction extends Instruction { AstNode s; int i; @@ -677,6 +706,7 @@ module IR { ) } + /** Gets the instruction computing the tuple value from which the element is extracted. */ Instruction getBase() { exists(Expr baseExpr | baseExpr = s.(Assignment).getRhs() or @@ -692,6 +722,7 @@ module IR { result = evalExprInstruction(s.(CallExpr).getArgument(0).stripParens()) } + /** Holds if this instruction extracts element `idx` from the tuple `base`. */ predicate extractsElement(Instruction base, int idx) { base = this.getBase() and idx = i } override Type getResultType() { @@ -733,6 +764,10 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(s) } } + /** + * An instruction that computes the zero value to which a variable without an initializer + * expression is initialized. + */ class EvalImplicitInitInstruction extends Instruction { ValueEntity v; int idx; @@ -775,6 +810,7 @@ module IR { override predicate isPlatformIndependentConstant() { any() } } + /** An instruction that declares a function. */ class DeclareFunctionInstruction extends Instruction { FuncDecl fd; @@ -783,6 +819,7 @@ module IR { override Type getResultType() { result = fd.getType() } } + /** An instruction that corresponds to a `defer` statement. */ class DeferInstruction extends Instruction { DeferStmt defer; @@ -791,6 +828,7 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(defer) } } + /** An instruction that corresponds to a `go` statement. */ class GoInstruction extends Instruction { GoStmt go; @@ -799,6 +837,7 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(go) } } + /** An instruction that corresponds to an increment or decrement statement. */ class IncDecInstruction extends WriteInstruction { IncDecStmt ids; @@ -811,11 +850,16 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(ids) } } + /** + * An instruction that computes the (implicit) right-hand side of an increment or + * decrement statement. + */ class EvalIncDecRhsInstruction extends Instruction { IncDecStmt ids; EvalIncDecRhsInstruction() { this.isAdditional(ids, "incdec-rhs") } + /** Gets the corresponding increment or decrement statement. */ IncDecStmt getStmt() { result = ids } override Type getResultType() { result = ids.getOperand().getType() } @@ -823,11 +867,13 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(ids) } } + /** An instruction computing the implicit operand `1` in an increment or decrement statement. */ class EvalImplicitOneInstruction extends Instruction { IncDecStmt ids; EvalImplicitOneInstruction() { this.isAdditional(ids, "implicit-one") } + /** Gets the corresponding increment or decrement statement. */ IncDecStmt getStmt() { result = ids } override Type getResultType() { result = ids.getOperand().getType() } @@ -843,24 +889,29 @@ module IR { override predicate isPlatformIndependentConstant() { any() } } + /** An instruction corresponding to a return from a function. */ class ReturnInstruction extends Instruction { ReturnStmt ret; ReturnInstruction() { this.isAdditional(ret, "return") } + /** Gets the corresponding `ReturnStmt`. */ ReturnStmt getReturnStmt() { result = ret } + /** Holds if this statement returns multiple results. */ predicate returnsMultipleResults() { exists(ExtractTupleElementInstruction ext | ext.isAdditional(ret, _)) or ret.getNumExpr() > 1 } + /** Gets the instruction whose result is the (unique) result returned by this statement. */ Instruction getResult() { not this.returnsMultipleResults() and result = evalExprInstruction(ret.getExpr()) } + /** Gets the instruction whose result is the `i`th result returned by this statement. */ Instruction getResult(int i) { result.isAdditional(ret, _) and result.(ExtractTupleElementInstruction).extractsElement(_, i) @@ -872,6 +923,10 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(ret) } } + /** + * An instruction that represents the implicit assignment to a result variable + * performed by a return statement. + */ class WriteResultInstruction extends WriteInstruction { ResultVariable var; int idx; @@ -889,6 +944,7 @@ module IR { override Instruction getRhs() { result = this.getReturnInstruction().getResult(idx) } + /** Gets the result variable being assigned. */ ResultVariable getResultVariable() { result = var } override Type getResultType() { result = var.getType() } @@ -896,6 +952,10 @@ module IR { override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } } + /** + * An instruction that reads the final value of a result variable upon returning + * from a function. + */ class ReadResultInstruction extends Instruction { ResultVariable var; int idx; @@ -913,6 +973,7 @@ module IR { override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } } + /** An instruction corresponding to a send statement. */ class SendInstruction extends Instruction { SendStmt send; @@ -921,6 +982,7 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(send) } } + /** An instruction initializing a parameter to the corresponding argument. */ class InitParameterInstruction extends WriteInstruction { Parameter parm; int idx; @@ -938,6 +1000,7 @@ module IR { override ControlFlow::Root getRoot() { result = parm.getFunction() } } + /** An instruction reading the value of a function argument. */ class ReadArgumentInstruction extends Instruction { Parameter parm; int idx; @@ -953,6 +1016,7 @@ module IR { override ControlFlow::Root getRoot() { result = parm.getFunction() } } + /** An instruction initializing a result variable to its zero value. */ class InitResultInstruction extends WriteInstruction { ResultVariable res; int idx; @@ -1005,16 +1069,23 @@ module IR { override predicate isPlatformIndependentConstant() { any() } } + /** An instruction that gets the next key-value pair in a range loop. */ class GetNextEntryInstruction extends Instruction { RangeStmt rs; GetNextEntryInstruction() { this.isAdditional(rs, "next") } + /** + * Gets the instruction computing the value whose key-value pairs this instruction reads. + */ Instruction getDomain() { result = evalExprInstruction(rs.getDomain()) } override ControlFlow::Root getRoot() { result.isRootOf(rs) } } + /** + * An instruction computing the implicit `true` value in an expression-less `switch` statement. + */ class EvalImplicitTrueInstruction extends Instruction { ExpressionSwitchStmt stmt; @@ -1033,6 +1104,10 @@ module IR { override predicate isPlatformIndependentConstant() { any() } } + /** + * An instruction corresponding to the implicit comparison or type check performed by an + * expression in a `case` clause. + */ class CaseInstruction extends Instruction { CaseClause cc; int i; @@ -1045,6 +1120,10 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(cc) } } + /** + * An instruction corresponding to the implicit declaration and assignment of a variable + * in a type switch case clause. + */ class TypeSwitchImplicitVariableInstruction extends Instruction { CaseClause cc; @@ -1058,6 +1137,7 @@ module IR { override ControlFlow::Root getRoot() { result.isRootOf(cc) } } + /** An instruction computing the implicit lower bound of a slice expression. */ class EvalImplicitLowerSliceBoundInstruction extends Instruction { SliceExpr slice; @@ -1076,6 +1156,7 @@ module IR { override predicate isPlatformIndependentConstant() { any() } } + /** An instruction computing the implicit upper bound of a slice expression. */ class EvalImplicitUpperSliceBoundInstruction extends Instruction { SliceExpr slice; @@ -1086,6 +1167,7 @@ module IR { override Type getResultType() { result instanceof IntType } } + /** An instruction computing the implicit maximum bound of a slice expression. */ class EvalImplicitMaxSliceBoundInstruction extends Instruction { SliceExpr slice; @@ -1096,11 +1178,16 @@ module IR { override Type getResultType() { result instanceof IntType } } + /** + * An instruction computing the implicit dereference of a pointer used as the base of a field + * or method access, element access, or slice expression. + */ class EvalImplicitDerefInstruction extends Instruction { Expr e; EvalImplicitDerefInstruction() { this.isAdditional(e, "implicit-deref") } + /** Gets the operand that is being dereferenced. */ Expr getOperand() { result = e } override Type getResultType() { @@ -1112,6 +1199,7 @@ module IR { /** A representation of the target of a write instruction. */ newtype TWriteTarget = + /** A left-hand side of an assignment. */ MkLhs(ControlFlow::Node write, Expr lhs) { exists(AstNode assgn, int i | write.isAdditional(assgn, "assign:" + i.toString()) | lhs = assgn.(Assignment).getLhs(i).stripParens() @@ -1137,9 +1225,12 @@ module IR { lhs = fd.getResultVar(idx).getDeclaration() ) } or + /** A composite literal element target. */ MkLiteralElementTarget(InitLiteralComponentInstruction elt) or + /** A result variable write target. */ MkResultWriteTarget(WriteResultInstruction w) + /** A representation of the target of a write instruction. */ class WriteTarget extends TWriteTarget { ControlFlow::Node w; @@ -1147,20 +1238,31 @@ module IR { this = MkLhs(w, _) or this = MkLiteralElementTarget(w) or this = MkResultWriteTarget(w) } + /** Gets the write instruction of which this is the target. */ WriteInstruction getWrite() { result = w } + /** Gets the name of the variable or field being written to, if any. */ string getName() { none() } + /** Gets the SSA variable being written to, if any. */ SsaVariable asSsaVariable() { this.getWrite() = result.getDefinition().(SsaExplicitDefinition).getInstruction() } + /** Holds if `e` is the variable or field being written to. */ predicate refersTo(ValueEntity e) { none() } + /** Gets a textual representation of this target. */ string toString() { result = "write target" } + /** Gets the source location for this element. */ Location getLocation() { none() } + /** + * DEPRECATED: Use `getLocation()` instead. + * + * Holds if this element is at the specified location. + */ deprecated predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { @@ -1175,6 +1277,7 @@ module IR { } } + /** A reference to a variable or constant, used as the target of a write. */ class VarOrConstTarget extends WriteTarget { Expr loc; @@ -1215,11 +1318,14 @@ module IR { ) } + /** Gets the variable this refers to, if any. */ Variable getVariable() { this.refersTo(result) } + /** Gets the constant this refers to, if any. */ Constant getConstant() { this.refersTo(result) } } + /** A reference to a field, used as the target of a write. */ class FieldTarget extends WriteTarget { FieldTarget() { exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.getBase() instanceof ValueExpr) @@ -1227,12 +1333,14 @@ module IR { w instanceof InitLiteralStructFieldInstruction } + /** Gets the instruction computing the base value on which this field is accessed. */ Instruction getBase() { exists(SelectorExpr sel | this = MkLhs(_, sel) | result = selectorBase(sel)) or result = w.(InitLiteralStructFieldInstruction).getBase() } + /** Gets the type of the base of this field access, that is, the type that contains the field. */ Type getBaseType() { result = this.getBase().getResultType() } override predicate refersTo(ValueEntity e) { @@ -1243,9 +1351,13 @@ module IR { override string getName() { exists(Field f | this.refersTo(f) | result = f.getName()) } + /** Gets the field this refers to, if it can be determined. */ Field getField() { this.refersTo(result) } } + /** + * A reference to an element of an array, slice, or map, used as the target of a write. + */ class ElementTarget extends WriteTarget { ElementTarget() { this = MkLhs(_, any(IndexExpr idx)) @@ -1253,12 +1365,14 @@ module IR { w instanceof InitLiteralElementInstruction } + /** Gets the instruction computing the base value of this element reference. */ Instruction getBase() { exists(IndexExpr idx | this = MkLhs(_, idx) | result = selectorBase(idx)) or result = w.(InitLiteralComponentInstruction).getBase() } + /** Gets the instruction computing the index of this element reference. */ Instruction getIndex() { exists(IndexExpr idx | this = MkLhs(_, idx) | result = evalExprInstruction(idx.getIndex())) or @@ -1266,6 +1380,9 @@ module IR { } } + /** + * A pointer dereference, used as the target of a write. + */ class PointerTarget extends WriteTarget { Expr lhs; @@ -1274,6 +1391,7 @@ module IR { (lhs instanceof StarExpr or lhs instanceof DerefExpr) } + /** Gets the instruction computing the pointer value being dereferenced. */ Instruction getBase() { exists(Expr base | base = lhs.(StarExpr).getBase() or base = lhs.(DerefExpr).getOperand() | result = evalExprInstruction(base) @@ -1290,32 +1408,58 @@ module IR { result = evalExprInstruction(e.(ParenExpr).getExpr()) } + /** + * Gets the instruction corresponding to the initialization of `r`. + */ InitParameterInstruction initRecvInstruction(ReceiverVariable r) { exists(FuncDef fd, int i | fd.getParameter(i) = r and result.isAdditional(fd, "param-init:" + i.toString()) ) } + /** + * Gets the instruction corresponding to the initialization of `p`. + */ InitParameterInstruction initParamInstruction(Parameter p) { exists(FuncDef fd, int i | fd.getParameter(i) = p and result.isAdditional(fd, "param-init:" + i.toString()) ) } + /** + * Gets the instruction corresponding to the `i`th assignment happening at + * `assgn` (0-based). + */ AssignInstruction assignInstruction(Assignment assgn, int i) { result.isAdditional(assgn, "assign:" + i.toString()) and exists(assgn.getLhs(i)) } + /** + * Gets the instruction corresponding to the `i`th initialization happening + * at `spec` (0-based). + */ AssignInstruction initInstruction(ValueSpec spec, int i) { result.isAdditional(spec, "assign:" + i.toString()) and exists(spec.getNameExpr(i)) } + /** + * Gets the instruction corresponding to the assignment of the key variable + * of range statement `rs`. + */ AssignInstruction assignKeyInstruction(RangeStmt rs) { result.isAdditional(rs, "assign:0") } + /** + * Gets the instruction corresponding to the assignment of the value variable + * of range statement `rs`. + */ AssignInstruction assignValueInstruction(RangeStmt rs) { result.isAdditional(rs, "assign:1") } + /** + * Gets the instruction corresponding to the implicit initialization of `v` + * to its zero value. + */ EvalImplicitInitInstruction implicitInitInstruction(ValueEntity v) { exists(ValueSpec spec, int i | spec.getNameExpr(i) = v.getDeclaration() and @@ -1323,26 +1467,44 @@ module IR { ) } + /** + * Gets the instruction corresponding to the extraction of the `idx`th element + * of the tuple produced by `base`. + */ ExtractTupleElementInstruction extractTupleElement(Instruction base, int idx) { result.extractsElement(base, idx) } + /** + * Gets the instruction corresponding to the implicit lower bound of slice `e`, if any. + */ EvalImplicitLowerSliceBoundInstruction implicitLowerSliceBoundInstruction(SliceExpr e) { result.isAdditional(e, "implicit-low") } + /** + * Gets the instruction corresponding to the implicit upper bound of slice `e`, if any. + */ EvalImplicitUpperSliceBoundInstruction implicitUpperSliceBoundInstruction(SliceExpr e) { result.isAdditional(e, "implicit-high") } + /** + * Gets the instruction corresponding to the implicit maximum bound of slice `e`, if any. + */ EvalImplicitMaxSliceBoundInstruction implicitMaxSliceBoundInstruction(SliceExpr e) { result.isAdditional(e, "implicit-max") } + /** + * Gets the implicit dereference instruction for `e`, where `e` is a pointer used as the base + * in a field/method access, element access, or slice expression. + */ EvalImplicitDerefInstruction implicitDerefInstruction(Expr e) { result.isAdditional(e, "implicit-deref") } + /** Gets the base of `insn`, if `insn` is an implicit field read. */ Instruction lookThroughImplicitFieldRead(Instruction insn) { result = insn.(ImplicitFieldReadInstruction).getBaseInstruction() } From 3cdb7b5b508032befea68bd685e463edb4e994ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:37:20 +0000 Subject: [PATCH 10/34] Fix shared Go CFG expression and return-edge regressions Agent-Logs-Url: https://github.com/github/codeql/sessions/3f96ead2-cda4-479c-9e37-f38ace035870 Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com> --- .../go/controlflow/ControlFlowGraph.qll | 5 ++++- .../go/controlflow/ControlFlowGraphShared.qll | 20 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll index 0bbb3045d2df..6122ae6639f4 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll @@ -340,7 +340,10 @@ module ControlFlow { * cannot return normally, but never fails to hold of a function that can return normally. */ predicate mayReturnNormally(FuncDecl f) { - exists(GoCfg::ControlFlow::NormalExitNode exit | exit.getEnclosingCallable() = f) + exists(GoCfg::ControlFlow::NormalExitNode exit | + exit.getEnclosingCallable() = f and + exists(exit.getAPredecessor()) + ) } /** diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 00f366ea38c6..9db6fa288385 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -79,8 +79,7 @@ module GoCfg { or not node instanceof Callable and not exists(node.getEnclosingFunction()) and - result = node.getFile() and - result instanceof Callable + result = node.getFile() } class Stmt = Go::Stmt; @@ -292,6 +291,10 @@ module GoCfg { predicate preOrderExpr(Ast::Expr e) { none() } predicate postOrInOrder(Ast::AstNode n) { + n instanceof Go::ReferenceExpr + or + n instanceof Go::BasicLit + or n instanceof Go::CallExpr and not n = any(Go::DeferStmt defer).getCall() and not n = any(Go::GoStmt go_).getCall() @@ -633,6 +636,12 @@ module GoCfg { c.hasLabel(TGoLabel(lbl.getLabel())) ) or + exists(Go::FuncDef fd | + ast = fd.getBody() and + c.getSuccessorType() instanceof ReturnSuccessor and + n.isAfter(fd.getBody()) + ) + or exists(Go::LabeledStmt lbl, Go::FuncDef fd | ast = fd.getBody() and n.isBefore(lbl) and @@ -841,6 +850,13 @@ module GoCfg { n1.isAfter(lastChild) and n2.isAdditional(ret, getFirstReturnEpilogueTag(ret)) ) or + exists(Ast::AstNode lastChild | lastChild = getLastRankedChild(ret) | + // After last expr → return node directly if there is no return epilogue + not exists(getFirstReturnEpilogueTag(ret)) and + n1.isAfter(lastChild) and + n2.isIn(ret) + ) + or // No expressions → before → return node directly not exists(getRankedChild(ret, _)) and n1.isBefore(ret) and From a82c295da90f0c8c7b146e6860909081bc1272a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 11:00:10 +0000 Subject: [PATCH 11/34] Add GotoStmt, Parameter, Throw, Assignment and PatternMatchExpr to Go Ast module for updated shared CFG AstSig Agent-Logs-Url: https://github.com/github/codeql/sessions/005953d9-1d54-4071-8cd2-5a7d26f65f61 Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com> --- .../go/controlflow/ControlFlowGraphShared.qll | 49 ++++++++++++++++++- .../codeql/controlflow/ControlFlowGraph.qll | 20 ++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 9db6fa288385..30b2b3409a27 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -9,6 +9,7 @@ module; private import codeql.controlflow.ControlFlowGraph as CfgLib private import codeql.controlflow.SuccessorType +private import codeql.util.Void /** Contains the shared CFG library instantiation for Go. */ module GoCfg { @@ -72,6 +73,14 @@ module GoCfg { AstNode callableGetBody(Callable c) { result = c } + class Parameter extends AstNode { + Parameter() { none() } + + Expr getDefaultValue() { none() } + } + + Parameter callableGetParameter(Callable c, int index) { none() } + Callable getEnclosingCallable(AstNode node) { result = node and node instanceof Callable or @@ -152,12 +161,14 @@ module GoCfg { class ContinueStmt = Go::ContinueStmt; + class GotoStmt = Go::GotoStmt; + class ReturnStmt extends Go::ReturnStmt { override Expr getExpr() { result = Go::ReturnStmt.super.getExpr() } } - class ThrowStmt extends Stmt { - ThrowStmt() { none() } + class Throw extends AstNode { + Throw() { none() } Expr getExpr() { none() } } @@ -241,12 +252,46 @@ module GoCfg { boolean getValue() { result = val } } + + class Assignment extends BinaryExpr { + Assignment() { none() } + } + + class AssignExpr extends Assignment { + AssignExpr() { none() } + } + + class CompoundAssignment extends Assignment { + CompoundAssignment() { none() } + } + + class AssignLogicalAndExpr extends CompoundAssignment { + AssignLogicalAndExpr() { none() } + } + + class AssignLogicalOrExpr extends CompoundAssignment { + AssignLogicalOrExpr() { none() } + } + + class AssignNullCoalescingExpr extends CompoundAssignment { + AssignNullCoalescingExpr() { none() } + } + + class PatternMatchExpr extends Expr { + PatternMatchExpr() { none() } + + Expr getExpr() { none() } + + AstNode getPattern() { none() } + } } /** The Input module implementing InputSig1 and InputSig2 for Go. */ private module Input implements Cfg0::InputSig1, Cfg1::InputSig2 { predicate cfgCachedStageRef() { CfgCachedStage::ref() } + class CallableContext = Void; + private newtype TLabel = TGoLabel(string l) { exists(Go::LabeledStmt ls | l = ls.getLabel()) } or TFallthrough() diff --git a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll index fff877b9fcd9..964e7e2cd2fa 100644 --- a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll +++ b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll @@ -1507,6 +1507,26 @@ module Make0 Ast> { n2.isAfterValue(pme, any(BooleanSuccessor s | s.getValue() = true)) ) or + exists(PatternMatchExpr pme | + n1.isBefore(pme) and + n2.isBefore(pme.getExpr()) + or + n1.isAfter(pme.getExpr()) and + n2.isIn(pme) + or + n1.isIn(pme) and + n2.isAfterValue(pme, any(BooleanSuccessor s | s.getValue() = false)) + or + n1.isIn(pme) and + n2.isAdditional(pme, patternMatchTrueTag()) + or + n1.isAdditional(pme, patternMatchTrueTag()) and + n2.isBefore(pme.getPattern()) + or + n1.isAfter(pme.getPattern()) and + n2.isAfterValue(pme, any(BooleanSuccessor s | s.getValue() = true)) + ) + or exists(IfStmt ifstmt | n1.isBefore(ifstmt) and n2.isBefore(ifstmt.getCondition()) From 61dda7cd9b9a865b30c838b178c5af7191aed5a9 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 8 May 2026 17:52:03 +0100 Subject: [PATCH 12/34] Fix overlay annotation in PrintAst.qll --- go/ql/lib/semmle/go/PrintAst.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/ql/lib/semmle/go/PrintAst.qll b/go/ql/lib/semmle/go/PrintAst.qll index 6ea5fcc39719..c7c9bb5b4b7e 100644 --- a/go/ql/lib/semmle/go/PrintAst.qll +++ b/go/ql/lib/semmle/go/PrintAst.qll @@ -1,7 +1,7 @@ /** * Provides queries to pretty-print a Go AST as a graph. */ -overlay[local] +overlay[local?] module; import go From 20361e14a319da576be2c4212e9bef6aed326c5f Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 13 May 2026 14:34:35 +0100 Subject: [PATCH 13/34] Add go/print-cfg --- go/ql/lib/printCfg.ql | 53 +++++++++++++++++++ .../go/controlflow/ControlFlowGraph.qll | 2 + .../go/controlflow/ControlFlowGraphShared.qll | 4 +- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 go/ql/lib/printCfg.ql diff --git a/go/ql/lib/printCfg.ql b/go/ql/lib/printCfg.ql new file mode 100644 index 000000000000..f61d918ce98f --- /dev/null +++ b/go/ql/lib/printCfg.ql @@ -0,0 +1,53 @@ +/** + * @name Print CFG + * @description Produces a representation of a file's Control Flow Graph. + * This query is used by the VS Code extension. + * @id go/print-cfg + * @kind graph + * @tags ide-contextual-queries/print-cfg + */ + +import go +import semmle.go.controlflow.ControlFlowGraph +private import semmle.go.controlflow.ControlFlowGraphShared + +external string selectedSourceFile(); + +private predicate selectedSourceFileAlias = selectedSourceFile/0; + +external int selectedSourceLine(); + +private predicate selectedSourceLineAlias = selectedSourceLine/0; + +external int selectedSourceColumn(); + +private predicate selectedSourceColumnAlias = selectedSourceColumn/0; + +module ViewCfgQueryInput implements GoCfg::ControlFlow::ViewCfgQueryInputSig { + predicate selectedSourceFile = selectedSourceFileAlias/0; + + predicate selectedSourceLine = selectedSourceLineAlias/0; + + predicate selectedSourceColumn = selectedSourceColumnAlias/0; + + predicate cfgScopeSpan( + CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn + ) { + file = scope.getFile() and + scope.getLocation().getStartLine() = startLine and + scope.getLocation().getStartColumn() = startColumn and + exists(Location loc | + loc.getEndLine() = endLine and + loc.getEndColumn() = endColumn and + loc = scope.(FuncDef).getBody().getLocation() + ) + or + file = scope.(File) and + startLine = 1 and + startColumn = 1 and + endLine = file.getNumberOfLines() and + endColumn = 999999 + } +} + +import GoCfg::ControlFlow::ViewCfgQuery diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll index 6122ae6639f4..d3047f8e81bc 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll @@ -366,4 +366,6 @@ module ControlFlow { class ControlFlowNode = ControlFlow::Node; +class CfgScope = GoCfg::CfgScope; + class Write = ControlFlow::WriteNode; diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 30b2b3409a27..7686e40eaf64 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -26,6 +26,8 @@ module GoCfg { private import Cfg2 import Public + class CfgScope = Ast::Callable; + /** Holds if `e` has an implicit field selection at `index` for `implicitField`. */ predicate implicitFieldSelection(Go::AstNode e, int index, Go::Field implicitField) { Input::implicitFieldSelection(e, index, implicitField) @@ -206,7 +208,7 @@ module GoCfg { class Case extends AstNode { Case() { none() } - AstNode getAPattern() { none() } + AstNode getPattern(int index) { none() } Expr getGuard() { none() } From 1b3056137d2b9eee3d18e5244cc4384bcb7ff9f1 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 14 May 2026 00:21:08 +0100 Subject: [PATCH 14/34] Create cfg node for child of ParenExpr --- go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 7686e40eaf64..24a687cbdf70 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -62,7 +62,11 @@ module GoCfg { } AstNode getChild(AstNode n, int index) { - not skipCfg(result) and not skipCfg(n) and result = n.getChild(index) + not skipCfg(n) and + not skipCfg(result) and + exists(Go::AstNode c | c = n.getChild(index) | + if c instanceof Go::ParenExpr then result = c.(Go::ParenExpr).stripParens() else result = c + ) } class Callable extends AstNode { From 4171d330ef0b545c56e1e0247ca6b3b300f908fa Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 19 May 2026 21:19:20 +0100 Subject: [PATCH 15/34] Model non-returning functions in CFG --- go/ql/lib/semmle/go/Scopes.qll | 15 +++++++++++++-- .../go/controlflow/ControlFlowGraphShared.qll | 12 ++++++++++++ go/ql/lib/semmle/go/frameworks/Zap.qll | 2 +- go/ql/lib/semmle/go/frameworks/stdlib/Log.qll | 2 +- go/ql/lib/semmle/go/frameworks/stdlib/Os.qll | 2 +- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/go/ql/lib/semmle/go/Scopes.qll b/go/ql/lib/semmle/go/Scopes.qll index 9f18290fb011..735dbe80d590 100644 --- a/go/ql/lib/semmle/go/Scopes.qll +++ b/go/ql/lib/semmle/go/Scopes.qll @@ -437,11 +437,12 @@ class Function extends ValueEntity, @functionobject { * This predicate is an over-approximation: it may hold for functions that can never * return normally, but it never fails to hold for functions that can. * - * Note this is declared here and not in `DeclaredFunction` so that library models can override this - * by extending `Function` rather than having to remember to extend `DeclaredFunction`. + * Library models should not override this predicate; override `mustNotReturnNormally` + * instead, so that the control-flow graph construction can take the model into account. */ predicate mayReturnNormally() { not this.mustPanic() and + not this.mustNotReturnNormally() and (ControlFlow::mayReturnNormally(this.getFuncDecl()) or not exists(this.getBody())) } @@ -461,6 +462,16 @@ class Function extends ValueEntity, @functionobject { */ predicate mustPanic() { none() } + /** + * Holds if calling this function never returns normally (for example because it + * always panics, exits the process, or loops forever). + * + * Unlike `mayReturnNormally`, this predicate must be defined without reference to + * the control-flow graph, so that it can be used during CFG construction to + * suppress normal-flow successors of calls to this function. + */ + predicate mustNotReturnNormally() { none() } + /** Gets the number of parameters of this function. */ int getNumParameter() { result = this.getType().(SignatureType).getNumParameter() } diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 24a687cbdf70..49a14936cd8b 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -634,6 +634,18 @@ module GoCfg { c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and always = false or + // Calls to functions that never return normally (e.g. `os.Exit`, `log.Fatal`, + // `panic`) must suppress normal flow past the call site. We emit an `always` + // exception completion so that the shared library's default In->After step + // is suppressed. + ast instanceof Go::CallExpr and + exists(Go::Function target | target = ast.(Go::CallExpr).getTarget() | + target.mustPanic() or target.mustNotReturnNormally() + ) and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = true + or ast instanceof Go::DivExpr and not ast.(Go::Expr).isConst() and n.isIn(ast) and diff --git a/go/ql/lib/semmle/go/frameworks/Zap.qll b/go/ql/lib/semmle/go/frameworks/Zap.qll index b634d8e97952..1042ce02c457 100644 --- a/go/ql/lib/semmle/go/frameworks/Zap.qll +++ b/go/ql/lib/semmle/go/frameworks/Zap.qll @@ -54,7 +54,7 @@ module Zap { this.hasQualifiedName(packagePath(), "SugaredLogger", "Fatal" + getSuffix()) } - override predicate mayReturnNormally() { none() } + override predicate mustNotReturnNormally() { any() } } /** A Zap logging function which always panics. */ diff --git a/go/ql/lib/semmle/go/frameworks/stdlib/Log.qll b/go/ql/lib/semmle/go/frameworks/stdlib/Log.qll index a5ebca68be54..4479dc99daa4 100644 --- a/go/ql/lib/semmle/go/frameworks/stdlib/Log.qll +++ b/go/ql/lib/semmle/go/frameworks/stdlib/Log.qll @@ -38,7 +38,7 @@ module Log { private class FatalLogFunction extends Function { FatalLogFunction() { this.hasQualifiedName("log", ["Fatal", "Fatalf", "Fatalln"]) } - override predicate mayReturnNormally() { none() } + override predicate mustNotReturnNormally() { any() } } // These models are not implemented using Models-as-Data because they represent reverse flow. diff --git a/go/ql/lib/semmle/go/frameworks/stdlib/Os.qll b/go/ql/lib/semmle/go/frameworks/stdlib/Os.qll index 0a633de08c82..6a3550e1cbd4 100644 --- a/go/ql/lib/semmle/go/frameworks/stdlib/Os.qll +++ b/go/ql/lib/semmle/go/frameworks/stdlib/Os.qll @@ -12,7 +12,7 @@ module Os { private class Exit extends Function { Exit() { this.hasQualifiedName("os", "Exit") } - override predicate mayReturnNormally() { none() } + override predicate mustNotReturnNormally() { any() } } // These models are not implemented using Models-as-Data because they represent reverse flow. From 69d9ec9b0a09214a9b037890d733cb7cc2617976 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 20 May 2026 15:35:34 +0100 Subject: [PATCH 16/34] Tweak `getEnclosingCallable` --- go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 49a14936cd8b..bbdc5b68b136 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -88,11 +88,12 @@ module GoCfg { Parameter callableGetParameter(Callable c, int index) { none() } Callable getEnclosingCallable(AstNode node) { - result = node and node instanceof Callable + node instanceof Go::FuncDef and result = node or - not node instanceof Callable and result = node.getEnclosingFunction() + not node instanceof Go::FuncDef and + result = node.getEnclosingFunction() or - not node instanceof Callable and + not node instanceof Go::FuncDef and not exists(node.getEnclosingFunction()) and result = node.getFile() } From b758f2352670f54e7bba672bc186ae06d7c0b7a4 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 20 May 2026 16:17:51 +0100 Subject: [PATCH 17/34] Fix edges to function exit with result variables --- .../go/controlflow/ControlFlowGraphShared.qll | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index bbdc5b68b136..0e95d05aaa37 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -703,7 +703,13 @@ module GoCfg { exists(Go::FuncDef fd | ast = fd.getBody() and c.getSuccessorType() instanceof ReturnSuccessor and - n.isAfter(fd.getBody()) + ( + // If the function has result variables, route the return completion + // through the result-read epilogue before reaching the function exit. + exists(fd.getResultVar(0)) and n.isAdditional(fd, "result-read:0") + or + not exists(fd.getResultVar(_)) and n.isAfter(fd.getBody()) + ) ) or exists(Go::LabeledStmt lbl, Go::FuncDef fd | @@ -1496,13 +1502,13 @@ module GoCfg { ) ) or - // After(body) → first epilogue or After(fd) if no result vars + // After(body) → After(fd). Only reachable when there are no result + // variables; with result variables, Go requires the body to end in a + // terminating statement, and the result-read epilogue is entered from + // the return completion (see endAbruptCompletion). n1.isAfter(fd.getBody()) and - ( - exists(fd.getResultVar(0)) and n2.isAdditional(fd, "result-read:0") - or - not exists(fd.getResultVar(_)) and n2.isAfter(fd) - ) + not exists(fd.getResultVar(_)) and + n2.isAfter(fd) or // result-read:j → result-read:(j+1) or After(fd) exists(int j | exists(fd.getResultVar(j)) | From f10dc17c66c6f2fb1477f580f24fb388c6f0f746 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 21 May 2026 11:50:09 +0100 Subject: [PATCH 18/34] Do not include comments in the CFG --- go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 0e95d05aaa37..e736460be634 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -56,6 +56,10 @@ module GoCfg { or e instanceof Go::ParenExpr or + e instanceof Go::CommentGroup + or + e instanceof Go::Comment + or e = any(Go::ImportSpec is).getPathExpr() or e.getParent*() = any(Go::ArrayTypeExpr ate).getLength() From e5059001fb0e2bd194b9f32ae0be31631199358c Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 21 May 2026 12:52:18 +0100 Subject: [PATCH 19/34] Use shared CFG implementation of for loops --- .../go/controlflow/ControlFlowGraphShared.qll | 52 ++----------------- 1 file changed, 4 insertions(+), 48 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index e736460be634..bd8011c54ca8 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -151,13 +151,13 @@ module GoCfg { } class ForStmt extends LoopStmt { - ForStmt() { none() } + ForStmt() { this instanceof Go::ForStmt } - Expr getInit(int index) { none() } + AstNode getInit(int index) { index = 0 and result = this.(Go::ForStmt).getInit() } - Expr getCondition() { none() } + Expr getCondition() { result = this.(Go::ForStmt).getCond() } - Expr getUpdate(int index) { none() } + AstNode getUpdate(int index) { index = 0 and result = this.(Go::ForStmt).getPost() } } class ForeachStmt extends LoopStmt { @@ -727,7 +727,6 @@ module GoCfg { predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { ifWithInit(n1, n2) or - forLoop(n1, n2) or rangeLoop(n1, n2) or switchStmt(n1, n2) or selectStmt(n1, n2) or @@ -1192,49 +1191,6 @@ module GoCfg { ) } - private predicate forLoop(PreControlFlowNode n1, PreControlFlowNode n2) { - exists(Go::ForStmt s | - exists(PreControlFlowNode cond | - ( - cond.isBefore(s.getCond()) - or - not exists(s.getCond()) and cond.isBefore(s.getBody()) - ) - | - n1.isBefore(s) and - ( - n2.isBefore(s.getInit()) - or - not exists(s.getInit()) and n2 = cond - ) - or - n1.isAfter(s.getInit()) and n2 = cond - or - n1.isAfterTrue(s.getCond()) and n2.isBefore(s.getBody()) - or - n1.isAfterFalse(s.getCond()) and n2.isAfter(s) - or - not exists(s.getCond()) and - n1.isAfter(s.getBody()) and - ( - n2.isBefore(s.getPost()) - or - not exists(s.getPost()) and n2.isBefore(s.getBody()) - ) - or - exists(s.getCond()) and - n1.isAfter(s.getBody()) and - ( - n2.isBefore(s.getPost()) - or - not exists(s.getPost()) and n2 = cond - ) - or - n1.isAfter(s.getPost()) and n2 = cond - ) - ) - } - private predicate rangeLoop(PreControlFlowNode n1, PreControlFlowNode n2) { exists(Go::RangeStmt s | n1.isBefore(s) and n2.isBefore(s.getDomain()) From 061a46064c72ac025b49b3d8673a98abc14f950a Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 21 May 2026 16:27:29 +0100 Subject: [PATCH 20/34] Fix CFG for select statements --- .../go/controlflow/ControlFlowGraphShared.qll | 122 ++++++++++++++++-- 1 file changed, 109 insertions(+), 13 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index bd8011c54ca8..d5a5fefbc0ae 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -375,6 +375,8 @@ module GoCfg { or n instanceof Go::GoStmt or + n instanceof Go::SelectStmt + or n instanceof Go::SendStmt or n instanceof Go::IncDecStmt @@ -1330,31 +1332,127 @@ module GoCfg { ) } + private predicate commClauseBodyStart( + Go::SelectStmt sel, Go::CommClause cc, PreControlFlowNode n + ) { + n.isBefore(cc.getStmt(0)) + or + not exists(cc.getStmt(0)) and n.isAfter(sel) + } + + private predicate selectCommPrepStart(Go::CommClause cc, PreControlFlowNode n) { + exists(Go::RecvStmt recv | recv = cc.getComm() | n.isBefore(recv.getExpr().getOperand())) + or + exists(Go::SendStmt send | send = cc.getComm() | n.isBefore(send.getChannel())) + } + + private predicate selectCommPrepEnd(Go::CommClause cc, PreControlFlowNode n) { + exists(Go::RecvStmt recv | recv = cc.getComm() | n.isAfter(recv.getExpr().getOperand())) + or + exists(Go::SendStmt send | send = cc.getComm() | n.isAfter(send.getValue())) + } + + private predicate selectCommPrepStep( + Go::CommClause cc, PreControlFlowNode n1, PreControlFlowNode n2 + ) { + exists(Go::SendStmt send | send = cc.getComm() | + n1.isAfter(send.getChannel()) and n2.isBefore(send.getValue()) + ) + } + + private predicate selectedCommStep( + Go::SelectStmt sel, PreControlFlowNode n1, PreControlFlowNode n2 + ) { + exists(Go::RecvStmt recv | recv = sel.getACommClause().getComm() | + n1.isBefore(recv) and n2.isIn(recv.getExpr()) + or + n1.isBefore(recv.getExpr()) and n2.isBefore(recv.getExpr().getOperand()) + ) + or + exists(Go::SendStmt send | send = sel.getACommClause().getComm() | + n1.isBefore(send) and n2.isBefore(send.getChannel()) + or + n1.isAfter(send.getChannel()) and n2.isBefore(send.getValue()) + or + n1.isIn(send) and n2.isAfter(send) + ) + } + + private predicate selectRecvStmtStep( + Go::SelectStmt sel, Go::CommClause cc, Go::RecvStmt recv, PreControlFlowNode n1, + PreControlFlowNode n2 + ) { + cc = sel.getACommClause() and + recv = cc.getComm() and + ( + n1.isIn(recv.getExpr()) and + ( + n2.isBefore(recv.getLhs(0)) + or + not exists(recv.getLhs(0)) and commClauseBodyStart(sel, cc, n2) + ) + or + exists(int j | n1.isAfter(recv.getLhs(j)) and n2.isBefore(recv.getLhs(j + 1))) + or + exists(int last | exists(recv.getLhs(last)) and not exists(recv.getLhs(last + 1)) | + n1.isAfter(recv.getLhs(last)) and n2.isAdditional(recv, getFirstEpilogueTag(recv)) + ) + or + exists(int last | exists(recv.getLhs(last)) and not exists(recv.getLhs(last + 1)) | + not exists(getFirstEpilogueTag(recv)) and + n1.isAfter(recv.getLhs(last)) and + commClauseBodyStart(sel, cc, n2) + ) + or + exists(string tag1, string tag2 | + epilogueSucc(recv, tag1, tag2) and + n1.isAdditional(recv, tag1) and + n2.isAdditional(recv, tag2) + ) + or + n1.isAdditional(recv, getLastEpilogueTag(recv)) and commClauseBodyStart(sel, cc, n2) + ) + } + private predicate selectStmt(PreControlFlowNode n1, PreControlFlowNode n2) { exists(Go::SelectStmt sel | + selectedCommStep(sel, n1, n2) + or n1.isBefore(sel) and ( - n2.isBefore(sel.getNonDefaultCommClause(0).getComm()) + selectCommPrepStart(sel.getNonDefaultCommClause(0), n2) or - not exists(sel.getACommClause()) and n2.isAfter(sel) + not exists(sel.getNonDefaultCommClause(0)) and n2.isIn(sel) ) or exists(Go::CommClause cc, int i | cc = sel.getNonDefaultCommClause(i) | - n1.isAfter(cc.getComm()) and + selectCommPrepStep(cc, n1, n2) + or + selectCommPrepEnd(cc, n1) and ( - n2.isBefore(sel.getNonDefaultCommClause(i + 1).getComm()) + selectCommPrepStart(sel.getNonDefaultCommClause(i + 1), n2) or - not exists(sel.getNonDefaultCommClause(i + 1)) and - ( - n2.isBefore(sel.getACommClause().getStmt(0)) - or - exists(sel.getDefaultCommClause()) and - n2.isBefore(sel.getDefaultCommClause().getStmt(0)) - ) + not exists(sel.getNonDefaultCommClause(i + 1)) and n2.isIn(sel) ) ) or + n1.isIn(sel) and + exists(Go::CommClause cc | sel.getACommClause() = cc | n2.isBefore(cc)) + or exists(Go::CommClause cc | sel.getACommClause() = cc | + n1.isBefore(cc) and + ( + n2.isIn(cc.getComm().(Go::RecvStmt).getExpr()) + or + n2.isIn(cc.getComm().(Go::SendStmt)) + or + not exists(cc.getComm()) and commClauseBodyStart(sel, cc, n2) + ) + or + exists(Go::RecvStmt recv | selectRecvStmtStep(sel, cc, recv, n1, n2)) + or + n1.isAfter(cc.getComm().(Go::SendStmt)) and commClauseBodyStart(sel, cc, n2) + or exists(int j | n1.isAfter(cc.getStmt(j)) and n2.isBefore(cc.getStmt(j + 1))) or exists(int last | @@ -1362,8 +1460,6 @@ module GoCfg { n1.isAfter(cc.getStmt(last)) and n2.isAfter(sel) ) - or - not exists(cc.getStmt(_)) and n1.isBefore(cc) and n2.isAfter(sel) ) ) } From 4f26b7c740679f39de547828902474d297252b7f Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 28 May 2026 16:27:11 +0100 Subject: [PATCH 21/34] Fix CFG for range loop --- .../semmle/go/controlflow/ControlFlowGraphShared.qll | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index d5a5fefbc0ae..d24588282660 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -1195,11 +1195,13 @@ module GoCfg { private predicate rangeLoop(PreControlFlowNode n1, PreControlFlowNode n2) { exists(Go::RangeStmt s | + // Use the shared library's auto-created `[LoopHeader]` additional node + // (created for every `LoopStmt`) as the join/branch point of the range loop. n1.isBefore(s) and n2.isBefore(s.getDomain()) or - n1.isAfter(s.getDomain()) and n2.isIn(s) + n1.isAfter(s.getDomain()) and n2.isAdditional(s, "[LoopHeader]") or - n1.isIn(s) and + n1.isAdditional(s, "[LoopHeader]") and ( n2.isBefore(s.getKey()) or @@ -1215,9 +1217,9 @@ module GoCfg { or n1.isAfter(s.getValue()) and n2.isBefore(s.getBody()) or - n1.isAfter(s.getBody()) and n2.isIn(s) + n1.isAfter(s.getBody()) and n2.isAdditional(s, "[LoopHeader]") or - n1.isIn(s) and n2.isAfter(s) + n1.isAdditional(s, "[LoopHeader]") and n2.isAfter(s) ) } From 22d0902495a60f55b37888020b9b02c74b423073 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 28 May 2026 17:18:19 +0100 Subject: [PATCH 22/34] Include receivers in parameter init --- .../go/controlflow/ControlFlowGraphShared.qll | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index d24588282660..cec1b3f1e5c1 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -1488,8 +1488,9 @@ module GoCfg { /** * Function definition prologue and epilogue: - * - Prologue: Before(fd) → arg:0 → param-init:0 → arg:1 → param-init:1 → ... - * → result-zero-init:0 → result-init:0 → ... → Before(body) + * - Prologue: Before(fd) → arg:-1 → param-init:-1 → arg:0 → param-init:0 → ... + * when a receiver exists; otherwise it starts at arg:0. Then + * result-zero-init:0 → result-init:0 → ... → Before(body) * - Epilogue: After(body) → result-read:0 → result-read:1 → ... → After(fd) * * `After(fd)` goes to `NormalExit(fd)` via the shared library's built-in step @@ -1500,8 +1501,13 @@ module GoCfg { // Before(fd) → first prologue node, or Before(body) if no prologue n1.isBefore(fd) and ( - // Has parameters: start with arg:0 - exists(fd.getParameter(0)) and n2.isAdditional(fd, "arg:0") + // Has receiver: start with arg:-1 + exists(fd.getParameter(-1)) and n2.isAdditional(fd, "arg:-1") + or + // Has ordinary parameters: start with arg:0 + not exists(fd.getParameter(-1)) and + exists(fd.getParameter(0)) and + n2.isAdditional(fd, "arg:0") or // No parameters, has result vars: start with result-zero-init:0 not exists(fd.getParameter(_)) and From b1f9ccd86096f9cca81b5a134343c1c6e577fc3a Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 28 May 2026 17:55:09 +0100 Subject: [PATCH 23/34] Fix global value numbering calculation --- go/ql/lib/semmle/go/dataflow/GlobalValueNumbering.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/ql/lib/semmle/go/dataflow/GlobalValueNumbering.qll b/go/ql/lib/semmle/go/dataflow/GlobalValueNumbering.qll index 3547e70b858a..541d52e6da35 100644 --- a/go/ql/lib/semmle/go/dataflow/GlobalValueNumbering.qll +++ b/go/ql/lib/semmle/go/dataflow/GlobalValueNumbering.qll @@ -200,7 +200,7 @@ private ControlFlow::Node mostRecentSideEffect(ControlFlow::Node entry, ControlF cached private ControlFlow::Node mostRecentSideEffectUnique(ControlFlow::Node node) { - result = unique( | | mostRecentSideEffect(_, node)) + result = unique( | | mostRecentSideEffect(getControlFlowEntry(node), node)) } /** Used to represent the "global value number" of an expression. */ From 4d90354b8a47d27025684f43d2453f7c8538f09a Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 29 May 2026 12:49:41 +0100 Subject: [PATCH 24/34] Produce CFG nodes for more reference expressions, like selector bases --- .../lib/semmle/go/controlflow/ControlFlowGraphShared.qll | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index cec1b3f1e5c1..b5c95dc93e86 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -52,7 +52,13 @@ module GoCfg { or e instanceof Go::SelectorExpr and not e instanceof Go::ReferenceExpr or - e instanceof Go::ReferenceExpr and not e.(Go::ReferenceExpr).isRvalue() + e instanceof Go::ReferenceExpr and + not e.(Go::ReferenceExpr).isRvalue() and + not e instanceof Go::SelectorExpr and + not e = any(Go::SelectorExpr sel).getBase() and + not e instanceof Go::IndexExpr and + not e = any(Go::IndexExpr idx).getBase() and + not e = any(Go::IndexExpr idx).getIndex() or e instanceof Go::ParenExpr or From e8f38b2870965bacfff3d5339e5e5b410d40b960 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 29 May 2026 13:34:22 +0100 Subject: [PATCH 25/34] Fix CFG for return instructions --- go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll | 5 +---- go/ql/lib/semmle/go/controlflow/IR.qll | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index b5c95dc93e86..a5c98645dff7 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -432,9 +432,6 @@ module GoCfg { or n instanceof Go::IncDecStmt and tag = "incdec-rhs" or - // Return node - n instanceof Go::ReturnStmt and tag = "return" - or // Result write nodes in return statements exists(int i, Go::ReturnStmt ret | n = ret and @@ -950,7 +947,7 @@ module GoCfg { n2.isAdditional(ret, tag2) ) or - // Last return epilogue → In(ret) (the return itself) + // Last return epilogue → return node n1.isAdditional(ret, getLastReturnEpilogueTag(ret)) and n2.isIn(ret) ) diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll b/go/ql/lib/semmle/go/controlflow/IR.qll index 22300f3688ba..86df852f7a5b 100644 --- a/go/ql/lib/semmle/go/controlflow/IR.qll +++ b/go/ql/lib/semmle/go/controlflow/IR.qll @@ -893,7 +893,7 @@ module IR { class ReturnInstruction extends Instruction { ReturnStmt ret; - ReturnInstruction() { this.isAdditional(ret, "return") } + ReturnInstruction() { this.isIn(ret) } /** Gets the corresponding `ReturnStmt`. */ ReturnStmt getReturnStmt() { result = ret } @@ -938,9 +938,7 @@ module IR { exists(retStmt.getAnExpr()) } - private ReturnInstruction getReturnInstruction() { - result.(ReturnInstruction).isAdditional(retStmt, "return") - } + private ReturnInstruction getReturnInstruction() { result.getReturnStmt() = retStmt } override Instruction getRhs() { result = this.getReturnInstruction().getResult(idx) } From 939d31feb282aaada68503988b170324ee809aa0 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 29 May 2026 15:42:03 +0100 Subject: [PATCH 26/34] Control flow shouldn't enter another callable --- .../go/controlflow/ControlFlowGraphShared.qll | 113 ++++++++++-------- 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index a5c98645dff7..72d7da2e8513 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -72,6 +72,7 @@ module GoCfg { } AstNode getChild(AstNode n, int index) { + not n instanceof Go::FuncDef and not skipCfg(n) and not skipCfg(result) and exists(Go::AstNode c | c = n.getChild(index) | @@ -87,7 +88,11 @@ module GoCfg { } } - AstNode callableGetBody(Callable c) { result = c } + AstNode callableGetBody(Callable c) { + result = c.(Go::FuncDef).getBody() + or + result = c.(Go::File) + } class Parameter extends AstNode { Parameter() { none() } @@ -98,12 +103,8 @@ module GoCfg { Parameter callableGetParameter(Callable c, int index) { none() } Callable getEnclosingCallable(AstNode node) { - node instanceof Go::FuncDef and result = node - or - not node instanceof Go::FuncDef and result = node.getEnclosingFunction() or - not node instanceof Go::FuncDef and not exists(node.getEnclosingFunction()) and result = node.getFile() } @@ -440,33 +441,33 @@ module GoCfg { tag = "result-write:" + i.toString() ) or - // Result read nodes (on the FuncDef) + // Result read nodes (on the function body) exists(int i, Go::FuncDef fd | - n = fd and + n = fd.getBody() and exists(fd.getBody()) and exists(fd.getResultVar(i)) and tag = "result-read:" + i.toString() ) or - // Parameter init + argument nodes (on the FuncDef) + // Parameter init + argument nodes (on the function body) exists(int i, Go::FuncDef fd | - n = fd and + n = fd.getBody() and exists(fd.getBody()) and exists(fd.getParameter(i)) and (tag = "param-init:" + i.toString() or tag = "arg:" + i.toString()) ) or - // Result variable init (on the FuncDef) + // Result variable init (on the function body) exists(int i, Go::FuncDef fd | - n = fd and + n = fd.getBody() and exists(fd.getBody()) and exists(fd.getResultVar(i)) and tag = "result-init:" + i.toString() ) or - // Result variable zero init (on the FuncDef) + // Result variable zero init (on the function body) exists(int i, Go::FuncDef fd | - n = fd and + n = fd.getBody() and exists(fd.getBody()) and exists(fd.getResultVar(i)) and exists(fd.getResultVar(i).(Go::ResultVariable).getFunction().getBody()) and @@ -715,7 +716,7 @@ module GoCfg { ( // If the function has result variables, route the return completion // through the result-read epilogue before reaching the function exit. - exists(fd.getResultVar(0)) and n.isAdditional(fd, "result-read:0") + exists(fd.getResultVar(0)) and n.isAdditional(fd.getBody(), "result-read:0") or not exists(fd.getResultVar(_)) and n.isAfter(fd.getBody()) ) @@ -1491,100 +1492,118 @@ module GoCfg { /** * Function definition prologue and epilogue: - * - Prologue: Before(fd) → arg:-1 → param-init:-1 → arg:0 → param-init:0 → ... + * - Prologue: Before(body) → arg:-1 → param-init:-1 → arg:0 → param-init:0 → ... * when a receiver exists; otherwise it starts at arg:0. Then - * result-zero-init:0 → result-init:0 → ... → Before(body) - * - Epilogue: After(body) → result-read:0 → result-read:1 → ... → After(fd) + * result-zero-init:0 → result-init:0 → ... → first statement + * - Epilogue: return → result-read:0 → result-read:1 → ... → After(body) * - * `After(fd)` goes to `NormalExit(fd)` via the shared library's built-in step - * (since `callableGetBody(fd) = fd`). + * `After(body)` goes to `NormalExit(fd)` via the shared library's built-in step. */ + private predicate hasFuncDefPrologue(Go::FuncDef fd) { + exists(fd.getParameter(_)) or exists(fd.getResultVar(_)) + } + + private predicate funcDefBodyStart(Go::FuncDef fd, PreControlFlowNode n) { + n.isBefore(getRankedChild(fd.getBody(), 1)) + or + not exists(getRankedChild(fd.getBody(), _)) and n.isAfter(fd.getBody()) + } + + private predicate funcDefBodyStep(Go::FuncDef fd, PreControlFlowNode n1, PreControlFlowNode n2) { + not hasFuncDefPrologue(fd) and + n1.isBefore(fd.getBody()) and + funcDefBodyStart(fd, n2) + or + exists(int i | + n1.isAfter(getRankedChild(fd.getBody(), i)) and + n2.isBefore(getRankedChild(fd.getBody(), i + 1)) + ) + or + exists(Ast::AstNode lastChild | lastChild = getLastRankedChild(fd.getBody()) | + n1.isAfter(lastChild) and n2.isAfter(fd.getBody()) + ) + } + private predicate funcDefStep(PreControlFlowNode n1, PreControlFlowNode n2) { exists(Go::FuncDef fd | exists(fd.getBody()) | - // Before(fd) → first prologue node, or Before(body) if no prologue - n1.isBefore(fd) and + // Before(body) → first prologue node, or first body statement if no prologue + n1.isBefore(fd.getBody()) and ( // Has receiver: start with arg:-1 - exists(fd.getParameter(-1)) and n2.isAdditional(fd, "arg:-1") + exists(fd.getParameter(-1)) and n2.isAdditional(fd.getBody(), "arg:-1") or // Has ordinary parameters: start with arg:0 not exists(fd.getParameter(-1)) and exists(fd.getParameter(0)) and - n2.isAdditional(fd, "arg:0") + n2.isAdditional(fd.getBody(), "arg:0") or // No parameters, has result vars: start with result-zero-init:0 not exists(fd.getParameter(_)) and exists(fd.getResultVar(0)) and - n2.isAdditional(fd, "result-zero-init:0") + n2.isAdditional(fd.getBody(), "result-zero-init:0") or // No parameters and no result vars: go directly to Before(body) not exists(fd.getParameter(_)) and not exists(fd.getResultVar(_)) and - n2.isBefore(fd.getBody()) + funcDefBodyStart(fd, n2) ) or // arg:i → param-init:i (for each parameter) exists(int i | exists(fd.getParameter(i)) | - n1.isAdditional(fd, "arg:" + i.toString()) and - n2.isAdditional(fd, "param-init:" + i.toString()) + n1.isAdditional(fd.getBody(), "arg:" + i.toString()) and + n2.isAdditional(fd.getBody(), "param-init:" + i.toString()) ) or // param-init:i → next: arg:(i+1), or result-zero-init:0, or Before(body) exists(int i | exists(fd.getParameter(i)) | - n1.isAdditional(fd, "param-init:" + i.toString()) and + n1.isAdditional(fd.getBody(), "param-init:" + i.toString()) and ( // Next parameter exists exists(fd.getParameter(i + 1)) and - n2.isAdditional(fd, "arg:" + (i + 1).toString()) + n2.isAdditional(fd.getBody(), "arg:" + (i + 1).toString()) or // No next parameter, has result vars: go to result-zero-init:0 not exists(fd.getParameter(i + 1)) and exists(fd.getResultVar(0)) and - n2.isAdditional(fd, "result-zero-init:0") + n2.isAdditional(fd.getBody(), "result-zero-init:0") or // No next parameter and no result vars: go to Before(body) not exists(fd.getParameter(i + 1)) and not exists(fd.getResultVar(_)) and - n2.isBefore(fd.getBody()) + funcDefBodyStart(fd, n2) ) ) or // result-zero-init:j → result-init:j (for each result variable) exists(int j | exists(fd.getResultVar(j)) | - n1.isAdditional(fd, "result-zero-init:" + j.toString()) and - n2.isAdditional(fd, "result-init:" + j.toString()) + n1.isAdditional(fd.getBody(), "result-zero-init:" + j.toString()) and + n2.isAdditional(fd.getBody(), "result-init:" + j.toString()) ) or // result-init:j → next: result-zero-init:(j+1), or Before(body) exists(int j | exists(fd.getResultVar(j)) | - n1.isAdditional(fd, "result-init:" + j.toString()) and + n1.isAdditional(fd.getBody(), "result-init:" + j.toString()) and ( // Next result var exists exists(fd.getResultVar(j + 1)) and - n2.isAdditional(fd, "result-zero-init:" + (j + 1).toString()) + n2.isAdditional(fd.getBody(), "result-zero-init:" + (j + 1).toString()) or // No next result var: go to Before(body) not exists(fd.getResultVar(j + 1)) and - n2.isBefore(fd.getBody()) + funcDefBodyStart(fd, n2) ) ) or - // After(body) → After(fd). Only reachable when there are no result - // variables; with result variables, Go requires the body to end in a - // terminating statement, and the result-read epilogue is entered from - // the return completion (see endAbruptCompletion). - n1.isAfter(fd.getBody()) and - not exists(fd.getResultVar(_)) and - n2.isAfter(fd) + funcDefBodyStep(fd, n1, n2) or - // result-read:j → result-read:(j+1) or After(fd) + // result-read:j → result-read:(j+1) or After(body) exists(int j | exists(fd.getResultVar(j)) | - n1.isAdditional(fd, "result-read:" + j.toString()) and + n1.isAdditional(fd.getBody(), "result-read:" + j.toString()) and ( exists(fd.getResultVar(j + 1)) and - n2.isAdditional(fd, "result-read:" + (j + 1).toString()) + n2.isAdditional(fd.getBody(), "result-read:" + (j + 1).toString()) or - not exists(fd.getResultVar(j + 1)) and n2.isAfter(fd) + not exists(fd.getResultVar(j + 1)) and n2.isAfter(fd.getBody()) ) ) ) From 879429889357374071de96576984c4f01529be6d Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 29 May 2026 15:57:08 +0100 Subject: [PATCH 27/34] Fix empty switch statements --- .../go/controlflow/ControlFlowGraphShared.qll | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 72d7da2e8513..992cef0ffeb5 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -1231,6 +1231,14 @@ module GoCfg { exprSwitch(n1, n2) or typeSwitch(n1, n2) or caseClause(n1, n2) } + private predicate switchCasesStartOrAfter(Go::SwitchStmt sw, PreControlFlowNode n) { + n.isBefore(sw.getNonDefaultCase(0)) + or + not exists(sw.getANonDefaultCase()) and n.isBefore(sw.getDefault()) + or + not exists(sw.getACase()) and n.isAfter(sw) + } + private predicate exprSwitch(PreControlFlowNode n1, PreControlFlowNode n2) { exists(Go::ExpressionSwitchStmt sw | n1.isBefore(sw) and @@ -1241,14 +1249,7 @@ module GoCfg { ( n2.isBefore(sw.getExpr()) or - not exists(sw.getExpr()) and - ( - n2.isBefore(sw.getNonDefaultCase(0)) - or - not exists(sw.getANonDefaultCase()) and n2.isBefore(sw.getDefault()) - or - not exists(sw.getACase()) and n2.isAfter(sw) - ) + not exists(sw.getExpr()) and switchCasesStartOrAfter(sw, n2) ) ) or @@ -1256,20 +1257,10 @@ module GoCfg { ( n2.isBefore(sw.getExpr()) or - not exists(sw.getExpr()) and - ( - n2.isBefore(sw.getNonDefaultCase(0)) - or - not exists(sw.getANonDefaultCase()) and n2.isBefore(sw.getDefault()) - ) + not exists(sw.getExpr()) and switchCasesStartOrAfter(sw, n2) ) or - n1.isAfter(sw.getExpr()) and - ( - n2.isBefore(sw.getNonDefaultCase(0)) - or - not exists(sw.getANonDefaultCase()) and n2.isBefore(sw.getDefault()) - ) + n1.isAfter(sw.getExpr()) and switchCasesStartOrAfter(sw, n2) ) } @@ -1284,12 +1275,7 @@ module GoCfg { or n1.isAfter(sw.getInit()) and n2.isBefore(sw.getTest()) or - n1.isAfter(sw.getTest()) and - ( - n2.isBefore(sw.getNonDefaultCase(0)) - or - not exists(sw.getANonDefaultCase()) and n2.isBefore(sw.getDefault()) - ) + n1.isAfter(sw.getTest()) and switchCasesStartOrAfter(sw, n2) ) } From a42782a1cb4e8d651399dba4826a08b34b46458a Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 29 May 2026 16:14:22 +0100 Subject: [PATCH 28/34] Accept change in test output To determine that test9 can't return normally, you have to use the fact that test5 can't return normally. This would make CFG construction recursive, which would be bad for performance. Therefore we accept the limitation that we cannot detect that test9 can't return normally, and we change the test output. --- .../go/controlflow/ControlFlowGraph/NoretFunctions.expected | 1 - 1 file changed, 1 deletion(-) diff --git a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/NoretFunctions.expected b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/NoretFunctions.expected index abd09c529763..07de8e5aaeee 100644 --- a/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/NoretFunctions.expected +++ b/go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/NoretFunctions.expected @@ -8,4 +8,3 @@ | stmts7.go:10:6:10:15 | canRecover | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph | | stmts.go:10:6:10:10 | test5 | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph | | stmts.go:46:6:46:10 | test6 | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph | -| stmts.go:112:6:112:10 | test9 | package github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph | From 5bb8f2ce20ecb9c3d3301caa7cdb7203f23c3353 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 29 May 2026 23:32:41 +0100 Subject: [PATCH 29/34] Restore `ExprNode` for `FuncLit` --- go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index 992cef0ffeb5..c84efa44e2d3 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -358,6 +358,8 @@ module GoCfg { or n instanceof Go::BasicLit or + n instanceof Go::FuncLit + or n instanceof Go::CallExpr and not n = any(Go::DeferStmt defer).getCall() and not n = any(Go::GoStmt go_).getCall() From 26fa079c8d240a1804ed3b57ccf3ad705ebc3528 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 30 May 2026 06:30:38 +0100 Subject: [PATCH 30/34] update function-entry additional nodes --- go/ql/lib/semmle/go/controlflow/IR.qll | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll b/go/ql/lib/semmle/go/controlflow/IR.qll index 86df852f7a5b..91d73fe901f7 100644 --- a/go/ql/lib/semmle/go/controlflow/IR.qll +++ b/go/ql/lib/semmle/go/controlflow/IR.qll @@ -960,7 +960,7 @@ module IR { FuncDef fd; ReadResultInstruction() { - this.isAdditional(fd, "result-read:" + idx.toString()) and + this.isAdditional(fd.getBody(), "result-read:" + idx.toString()) and var = fd.getResultVar(idx) } @@ -987,12 +987,12 @@ module IR { FuncDef fd; InitParameterInstruction() { - this.isAdditional(fd, "param-init:" + idx.toString()) and + this.isAdditional(fd.getBody(), "param-init:" + idx.toString()) and parm = fd.getParameter(idx) } override Instruction getRhs() { - result.(ReadArgumentInstruction).isAdditional(fd, "arg:" + idx.toString()) + result.(ReadArgumentInstruction).isAdditional(fd.getBody(), "arg:" + idx.toString()) } override ControlFlow::Root getRoot() { result = parm.getFunction() } @@ -1005,7 +1005,7 @@ module IR { FuncDef fd; ReadArgumentInstruction() { - this.isAdditional(fd, "arg:" + idx.toString()) and + this.isAdditional(fd.getBody(), "arg:" + idx.toString()) and parm = fd.getParameter(idx) } @@ -1021,12 +1021,14 @@ module IR { FuncDef fd; InitResultInstruction() { - this.isAdditional(fd, "result-init:" + idx.toString()) and + this.isAdditional(fd.getBody(), "result-init:" + idx.toString()) and res = fd.getResultVar(idx) } override Instruction getRhs() { - result.(ResultZeroInitInstruction).isAdditional(fd, "result-zero-init:" + idx.toString()) + result + .(ResultZeroInitInstruction) + .isAdditional(fd.getBody(), "result-zero-init:" + idx.toString()) } override ControlFlow::Root getRoot() { result = res.getFunction() } @@ -1038,7 +1040,7 @@ module IR { FuncDef fd; ResultZeroInitInstruction() { - this.isAdditional(fd, "result-zero-init:" + idx.toString()) and + this.isAdditional(fd.getBody(), "result-zero-init:" + idx.toString()) and res = fd.getResultVar(idx) } @@ -1214,12 +1216,12 @@ module IR { exists(IncDecStmt ids | write.isIn(ids) | lhs = ids.getOperand().stripParens()) or exists(FuncDef fd, int idx | - write.isAdditional(fd, "param-init:" + idx.toString()) and + write.isAdditional(fd.getBody(), "param-init:" + idx.toString()) and lhs = fd.getParameter(idx).getDeclaration() ) or exists(FuncDef fd, int idx | - write.isAdditional(fd, "result-init:" + idx.toString()) and + write.isAdditional(fd.getBody(), "result-init:" + idx.toString()) and lhs = fd.getResultVar(idx).getDeclaration() ) } or @@ -1411,7 +1413,7 @@ module IR { */ InitParameterInstruction initRecvInstruction(ReceiverVariable r) { exists(FuncDef fd, int i | - fd.getParameter(i) = r and result.isAdditional(fd, "param-init:" + i.toString()) + fd.getParameter(i) = r and result.isAdditional(fd.getBody(), "param-init:" + i.toString()) ) } @@ -1420,7 +1422,7 @@ module IR { */ InitParameterInstruction initParamInstruction(Parameter p) { exists(FuncDef fd, int i | - fd.getParameter(i) = p and result.isAdditional(fd, "param-init:" + i.toString()) + fd.getParameter(i) = p and result.isAdditional(fd.getBody(), "param-init:" + i.toString()) ) } From ec7769171839aa10e28ae250874eaf923e5a737a Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 30 May 2026 06:47:59 +0100 Subject: [PATCH 31/34] Fix range loop CFG --- .../go/controlflow/ControlFlowGraphShared.qll | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index c84efa44e2d3..b9e7d099d3c2 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -859,6 +859,10 @@ module GoCfg { notBlankIdent(assgn.(Go::Assignment).getLhs(j)) or notBlankIdent(assgn.(Go::ValueSpec).getNameExpr(j)) + or + notBlankIdent(assgn.(Go::RangeStmt).getKey()) and j = 0 + or + notBlankIdent(assgn.(Go::RangeStmt).getValue()) and j = 1 ) and result = "assign:" + j.toString() and ord = 2 * j + 1 @@ -1207,21 +1211,22 @@ module GoCfg { or n1.isAfter(s.getDomain()) and n2.isAdditional(s, "[LoopHeader]") or - n1.isAdditional(s, "[LoopHeader]") and + n1.isAdditional(s, "[LoopHeader]") and n2.isAdditional(s, "next") + or + n1.isAdditional(s, "next") and ( - n2.isBefore(s.getKey()) + exists(getFirstEpilogueTag(s)) and n2.isAdditional(s, getFirstEpilogueTag(s)) or - not exists(s.getKey()) and n2.isBefore(s.getBody()) + not exists(getFirstEpilogueTag(s)) and n2.isBefore(s.getBody()) ) or - n1.isAfter(s.getKey()) and - ( - n2.isBefore(s.getValue()) - or - not exists(s.getValue()) and n2.isBefore(s.getBody()) + exists(string tag1, string tag2 | + epilogueSucc(s, tag1, tag2) and + n1.isAdditional(s, tag1) and + n2.isAdditional(s, tag2) ) or - n1.isAfter(s.getValue()) and n2.isBefore(s.getBody()) + n1.isAdditional(s, getLastEpilogueTag(s)) and n2.isBefore(s.getBody()) or n1.isAfter(s.getBody()) and n2.isAdditional(s, "[LoopHeader]") or From e68a77db63b26d43284581fe5230af958adc2958 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 30 May 2026 07:07:50 +0100 Subject: [PATCH 32/34] Fix lit-init nodes There was a loop in the definitions which meant nothing ever got made --- .../go/controlflow/ControlFlowGraphShared.qll | 13 +++++++++++-- go/ql/lib/semmle/go/controlflow/IR.qll | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index b9e7d099d3c2..f2da7c52f01e 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -1153,9 +1153,18 @@ module GoCfg { not exists(lit.getElement(_)) and n2.isAfter(lit) ) or - // After element → lit-init → next element or After + // After element → optional lit-index → lit-init → next element or After exists(int i | - n1.isAfter(lit.getElement(i)) and n2.isAdditional(lit.getElement(i), "lit-init") + n1.isAfter(lit.getElement(i)) and + ( + n2.isAdditional(lit.getElement(i), "lit-index") + or + not exists(PreControlFlowNode idx | idx.isAdditional(lit.getElement(i), "lit-index")) and + n2.isAdditional(lit.getElement(i), "lit-init") + ) + or + n1.isAdditional(lit.getElement(i), "lit-index") and + n2.isAdditional(lit.getElement(i), "lit-init") or n1.isAdditional(lit.getElement(i), "lit-init") and ( diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll b/go/ql/lib/semmle/go/controlflow/IR.qll index 91d73fe901f7..651884757d02 100644 --- a/go/ql/lib/semmle/go/controlflow/IR.qll +++ b/go/ql/lib/semmle/go/controlflow/IR.qll @@ -1226,7 +1226,9 @@ module IR { ) } or /** A composite literal element target. */ - MkLiteralElementTarget(InitLiteralComponentInstruction elt) or + MkLiteralElementTarget(ControlFlow::Node write) { + write.isAdditional(any(CompositeLit lit).getAnElement(), "lit-init") + } or /** A result variable write target. */ MkResultWriteTarget(WriteResultInstruction w) From 6befac24127b3d98515a152887d19729d1df878a Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 30 May 2026 07:27:56 +0100 Subject: [PATCH 33/34] Shared CFG: allow init stmts for IfStmt --- .../codeql/controlflow/ControlFlowGraph.qll | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll index 964e7e2cd2fa..5cd896056093 100644 --- a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll +++ b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll @@ -95,6 +95,9 @@ signature module AstSig { Stmt getElse(); } + /** Gets the initializer of this `if` statement, if any. */ + default AstNode getIfInit(IfStmt ifstmt) { none() } + /** * A loop statement. Loop statements are further subclassed into specific * types of loops. @@ -1529,6 +1532,13 @@ module Make0 Ast> { or exists(IfStmt ifstmt | n1.isBefore(ifstmt) and + ( + n2.isBefore(getIfInit(ifstmt)) + or + not exists(getIfInit(ifstmt)) and n2.isBefore(ifstmt.getCondition()) + ) + or + n1.isAfter(getIfInit(ifstmt)) and n2.isBefore(ifstmt.getCondition()) or n1.isAfterTrue(ifstmt.getCondition()) and From 61498bc1c3a83482f48226d528064a08e8731ee0 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 30 May 2026 07:28:28 +0100 Subject: [PATCH 34/34] Use shared CFG `getIfInit` --- .../go/controlflow/ControlFlowGraphShared.qll | 29 ++----------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll index f2da7c52f01e..77a72093077d 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll @@ -128,9 +128,8 @@ module GoCfg { class ExprStmt = Go::ExprStmt; - /** If statements without init (those with init use custom steps). */ class IfStmt extends Stmt { - IfStmt() { this instanceof Go::IfStmt and not exists(this.(Go::IfStmt).getInit()) } + IfStmt() { this instanceof Go::IfStmt } Expr getCondition() { result = this.(Go::IfStmt).getCond() } @@ -139,6 +138,8 @@ module GoCfg { Stmt getElse() { result = this.(Go::IfStmt).getElse() } } + AstNode getIfInit(IfStmt ifstmt) { result = ifstmt.(Go::IfStmt).getInit() } + class LoopStmt extends Stmt { LoopStmt() { this instanceof Go::LoopStmt } @@ -341,8 +342,6 @@ module GoCfg { predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) { kind.isBoolean() and ( - exists(Go::IfStmt ifstmt | ifstmt.getCond() = n and exists(ifstmt.getInit())) - or n = any(Go::ForStmt fs).getCond() or exists(Go::ExpressionSwitchStmt ess | @@ -734,7 +733,6 @@ module GoCfg { } predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { - ifWithInit(n1, n2) or rangeLoop(n1, n2) or switchStmt(n1, n2) or selectStmt(n1, n2) or @@ -1191,27 +1189,6 @@ module GoCfg { ) } - private predicate ifWithInit(PreControlFlowNode n1, PreControlFlowNode n2) { - exists(Go::IfStmt s | exists(s.getInit()) | - n1.isBefore(s) and n2.isBefore(s.getInit()) - or - n1.isAfter(s.getInit()) and n2.isBefore(s.getCond()) - or - n1.isAfterTrue(s.getCond()) and n2.isBefore(s.getThen()) - or - n1.isAfterFalse(s.getCond()) and - ( - n2.isBefore(s.getElse()) - or - not exists(s.getElse()) and n2.isAfter(s) - ) - or - n1.isAfter(s.getThen()) and n2.isAfter(s) - or - n1.isAfter(s.getElse()) and n2.isAfter(s) - ) - } - private predicate rangeLoop(PreControlFlowNode n1, PreControlFlowNode n2) { exists(Go::RangeStmt s | // Use the shared library's auto-created `[LoopHeader]` additional node