diff --git a/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll index e1e74100ca84..09f65034f6d6 100644 --- a/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -25,7 +25,7 @@ private module Ast implements AstSig { class AstNode = ControlFlowElementOrCallable; - private predicate skipControlFlow(AstNode e) { + additional predicate skipControlFlow(AstNode e) { e instanceof TypeAccess and not e instanceof TypeAccessPatternExpr or @@ -82,13 +82,7 @@ private module Ast implements AstSig { AstNode callableGetBody(Callable c) { not skipControlFlow(result) and - ( - result = c.getBody() or - result = c.(Constructor).getObjectInitializerCall() or - result = c.(Constructor).getInitializer() or - c.(ObjectInitMethod).initializes(result) or - Initializers::staticMemberInitializer(c, result) - ) + result = c.getBody() } class Stmt = CS::Stmt; @@ -222,10 +216,21 @@ private module Ast implements AstSig { * Unlike the standard `Compilation` class, this class also supports buildless * extraction. */ -private newtype CompilationExt = +private newtype TCompilationExt = TCompilation(Compilation c) { not extractionIsStandalone() } or TBuildless() { extractionIsStandalone() } +private class CompilationExt extends TCompilationExt { + string toString() { + exists(Compilation c | + this = TCompilation(c) and + result = c.toString() + ) + or + this = TBuildless() and result = "buildless compilation" + } +} + /** Gets the compilation that source file `f` belongs to. */ private CompilationExt getCompilation(File f) { exists(Compilation c | @@ -286,32 +291,18 @@ private module Initializers { } /** - * Gets the `i`th member initializer expression for object initializer method `obinit` - * in compilation `comp`. + * Gets the `i`th member initializer expression for object initializer method `obinit`. */ - AssignExpr initializedInstanceMemberOrder(ObjectInitMethod obinit, CompilationExt comp, int i) { - obinit.initializes(result) and + AssignExpr initializedInstanceMemberOrder(ObjectInitMethod obinit, int i) { result = rank[i + 1](AssignExpr ae0, Location l, string filepath, int startline, int startcolumn | obinit.initializes(ae0) and l = ae0.getLocation() and - l.hasLocationInfo(filepath, startline, startcolumn, _, _) and - getCompilation(l.getFile()) = comp + l.hasLocationInfo(filepath, startline, startcolumn, _, _) | ae0 order by startline, startcolumn, filepath ) } - - /** - * Gets the last member initializer expression for object initializer method `obinit` - * in compilation `comp`. - */ - AssignExpr lastInitializer(ObjectInitMethod obinit, CompilationExt comp) { - exists(int i | - result = initializedInstanceMemberOrder(obinit, comp, i) and - not exists(initializedInstanceMemberOrder(obinit, comp, i + 1)) - ) - } } private module Exceptions { @@ -424,6 +415,31 @@ private module Input implements InputSig1, InputSig2 { l = TLblGoto(n.(LabelStmt).getLabel()) } + class CallableBodyPartContext = CompilationExt; + + pragma[nomagic] + Ast::AstNode callableGetBodyPart(Callable c, CallableBodyPartContext ctx, int index) { + not Ast::skipControlFlow(result) and + ctx = getCompilation(result.getFile()) and + ( + result = Initializers::initializedInstanceMemberOrder(c, index) + or + result = Initializers::initializedStaticMemberOrder(c, index) + or + exists(Constructor ctor, int i, int staticMembers | + c = ctor and + staticMembers = count(Expr init | Initializers::staticMemberInitializer(ctor, init)) and + index = staticMembers + i + 1 + | + i = 0 and result = ctor.getObjectInitializerCall() + or + i = 1 and result = ctor.getInitializer() + or + i = 2 and result = ctor.getBody() + ) + ) + } + private Expr getQualifier(QualifiableExpr qe) { result = qe.getQualifier() or result = qe.(ExtensionMethodCall).getArgument(0) @@ -474,80 +490,7 @@ private module Input implements InputSig1, InputSig2 { ) } - pragma[noinline] - private MethodCall getObjectInitializerCall(Constructor ctor, CompilationExt comp) { - result = ctor.getObjectInitializerCall() and - comp = getCompilation(result.getFile()) - } - - pragma[noinline] - private ConstructorInitializer getInitializer(Constructor ctor, CompilationExt comp) { - result = ctor.getInitializer() and - comp = getCompilation(result.getFile()) - } - - pragma[noinline] - private Ast::AstNode getBody(Constructor ctor, CompilationExt comp) { - result = ctor.getBody() and - comp = getCompilation(result.getFile()) - } - predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { - exists(Constructor ctor | - n1.(EntryNodeImpl).getEnclosingCallable() = ctor and - if Initializers::staticMemberInitializer(ctor, _) - then n2.isBefore(Initializers::initializedStaticMemberOrder(ctor, 0)) - else - if exists(ctor.getObjectInitializerCall()) - then n2.isBefore(ctor.getObjectInitializerCall()) - else - if exists(ctor.getInitializer()) - then n2.isBefore(ctor.getInitializer()) - else n2.isBefore(ctor.getBody()) - or - exists(int i | n1.isAfter(Initializers::initializedStaticMemberOrder(ctor, i)) | - n2.isBefore(Initializers::initializedStaticMemberOrder(ctor, i + 1)) - or - not exists(Initializers::initializedStaticMemberOrder(ctor, i + 1)) and - n2.isBefore(ctor.getBody()) - ) - or - exists(CompilationExt comp | - n1.isAfter(getObjectInitializerCall(ctor, comp)) and - if exists(getInitializer(ctor, comp)) - then n2.isBefore(getInitializer(ctor, comp)) - else - // This is only relevant in the context of compilation errors, since - // normally the existence of an object initializer call implies the - // existence of an initializer. - if exists(getBody(ctor, comp)) - then n2.isBefore(getBody(ctor, comp)) - else n2.(NormalExitNodeImpl).getEnclosingCallable() = ctor - or - n1.isAfter(getInitializer(ctor, comp)) and - if exists(getBody(ctor, comp)) - then n2.isBefore(getBody(ctor, comp)) - else n2.(NormalExitNodeImpl).getEnclosingCallable() = ctor - ) - or - n1.isAfter(ctor.getBody()) and - n2.(NormalExitNodeImpl).getEnclosingCallable() = ctor - ) - or - exists(ObjectInitMethod obinit | - n1.(EntryNodeImpl).getEnclosingCallable() = obinit and - n2.isBefore(Initializers::initializedInstanceMemberOrder(obinit, _, 0)) - or - exists(CompilationExt comp, int i | - // Flow from one member initializer to the next - n1.isAfter(Initializers::initializedInstanceMemberOrder(obinit, comp, i)) and - n2.isBefore(Initializers::initializedInstanceMemberOrder(obinit, comp, i + 1)) - ) - or - n1.isAfter(Initializers::lastInitializer(obinit, _)) and - n2.(NormalExitNodeImpl).getEnclosingCallable() = obinit - ) - or exists(QualifiableExpr qe | qe.isConditional() | n1.isBefore(qe) and n2.isBefore(getQualifier(qe)) or diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 72353de39fcc..1137b46f32c2 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -468,6 +468,7 @@ private module NonReturningCalls { private module Input implements InputSig1, InputSig2 { private import java as J + private import codeql.util.Void predicate cfgCachedStageRef() { CfgCachedStage::ref() } @@ -533,6 +534,8 @@ private module Input implements InputSig1, InputSig2 { l = TYield() and n instanceof SwitchExpr } + class CallableBodyPartContext = Void; + predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) { kind.isBoolean() and ( diff --git a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll index 0e61884b4373..4e8b37531296 100644 --- a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll +++ b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll @@ -38,16 +38,16 @@ signature module AstSig { Location getLocation(); } - /** Gets the child of this AST node at the specified index. */ + /** Gets the child of AST node `n` at the specified index. */ AstNode getChild(AstNode n, int index); - /** Gets the immediately enclosing callable that contains this node. */ + /** Gets the immediately enclosing callable that contains `node`. */ Callable getEnclosingCallable(AstNode node); /** A callable, for example a function, method, constructor, or top-level script. */ class Callable extends AstNode; - /** Gets the body of this callable, if any. */ + /** Gets the body of callable `c`, if any. */ AstNode callableGetBody(Callable c); /** A statement. */ @@ -454,6 +454,28 @@ module Make0 Ast> { default predicate successorValueImplies(ConditionalSuccessor t1, ConditionalSuccessor t2) { none() } + + /** + * An additional context needed to identify the body parts of a callable. + * + * When not used, instantiate with the `Void` type. + */ + class CallableBodyPartContext { + /** Gets a textual representation of this context. */ + string toString(); + } + + /** + * Gets the `index`th part of the body of `c` in context `ctx`. The indices do not + * need to be consecutive nor start from a specific index. + * + * This overrides the default CFG for a `Callable` with sequential evaluation + * of the body parts, in case a singleton `callableGetBody(c)` is inadequate + * to describe the child nodes of `c`. + */ + default AstNode callableGetBodyPart(Callable c, CallableBodyPartContext ctx, int index) { + none() + } } /** @@ -461,6 +483,8 @@ module Make0 Ast> { * by subsequent instatiation of `Make2`. */ module Make1 { + private import codeql.util.DenseRank + /** * Holds if `n` is executed in post-order or in-order. This means that an * additional node is created to represent `n` in the control flow graph. @@ -661,6 +685,41 @@ module Make0 Ast> { * not step to it, since "after" represents normal termination). */ + private predicate callableHasBodyPart(Callable c, AstNode n) { + n = callableGetBody(c) or n = Input1::callableGetBodyPart(c, _, _) + } + + private module BodyPartDenseRankInput implements DenseRankInputSig2 { + class C1 = Callable; + + class C2 = Input1::CallableBodyPartContext; + + class Ranked = AstNode; + + int getRank(C1 c, C2 ctx, Ranked child) { + child = Input1::callableGetBodyPart(c, ctx, result) + } + } + + private predicate getRankedBodyPart = DenseRank2::denseRank/3; + + private AstNode getBodyEntry(Callable c) { + result = callableGetBody(c) and + not exists(getRankedBodyPart(c, _, _)) + or + result = getRankedBodyPart(c, _, 1) + } + + private AstNode getBodyExit(Callable c) { + result = callableGetBody(c) and + not exists(getRankedBodyPart(c, _, _)) + or + exists(Input1::CallableBodyPartContext ctx, int last | + result = getRankedBodyPart(c, ctx, last) and + not exists(getRankedBodyPart(c, ctx, last + 1)) + ) + } + cached private newtype TNode = TBeforeNode(AstNode n) { Input1::cfgCachedStageRef() and exists(getEnclosingCallable(n)) } or @@ -677,9 +736,9 @@ module Make0 Ast> { TAdditionalNode(AstNode n, string tag) { additionalNode(n, tag, _) and exists(getEnclosingCallable(n)) } or - TEntryNode(Callable c) { exists(callableGetBody(c)) } or - TAnnotatedExitNode(Callable c, Boolean normal) { exists(callableGetBody(c)) } or - TExitNode(Callable c) { exists(callableGetBody(c)) } + TEntryNode(Callable c) { callableHasBodyPart(c, _) } or + TAnnotatedExitNode(Callable c, Boolean normal) { callableHasBodyPart(c, _) } or + TExitNode(Callable c) { callableHasBodyPart(c, _) } private class NodeImpl extends TNode { /** @@ -895,7 +954,7 @@ module Make0 Ast> { } /** The `PreControlFlowNode` at the entry point of a callable. */ - final class EntryNodeImpl extends NodeImpl, TEntryNode { + final private class EntryNodeImpl extends NodeImpl, TEntryNode { private Callable c; EntryNodeImpl() { this = TEntryNode(c) } @@ -1097,7 +1156,7 @@ module Make0 Ast> { private predicate endAbruptCompletion(AstNode ast, PreControlFlowNode n, AbruptCompletion c) { Input2::endAbruptCompletion(ast, n, c) or - exists(Callable callable | ast = callableGetBody(callable) | + exists(Callable callable | callableHasBodyPart(callable, ast) | c.getSuccessorType() instanceof ReturnSuccessor and n.(NormalExitNodeImpl).getEnclosingCallable() = callable or @@ -1195,10 +1254,16 @@ module Make0 Ast> { ) } - private Case getRankedCaseCfgOrder(Switch s, int rnk) { - result = rank[rnk](Case c, int i | getCaseControlFlowOrder(s, c) = i | c order by i) + private module CaseDenseRankInput implements DenseRankInputSig1 { + class C = Switch; + + class Ranked = Case; + + predicate getRank = getCaseControlFlowOrder/2; } + private predicate getRankedCaseCfgOrder = DenseRank1::denseRank/2; + private int numberOfStmts(Switch s) { result = strictcount(s.getStmt(_)) } private predicate caseIndex(Switch s, Case c, int caseIdx, int caseStmtPos) { @@ -1255,22 +1320,20 @@ module Make0 Ast> { ) } - private predicate hasSpecificCallableSteps(Callable c) { - exists(EntryNodeImpl entry | entry.getEnclosingCallable() = c and Input2::step(entry, _)) - } - /** Holds if there is a local non-abrupt step from `n1` to `n2`. */ private predicate explicitStep(PreControlFlowNode n1, PreControlFlowNode n2) { Input2::step(n1, n2) or exists(Callable c | - // Allow language-specific overrides for the default entry and exit edges. - not hasSpecificCallableSteps(c) and n1.(EntryNodeImpl).getEnclosingCallable() = c and - n2.isBefore(callableGetBody(c)) + n2.isBefore(getBodyEntry(c)) + or + exists(Input1::CallableBodyPartContext ctx, int i | + n1.isAfter(getRankedBodyPart(c, ctx, i)) and + n2.isBefore(getRankedBodyPart(c, ctx, i + 1)) + ) or - not hasSpecificCallableSteps(c) and - n1.isAfter(callableGetBody(c)) and + n1.isAfter(getBodyExit(c)) and n2.(NormalExitNodeImpl).getEnclosingCallable() = c or n1.(AnnotatedExitNodeImpl).getEnclosingCallable() = c and @@ -1635,11 +1698,19 @@ module Make0 Ast> { not explicitStep(any(PreControlFlowNode n | n.isBefore(ast)), _) } - private AstNode getRankedChild(AstNode parent, int rnk) { - defaultCfg(parent) and - result = rank[rnk](AstNode c, int ix | c = getChild(parent, ix) | c order by ix) + private module ChildDenseRankInput implements DenseRankInputSig1 { + class C = AstNode; + + class Ranked = AstNode; + + int getRank(C parent, Ranked child) { + defaultCfg(parent) and + child = getChild(parent, result) + } } + private predicate getRankedChild = DenseRank1::denseRank/2; + /** * Holds if `n1` to `n2` is a default left-to-right evaluation step for * an `AstNode` that does not otherwise have explicitly defined control @@ -2128,6 +2199,15 @@ module Make0 Ast> { query predicate selfLoop(ControlFlowNode node, SuccessorType t) { node.getASuccessor(t) = node } + + /** + * Holds if `c` does not include `callableGetBody` in a non-empty `callableGetBodyPart`. + */ + query predicate bodyPartOverlap(Callable c) { + exists(callableGetBody(c)) and + exists(Input1::callableGetBodyPart(c, _, _)) and + not Input1::callableGetBodyPart(c, _, _) = callableGetBody(c) + } } } }