Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 42 additions & 99 deletions csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 |
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions java/ql/lib/semmle/code/java/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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() }

Expand Down Expand Up @@ -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
(
Expand Down
93 changes: 76 additions & 17 deletions shared/controlflow/codeql/controlflow/ControlFlowGraph.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Comment thread
hvitved marked this conversation as resolved.
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. */
Expand Down Expand Up @@ -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()
}
}

/**
Expand Down Expand Up @@ -661,6 +683,36 @@ 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 AstNode getRankedBodyPart(Callable c, Input1::CallableBodyPartContext ctx, int rnk) {
result =
rank[rnk](AstNode child, int ix |
child = Input1::callableGetBodyPart(c, ctx, ix)
|
child order by ix
)
}

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
Expand All @@ -677,9 +729,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 {
/**
Expand Down Expand Up @@ -895,7 +947,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) }
Expand Down Expand Up @@ -1097,7 +1149,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
Expand Down Expand Up @@ -1255,22 +1307,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
Comment thread
hvitved marked this conversation as resolved.
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
Expand Down Expand Up @@ -2128,6 +2178,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)
}
}
}
}
Expand Down
Loading