Skip to content

Commit

Permalink
C#: Move implicit entry definitions inside method bodies in SSA const…
Browse files Browse the repository at this point in the history
…ruction
  • Loading branch information
hvitved committed Jul 2, 2024
1 parent 03d2f46 commit 1557b86
Show file tree
Hide file tree
Showing 28 changed files with 309 additions and 140 deletions.
17 changes: 14 additions & 3 deletions csharp/ql/lib/semmle/code/csharp/Assignable.qll
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,18 @@ private import AssignableInternal
*/
class AssignableDefinition extends TAssignableDefinition {
/**
* DEPRECATED: Use `this.getExpr().getAControlFlowNode()` instead.
*
* Gets a control flow node that updates the targeted assignable when
* reached.
*
* Multiple definitions may relate to the same control flow node. For example,
* the definitions of `x` and `y` in `M(out x, out y)` and `(x, y) = (0, 1)`
* relate to the same call to `M` and assignment node, respectively.
*/
ControlFlow::Node getAControlFlowNode() { result = this.getExpr().getAControlFlowNode() }
deprecated ControlFlow::Node getAControlFlowNode() {
result = this.getExpr().getAControlFlowNode()
}

/**
* Gets the underlying expression that updates the targeted assignable when
Expand Down Expand Up @@ -477,6 +481,11 @@ class AssignableDefinition extends TAssignableDefinition {
exists(Ssa::ExplicitDefinition def | result = def.getAFirstReadAtNode(cfn) |
this = def.getADefinition()
)
or
exists(Ssa::ImplicitParameterDefinition def | result = def.getAFirstReadAtNode(cfn) |
this.(AssignableDefinitions::ImplicitParameterDefinition).getParameter() =
def.getParameter()
)
)
}

Expand Down Expand Up @@ -550,7 +559,7 @@ module AssignableDefinitions {
ControlFlow::BasicBlock bb, Parameter p, AssignableDefinition def
) {
def = any(RefArg arg).getAnAnalyzableRefDef(p) and
bb.getANode() = def.getAControlFlowNode()
bb.getANode() = def.getExpr().getAControlFlowNode()
}

/**
Expand Down Expand Up @@ -662,7 +671,9 @@ module AssignableDefinitions {
/** Gets the underlying parameter. */
Parameter getParameter() { result = p }

override ControlFlow::Node getAControlFlowNode() { result = p.getCallable().getEntryPoint() }
deprecated override ControlFlow::Node getAControlFlowNode() {
result = p.getCallable().getEntryPoint()
}

override Parameter getElement() { result = p }

Expand Down
9 changes: 1 addition & 8 deletions csharp/ql/lib/semmle/code/csharp/ExprOrStmtParent.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import csharp
private import internal.Location

/**
* INTERNAL: Do not use.
Expand Down Expand Up @@ -65,14 +66,6 @@ private ControlFlowElement getBody(Callable c) {
result = getStatementBody(c)
}

pragma[nomagic]
private SourceLocation getASourceLocation(Element e) {
result = e.getALocation().(SourceLocation) and
not exists(e.getALocation().(SourceLocation).getMappedLocation())
or
result = e.getALocation().(SourceLocation).getMappedLocation()
}

pragma[nomagic]
private predicate hasNoSourceLocation(Element e) { not exists(getASourceLocation(e)) }

Expand Down
21 changes: 21 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/Location.qll
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ class Location extends @location {

/** Gets the 1-based column number (inclusive) where this location ends. */
final int getEndColumn() { this.hasLocationInfo(_, _, _, _, result) }

/** Holds if this location starts strictly before the specified location. */
bindingset[this, other]
pragma[inline_late]
predicate strictlyBefore(Location other) {
this.getFile() = other.getFile() and
(
this.getStartLine() < other.getStartLine()
or
this.getStartLine() = other.getStartLine() and this.getStartColumn() < other.getStartColumn()
)
}

/** Holds if this location starts before the specified location. */
bindingset[this, other]
pragma[inline_late]
predicate before(Location other) {
this.getStartLine() < other.getStartLine()
or
this.getStartLine() = other.getStartLine() and this.getStartColumn() <= other.getStartColumn()
}
}

