Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
hvitved committed Jun 28, 2024
1 parent 7afebf6 commit beaa1ac
Show file tree
Hide file tree
Showing 17 changed files with 472 additions and 124 deletions.
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
49 changes: 34 additions & 15 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,22 @@ import csharp
*/
module Ssa {
private import internal.SsaImpl as SsaImpl
private import semmle.code.csharp.internal.Location

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

private predicate localSourceVariable(
SourceVariables::LocalScopeSourceVariable sv, LocalScopeVariable v, Callable c
) {
sv.getAssignable() = v and
sv.getEnclosingCallable() = c
}

/**
* A variable that can be SSA converted.
Expand All @@ -34,11 +50,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 |
assignableDefinitionLocalVariable(result, v, c) and
localSourceVariable(this, v, c)
)
}

/**
Expand Down Expand Up @@ -564,9 +579,15 @@ module Ssa {
override Location getLocation() { result = this.getCallable().getLocation() }
}

bindingset[e1, e2]
pragma[inline_late]
private predicate inSameFile(Location e1, Location e2) { e1.getFile() = e2.getFile() }
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()
}
}

/**
* An SSA definition representing the implicit initialization of a parameter
Expand All @@ -590,13 +611,11 @@ module Ssa {
}

override Location getLocation() {
// Each parameter has an implicit entry definition. In case of multiple bodies,
// we need to match the location of the parameter with the location of the
// assigning basic block; requiring them to be in the same file should be
// sufficient (multiple method bodies in the same file would be a compilation
// error).
result = p.getALocation() and
inSameFile(result, this.getBasicBlock().getLocation())
this.getBasicBlock() instanceof ControlFlow::BasicBlocks::EntryBlock and
result = p.getLocation()
or
// multi-bodied method: use matching parameter location
NearestLocation<NearestLocationInput>::nearestLocation(this, result, _)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ module BaseSsa {
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`.
// 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()
|
Expand Down
164 changes: 145 additions & 19 deletions csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.dispatch.RuntimeCallable
private import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.system.collections.Generic
private import semmle.code.csharp.internal.Location

/**
* Gets a source declaration of callable `c` that has a body and is
Expand All @@ -24,6 +25,21 @@ newtype TReturnKind =
TOutReturnKind(int i) { i = any(Parameter p | p.isOut()).getPosition() } or
TRefReturnKind(int i) { i = any(Parameter p | p.isRef()).getPosition() }

private predicate hasMultipleSourceLocations(Callable c) { strictcount(getASourceLocation(c)) > 1 }

private module NearestBodyLocationInput implements NearestLocationInputSig {
class C = ControlFlowElement;

predicate relevantLocations(ControlFlowElement body, Location l1, Location l2) {
exists(Callable c |
hasMultipleSourceLocations(c) and
l1 = getASourceLocation(c) and
body = c.getBody() and
l2 = body.getLocation()
)
}
}

cached
private module Cached {
/**
Expand All @@ -33,7 +49,18 @@ private module Cached {
*/
cached
newtype TDataFlowCallable =
TCallable(Callable c) { c.isUnboundDeclaration() } or
TCallable(Callable c, Location l) {
c.isUnboundDeclaration() and
l = [c.getLocation(), getASourceLocation(c)] and
(
not hasMultipleSourceLocations(c)
or
// when `c` has multiple source locations, only use those with a body;
// for example, `partial` methods may have multiple source locations but
// we are only interested in the one with a body
NearestLocation<NearestBodyLocationInput>::nearestLocation(_, l, _)
)
} or
TSummarizedCallable(FlowSummary::SummarizedCallable sc) or
TFieldOrPropertyCallable(FieldOrProperty f) or
TCapturedVariableCallable(LocalScopeVariable v) { v.isCaptured() }
Expand Down Expand Up @@ -88,6 +115,7 @@ private module DispatchImpl {
*/
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
exists(DispatchCall dc | dc = call.(NonDelegateDataFlowCall).getDispatchCall() |
result = call.getARuntimeTarget() and
result.getUnderlyingCallable() =
getCallableForDataFlow(dc.getADynamicTargetInCallContext(ctx.(NonDelegateDataFlowCall)
.getDispatchCall()).getUnboundDeclaration())
Expand Down Expand Up @@ -159,7 +187,51 @@ class RefReturnKind extends OutRefReturnKind, TRefReturnKind {
/** A callable used for data flow. */
class DataFlowCallable extends TDataFlowCallable {
/** Gets the underlying source code callable, if any. */
Callable asCallable() { this = TCallable(result) }
Callable asCallable(Location l) { this = TCallable(result, l) }

/** Holds if this callable is multi-bodied. */
pragma[nomagic]
predicate isMultiBodied() { hasMultipleSourceLocations(this.asCallable(_)) }

private ControlFlow::Nodes::ElementNode getAMultiBodyEntryNode() {
exists(ControlFlowElement body, Location l |
body = this.asCallable(l).getBody() and
NearestLocation<NearestBodyLocationInput>::nearestLocation(body, l, _) and
result = body.getAControlFlowEntryNode()
)
}

pragma[nomagic]
private ControlFlow::Nodes::ElementNode getAMultiBodyControlFlowNodePred() {
result = this.getAMultiBodyEntryNode().getAPredecessor()
or
result = this.getAMultiBodyControlFlowNodePred().getAPredecessor()
}

pragma[nomagic]
private ControlFlow::Nodes::ElementNode getAMultiBodyControlFlowNodeSucc() {
result = this.getAMultiBodyEntryNode().getASuccessor()
or
result = this.getAMultiBodyControlFlowNodeSucc().getASuccessor()
}

pragma[nomagic]
private ControlFlow::Nodes::ElementNode getAMultiBodyControlFlowNode() {
result =
[
this.getAMultiBodyEntryNode(), this.getAMultiBodyControlFlowNodePred(),
this.getAMultiBodyControlFlowNodeSucc()
]
}

/** Gets a control flow node belonging to this callable. */
pragma[inline]
ControlFlow::Node getAControlFlowNode() {
result = this.getAMultiBodyControlFlowNode()
or
not this.isMultiBodied() and
result.getEnclosingCallable() = this.asCallable(_)
}

/** Gets the underlying summarized callable, if any. */
FlowSummary::SummarizedCallable asSummarizedCallable() { this = TSummarizedCallable(result) }
Expand All @@ -171,7 +243,7 @@ class DataFlowCallable extends TDataFlowCallable {

/** Gets the underlying callable. */
Callable getUnderlyingCallable() {
result = this.asCallable() or result = this.asSummarizedCallable()
result = this.asCallable(_) or result = this.asSummarizedCallable()
}

/** Gets a textual representation of this dataflow callable. */
Expand All @@ -185,7 +257,9 @@ class DataFlowCallable extends TDataFlowCallable {

/** Get the location of this dataflow callable. */
Location getLocation() {
result = this.getUnderlyingCallable().getLocation()
this = TCallable(_, result)
or
result = this.asSummarizedCallable().getLocation()
or
result = this.asFieldOrProperty().getLocation()
or
Expand Down Expand Up @@ -256,6 +330,26 @@ abstract class DataFlowCall extends TDataFlowCall {
}
}

private predicate relevantFolder(Folder f) {
exists(NonDelegateDataFlowCall call, Location l | f = l.getFile().getParentContainer() |
l = call.getLocation() and
call.getARuntimeTargetCandidate(_, _).isMultiBodied()
or
call.getARuntimeTargetCandidate(l, _).isMultiBodied()
)
}

private predicate adjacentFolders(Folder f1, Folder f2) {
f1 = f2.getParentContainer()
or
f2 = f1.getParentContainer()
}

bindingset[f1, f2]
pragma[inline_late]
private predicate folderDist(Folder f1, Folder f2, int i) =
shortestDistances(relevantFolder/1, adjacentFolders/2)(f1, f2, i)

/** A non-delegate C# call relevant for data flow. */
class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
private ControlFlow::Nodes::ElementNode cfn;
Expand All @@ -266,23 +360,59 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
/** Gets the underlying call. */
DispatchCall getDispatchCall() { result = dc }

override DataFlowCallable getARuntimeTarget() {
result.asCallable() = getCallableForDataFlow(dc.getADynamicTarget())
or
pragma[nomagic]
private predicate hasSourceTarget() { dc.getADynamicTarget().fromSource() }

pragma[nomagic]
private FlowSummary::SummarizedCallable getASummarizedCallableTarget() {
// Only use summarized callables with generated summaries in case
// we are not able to dispatch to a source declaration.
exists(FlowSummary::SummarizedCallable sc, boolean static |
result.asSummarizedCallable() = sc and
sc = this.getATarget(static) and
exists(boolean static |
result = this.getATarget(static) and
not (
sc.applyGeneratedModel() and
dc.getADynamicTarget().getUnboundDeclaration().getFile().fromSource()
result.applyGeneratedModel() and
this.hasSourceTarget()
)
|
static = false
or
static = true and not sc instanceof RuntimeCallable
static = true and not result instanceof RuntimeCallable
)
}

pragma[nomagic]
DataFlowCallable getARuntimeTargetCandidate(Location l, Callable c) {
c = result.asCallable(l) and
c = getCallableForDataFlow(dc.getADynamicTarget())
}

pragma[nomagic]
private DataFlowCallable getAMultiBodiedRuntimeTargetCandidate(Callable c, int dist) {
result.isMultiBodied() and
exists(Location l | result = this.getARuntimeTargetCandidate(l, c) |
inSameFile(l, this.getLocation()) and
dist = -1
or
folderDist(l.getFile().getParentContainer(),
this.getLocation().getFile().getParentContainer(), dist)
)
}

pragma[nomagic]
override DataFlowCallable getARuntimeTarget() {
exists(Callable c |
result =
min(DataFlowCallable cand, int dist |
cand = this.getAMultiBodiedRuntimeTargetCandidate(c, dist)
|
cand order by dist
)
)
or
result = this.getARuntimeTargetCandidate(_, _) and
not result.isMultiBodied()
or
result.asSummarizedCallable() = this.getASummarizedCallableTarget()
}

/** Gets a static or dynamic target of this call. */
Expand All @@ -296,9 +426,7 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {

override DataFlow::ExprNode getNode() { result.getControlFlowNode() = cfn }

override DataFlowCallable getEnclosingCallable() {
result.asCallable() = cfn.getEnclosingCallable()
}
override DataFlowCallable getEnclosingCallable() { result.getAControlFlowNode() = cfn }

override string toString() { result = cfn.toString() }

Expand Down Expand Up @@ -326,9 +454,7 @@ class ExplicitDelegateLikeDataFlowCall extends DelegateDataFlowCall, TExplicitDe

override DataFlow::ExprNode getNode() { result.getControlFlowNode() = cfn }

override DataFlowCallable getEnclosingCallable() {
result.asCallable() = cfn.getEnclosingCallable()
}
override DataFlowCallable getEnclosingCallable() { result.getAControlFlowNode() = cfn }

override string toString() { result = cfn.toString() }

Expand Down
Loading

0 comments on commit beaa1ac

Please sign in to comment.