mirror of
https://github.com/github/codeql.git
synced 2026-05-14 11:19:27 +02:00
Merge pull request #21743 from hvitved/cfg/body-parts
C#: Move handling of callables into shared control flow library
This commit is contained in:
@@ -25,7 +25,7 @@ private module Ast implements AstSig<Location> {
|
||||
|
||||
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<Location> {
|
||||
|
||||
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<Location> {
|
||||
* 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
|
||||
|
||||
@@ -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
|
||||
(
|
||||
|
||||
@@ -38,16 +38,16 @@ signature module AstSig<LocationSig Location> {
|
||||
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<LocationSig Location, AstSig<Location> 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<LocationSig Location, AstSig<Location> Ast> {
|
||||
* by subsequent instatiation of `Make2`.
|
||||
*/
|
||||
module Make1<InputSig1 Input1> {
|
||||
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<LocationSig Location, AstSig<Location> 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<BodyPartDenseRankInput>::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<LocationSig Location, AstSig<Location> 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<LocationSig Location, AstSig<Location> 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<LocationSig Location, AstSig<Location> 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<LocationSig Location, AstSig<Location> 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<CaseDenseRankInput>::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<LocationSig Location, AstSig<Location> 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
|
||||
not hasSpecificCallableSteps(c) and
|
||||
n1.isAfter(callableGetBody(c)) and
|
||||
exists(Input1::CallableBodyPartContext ctx, int i |
|
||||
n1.isAfter(getRankedBodyPart(c, ctx, i)) and
|
||||
n2.isBefore(getRankedBodyPart(c, ctx, i + 1))
|
||||
)
|
||||
or
|
||||
n1.isAfter(getBodyExit(c)) and
|
||||
n2.(NormalExitNodeImpl).getEnclosingCallable() = c
|
||||
or
|
||||
n1.(AnnotatedExitNodeImpl).getEnclosingCallable() = c and
|
||||
@@ -1635,11 +1698,19 @@ module Make0<LocationSig Location, AstSig<Location> 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<ChildDenseRankInput>::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<LocationSig Location, AstSig<Location> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user