/** An empty location. */
Expand Down
13 changes: 7 additions & 6 deletions csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ private ControlFlowElement getANullCheck(
exists(Expr e, G::AbstractValue v | v.branch(result, s, e) | exprImpliesSsaDef(e, v, def, nv))
}

private predicate isMaybeNullArgument(Ssa::ExplicitDefinition def, MaybeNullExpr arg) {
private predicate isMaybeNullArgument(Ssa::ImplicitParameterDefinition def, MaybeNullExpr arg) {
exists(AssignableDefinitions::ImplicitParameterDefinition pdef, Parameter p |
pdef = def.getADefinition()
p = def.getParameter()
|
p = pdef.getParameter().getUnboundDeclaration() and
arg = p.getAnAssignedArgument() and
Expand Down Expand Up @@ -208,9 +208,9 @@ private predicate hasMultipleParamsArguments(Call c) {
)
}

private predicate isNullDefaultArgument(Ssa::ExplicitDefinition def, AlwaysNullExpr arg) {
private predicate isNullDefaultArgument(Ssa::ImplicitParameterDefinition def, AlwaysNullExpr arg) {
exists(AssignableDefinitions::ImplicitParameterDefinition pdef, Parameter p |
pdef = def.getADefinition()
p = def.getParameter()
|
p = pdef.getParameter().getUnboundDeclaration() and
arg = p.getDefaultValue() and
Expand Down Expand Up @@ -543,9 +543,10 @@ class Dereference extends G::DereferenceableExpr {
p.fromSource() // assume all non-source extension methods perform a dereference
implies
exists(
Ssa::ExplicitDefinition def, AssignableDefinitions::ImplicitParameterDefinition pdef
Ssa::ImplicitParameterDefinition def,
AssignableDefinitions::ImplicitParameterDefinition pdef
|
pdef = def.getADefinition()
p = def.getParameter()
|
p.getUnboundDeclaration() = pdef.getParameter() and
def.getARead() instanceof Dereference
Expand Down
83 changes: 73 additions & 10 deletions csharp/ql/lib/semmle/code/csharp/dataflow/SSA.qll
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ import csharp
*/
module Ssa {
private import internal.SsaImpl as SsaImpl
private import semmle.code.csharp.internal.Location

pragma[nomagic]
private predicate assignableDefinitionLocalScopeVariable(
AssignableDefinition ad, LocalScopeVariable v, Callable c
) {
ad.getTarget() = v and
ad.getEnclosingCallable() = c
}

pragma[nomagic]
private predicate localScopeSourceVariable(
SourceVariables::LocalScopeSourceVariable sv, LocalScopeVariable v, Callable c1, Callable c2
) {
sv.getAssignable() = v and
sv.getEnclosingCallable() = c1 and
v.getCallable() = c2
}

/**
* A variable that can be SSA converted.
Expand All @@ -34,11 +52,10 @@ module Ssa {
or
// Local variable declaration without initializer
not exists(result.getTargetAccess()) and
this =
any(SourceVariables::LocalScopeSourceVariable v |
result.getTarget() = v.getAssignable() and
result.getEnclosingCallable() = v.getEnclosingCallable()
)
exists(LocalScopeVariable v, Callable c |
assignableDefinitionLocalScopeVariable(result, v, c) and
localScopeSourceVariable(this, v, c, _)
)
}

/**
Expand Down Expand Up @@ -507,9 +524,7 @@ module Ssa {
override Element getElement() { result = ad.getElement() }

override string toString() {
if this.getADefinition() instanceof AssignableDefinitions::ImplicitParameterDefinition
then result = SsaImpl::getToStringPrefix(this) + "SSA param(" + this.getSourceVariable() + ")"
else result = SsaImpl::getToStringPrefix(this) + "SSA def(" + this.getSourceVariable() + ")"
result = SsaImpl::getToStringPrefix(this) + "SSA def(" + this.getSourceVariable() + ")"
}

override Location getLocation() { result = ad.getLocation() }
Expand Down Expand Up @@ -537,7 +552,7 @@ module Ssa {

/**
* An SSA definition representing the implicit initialization of a variable
* at the beginning of a callable. Either the variable is a local scope variable
* at the beginning of a callable. Either a parameter, a local scope variable
* captured by the callable, or a field or property accessed inside the callable.
*/
class ImplicitEntryDefinition extends ImplicitDefinition {
Expand All @@ -551,7 +566,7 @@ module Ssa {
/** Gets the callable that this entry definition belongs to. */
final Callable getCallable() { result = this.getBasicBlock().getCallable() }

override Callable getElement() { result = this.getCallable() }
override Element getElement() { result = this.getCallable() }

override string toString() {
if this.getSourceVariable().getAssignable() instanceof LocalScopeVariable
Expand All @@ -566,6 +581,54 @@ module Ssa {
override Location getLocation() { result = this.getCallable().getLocation() }
}

private module NearestLocationInput implements NearestLocationInputSig {
class C = ImplicitParameterDefinition;

predicate relevantLocations(ImplicitParameterDefinition def, Location l1, Location l2) {
not def.getBasicBlock() instanceof ControlFlow::BasicBlocks::EntryBlock and
l1 = def.getParameter().getALocation() and
l2 = def.getBasicBlock().getLocation()
}
}

pragma[nomagic]
private predicate implicitEntryDef(ImplicitEntryDefinition def, SourceVariable v, Callable c) {
v = def.getSourceVariable() and
c = def.getCallable()
}

/**
* An SSA definition representing the implicit initialization of a parameter
* at the beginning of a callable.
*/
class ImplicitParameterDefinition extends ImplicitEntryDefinition {
private Parameter p;

ImplicitParameterDefinition() {
exists(SourceVariable sv, Callable c |
implicitEntryDef(this, sv, c) and
localScopeSourceVariable(sv, p, _, c)
)
}

/** Gets the parameter that this entry definition represents. */
Parameter getParameter() { result = p }

override Element getElement() { result = this.getParameter() }

override string toString() {
result = SsaImpl::getToStringPrefix(this) + "SSA param(" + this.getParameter() + ")"
}

override Location getLocation() {
not NearestLocation<NearestLocationInput>::nearestLocation(this, _, _) and
result = p.getLocation()
or
// multi-bodied method: use matching parameter location
NearestLocation<NearestLocationInput>::nearestLocation(this, result, _)
}
}

/**
* An SSA definition representing the potential definition of a variable
* via a call.
Expand Down
24 changes: 21 additions & 3 deletions csharp/ql/lib/semmle/code/csharp/dataflow/internal/BaseSSA.qll
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module BaseSsa {
private predicate definitionAt(
AssignableDefinition def, ControlFlow::BasicBlock bb, int i, SsaInput::SourceVariable v
) {
bb.getNode(i) = def.getAControlFlowNode() and
bb.getNode(i) = def.getExpr().getAControlFlowNode() and
v = def.getTarget() and
// In cases like `(x, x) = (0, 1)`, we discard the first (dead) definition of `x`
not exists(TupleAssignmentDefinition first, TupleAssignmentDefinition second | first = def |
Expand All @@ -27,8 +27,19 @@ module BaseSsa {
private predicate implicitEntryDef(
Callable c, ControlFlow::BasicBlocks::EntryBlock bb, SsaInput::SourceVariable v
) {
v.isReadonlyCapturedBy(c) and
c = bb.getCallable()
exists(ControlFlow::ControlFlow::BasicBlocks::EntryBlock entry |
c = entry.getCallable() and
// In case `c` has multiple bodies, we want each body to gets its own implicit
// entry definition. In case `c` doesn't have multiple bodies, the line below
// is simply the same as `bb = entry`, because `entry.getFirstNode().getASuccessor()`
// will be in the entry block.
bb = entry.getFirstNode().getASuccessor().getBasicBlock() and
c = v.getCallable()
|
v.isReadonlyCapturedBy(c)
or
v instanceof Parameter
)
}

private module SsaInput implements SsaImplCommon::InputSig<Location> {
Expand Down Expand Up @@ -83,6 +94,13 @@ module BaseSsa {
)
}

final predicate isImplicitEntryDefinition(SsaInput::SourceVariable v) {
exists(ControlFlow::BasicBlock bb |
this.definesAt(v, bb, -1) and
implicitEntryDef(_, bb, v)
)
}

private Definition getAPhiInputOrPriorDefinition() {
result = this.(PhiNode).getAnInput() or
SsaImpl::uncertainWriteDefinitionInput(this, result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ abstract class ControlFlowReachabilityConfiguration extends string {
exists(ControlFlow::BasicBlock bb, boolean isSuccessor, int i, int j |
this.reachesBasicBlockDefinitionBase(e, def, isSuccessor, cfn, i, bb) and
cfnDef = bb.getNode(j) and
def.getAControlFlowNode() = cfnDef
def.getExpr().getAControlFlowNode() = cfnDef
|
isSuccessor = true and j >= i
or
Expand All @@ -208,7 +208,7 @@ abstract class ControlFlowReachabilityConfiguration extends string {
or
exists(ControlFlow::BasicBlock bb |
this.reachesBasicBlockDefinitionRec(e, def, _, cfn, bb) and
def.getAControlFlowNode() = cfnDef and
def.getExpr().getAControlFlowNode() = cfnDef and
cfnDef = bb.getANode()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ private ControlFlow::Node getAPrimaryConstructorParameterCfn(ParameterAccess pa)
(
result = pa.(ParameterRead).getAControlFlowNode()
or
pa = any(AssignableDefinition def | result = def.getAControlFlowNode()).getTargetAccess()
pa =
any(AssignableDefinition def | result = def.getExpr().getAControlFlowNode()).getTargetAccess()
)
}

Expand Down Expand Up @@ -352,9 +353,7 @@ module VariableCapture {

VariableWrite() {
def.getTarget() = v.asLocalScopeVariable() and
this = def.getAControlFlowNode() and
// the shared variable capture library inserts implicit parameter definitions
not def instanceof AssignableDefinitions::ImplicitParameterDefinition
this = def.getExpr().getAControlFlowNode()
}

ControlFlow::Node getRhs() { LocalFlow::defAssigns(def, this, result) }
Expand Down Expand Up @@ -1098,13 +1097,10 @@ private module Cached {
TExprNode(ControlFlow::Nodes::ElementNode cfn) { cfn.getAstNode() instanceof Expr } or
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) {
// Handled by `TExplicitParameterNode` below
not def.(Ssa::ExplicitDefinition).getADefinition() instanceof
AssignableDefinitions::ImplicitParameterDefinition
not def instanceof Ssa::ImplicitParameterDefinition
} or
TAssignableDefinitionNode(AssignableDefinition def, ControlFlow::Node cfn) {
cfn = def.getAControlFlowNode() and
// Handled by `TExplicitParameterNode` below
not def instanceof AssignableDefinitions::ImplicitParameterDefinition
cfn = def.getExpr().getAControlFlowNode()
} or
TExplicitParameterNode(Parameter p) {
p = any(DataFlowCallable dfc).asCallable().getAParameter()
Expand Down Expand Up @@ -1351,10 +1347,7 @@ private module ParameterNodes {
ExplicitParameterNode() { this = TExplicitParameterNode(parameter) }

/** Gets the SSA definition corresponding to this parameter, if any. */
Ssa::ExplicitDefinition getSsaDefinition() {
result.getADefinition().(AssignableDefinitions::ImplicitParameterDefinition).getParameter() =
parameter
}
Ssa::ImplicitParameterDefinition getSsaDefinition() { result.getParameter() = parameter }

override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
c.asCallable().getParameter(pos.getPosition()) = parameter
Expand Down
Loading

0 comments on commit 1557b86

Please sign in to comment.