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
38 changes: 23 additions & 15 deletions csharp/ql/lib/semmle/code/csharp/controlflow/Guards.qll
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ private module GuardsInput implements
}
}

pragma[nomagic]
predicate equalityTest(Expr eqtest, Expr left, Expr right, boolean polarity) {
exists(ComparisonTest ct |
ct.getExpr() = eqtest and
Expand Down Expand Up @@ -410,6 +411,22 @@ private predicate typePattern(PatternMatch pm, TypePatternExpr tpe, Type t) {
t = pm.getExpr().getType()
}

pragma[nomagic]
private predicate dereferenceableExpr(Expr e, boolean isNullableType) {
exists(Type t | t = e.getType() |
t instanceof NullableType and
isNullableType = true
or
t instanceof RefType and
isNullableType = false
)
or
exists(Expr parent |
dereferenceableExpr(parent, isNullableType) and
e = getNullEquivParent(parent)
)
}

/**
* An expression that evaluates to a value that can be dereferenced. That is,
* an expression that may evaluate to `null`.
Expand All @@ -418,21 +435,12 @@ class DereferenceableExpr extends Expr {
private boolean isNullableType;

DereferenceableExpr() {
exists(Expr e, Type t |
// There is currently a bug in the extractor: the type of `x?.Length` is
// incorrectly `int`, while it should have been `int?`. We apply
// `getNullEquivParent()` as a workaround
this = getNullEquivParent*(e) and
t = e.getType() and
not this instanceof SwitchCaseExpr and
not this instanceof PatternExpr
|
t instanceof NullableType and
isNullableType = true
or
t instanceof RefType and
isNullableType = false
)
// There is currently a bug in the extractor: the type of `x?.Length` is
// incorrectly `int`, while it should have been `int?`. We apply
// `getNullEquivParent()` as a workaround
dereferenceableExpr(this, isNullableType) and
not this instanceof SwitchCaseExpr and
not this instanceof PatternExpr
}

/** Holds if this expression has a nullable type `T?`. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,19 @@ private Element getAChild(Element p) {
result = p.(AssignOperation).getExpandedAssignment()
}

pragma[nomagic]
private predicate astNode(Element e) {
e = any(@top_level_exprorstmt_parent p | not p instanceof Attribute)
or
exists(Element parent |
astNode(parent) and
e = getAChild(parent)
)
}

/** An AST node. */
class AstNode extends Element, TAstNode {
AstNode() { this = getAChild*(any(@top_level_exprorstmt_parent p | not p instanceof Attribute)) }
AstNode() { astNode(this) }

int getId() { idOf(this, result) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,47 @@ private class ControlFlowScope extends ControlFlowElement {
predicate isNonExact() { exactScope = false }
}

private ControlFlowElement getANonExactScopeChild(ControlFlowScope scope) {
scope.isNonExact() and
result = scope
private newtype TControlFlowElementOrBasicBlock =
TControlFlowElement(ControlFlowElement cfe) or
TBasicBlock(ControlFlow::BasicBlock bb)

class ControlFlowElementOrBasicBlock extends TControlFlowElementOrBasicBlock {
ControlFlowElement asControlFlowElement() { this = TControlFlowElement(result) }

ControlFlow::BasicBlock asBasicBlock() { this = TBasicBlock(result) }

string toString() {
result = this.asControlFlowElement().toString()
or
result = this.asBasicBlock().toString()
}

Location getLocation() {
result = this.asControlFlowElement().getLocation()
or
result = this.asBasicBlock().getLocation()
}
}

private predicate isBasicBlock(ControlFlowElementOrBasicBlock c) { c instanceof TBasicBlock }

private predicate isNonExactScope(ControlFlowElementOrBasicBlock c) {
c.asControlFlowElement().(ControlFlowScope).isNonExact()
}

private predicate step(ControlFlowElementOrBasicBlock pred, ControlFlowElementOrBasicBlock succ) {
pred.asBasicBlock().getANode().getAstNode() = succ.asControlFlowElement()
or
result = getANonExactScopeChild(scope).getAChild()
pred.asControlFlowElement() = succ.asControlFlowElement().getAChild()
}

private predicate basicBlockInNonExactScope(
ControlFlowElementOrBasicBlock bb, ControlFlowElementOrBasicBlock scope
) = doublyBoundedFastTC(step/2, isBasicBlock/1, isNonExactScope/1)(bb, scope)

pragma[noinline]
private ControlFlow::BasicBlock getABasicBlockInScope(ControlFlowScope scope, boolean exactScope) {
result.getANode().getAstNode() = getANonExactScopeChild(scope) and
basicBlockInNonExactScope(TBasicBlock(result), TControlFlowElement(scope)) and
exactScope = false
or
scope.isExact() and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ private import FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.FlowSummary as FlowSummary
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.Conversion
private import semmle.code.csharp.exprs.internal.Expr
private import semmle.code.csharp.dataflow.internal.SsaImpl as SsaImpl
private import semmle.code.csharp.ExprOrStmtParent
private import semmle.code.csharp.Unification
Expand Down Expand Up @@ -2377,6 +2378,16 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
storeStepDelegateCall(node1, c, node2)
}

pragma[nomagic]
private predicate isAssignExprLValueDescendant(Expr e) {
e = any(AssignExpr ae).getLValue()
or
exists(Expr parent |
isAssignExprLValueDescendant(parent) and
e = parent.getAChildExpr()
)
}

private class ReadStepConfiguration extends ControlFlowReachabilityConfiguration {
ReadStepConfiguration() { this = "ReadStepConfiguration" }

Expand Down Expand Up @@ -2432,7 +2443,7 @@ private class ReadStepConfiguration extends ControlFlowReachabilityConfiguration
scope =
any(AssignExpr ae |
ae = defTo.(AssignableDefinitions::TupleAssignmentDefinition).getAssignment() and
e = ae.getLValue().getAChildExpr*().(TupleExpr) and
isAssignExprLValueDescendant(e.(TupleExpr)) and
exactScope = false and
isSuccessor = true
)
Expand Down Expand Up @@ -2488,7 +2499,7 @@ private predicate readContentStep(Node node1, Content c, Node node2) {
)
or
// item = variable in node1 = (..., variable, ...) in a case/is var (..., ...)
te = any(PatternExpr pe).getAChildExpr*() and
isPatternExprDescendant(te) and
exists(AssignableDefinitions::LocalVariableDefinition lvd |
node2.(AssignableDefinitionNode).getDefinition() = lvd and
lvd.getDeclaration() = item and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,47 @@ private class ControlFlowScope extends ControlFlowElement {
predicate isNonExact() { exactScope = false }
}

private ControlFlowElement getANonExactScopeChild(ControlFlowScope scope) {
scope.isNonExact() and
result = scope
private newtype TControlFlowElementOrBasicBlock =
TControlFlowElement(ControlFlowElement cfe) or
TBasicBlock(ControlFlow::BasicBlock bb)

class ControlFlowElementOrBasicBlock extends TControlFlowElementOrBasicBlock {
ControlFlowElement asControlFlowElement() { this = TControlFlowElement(result) }

ControlFlow::BasicBlock asBasicBlock() { this = TBasicBlock(result) }

string toString() {
result = this.asControlFlowElement().toString()
or
result = this.asBasicBlock().toString()
}

Location getLocation() {
result = this.asControlFlowElement().getLocation()
or
result = this.asBasicBlock().getLocation()
}
}

private predicate isBasicBlock(ControlFlowElementOrBasicBlock c) { c instanceof TBasicBlock }

private predicate isNonExactScope(ControlFlowElementOrBasicBlock c) {
c.asControlFlowElement().(ControlFlowScope).isNonExact()
}

private predicate step(ControlFlowElementOrBasicBlock pred, ControlFlowElementOrBasicBlock succ) {
pred.asBasicBlock().getANode().getAstNode() = succ.asControlFlowElement()
or
result = getANonExactScopeChild(scope).getAChild()
pred.asControlFlowElement() = succ.asControlFlowElement().getAChild()
}

private predicate basicBlockInNonExactScope(
ControlFlowElementOrBasicBlock bb, ControlFlowElementOrBasicBlock scope
) = doublyBoundedFastTC(step/2, isBasicBlock/1, isNonExactScope/1)(bb, scope)

pragma[noinline]
private ControlFlow::BasicBlock getABasicBlockInScope(ControlFlowScope scope, boolean exactScope) {
result.getANode().getAstNode() = getANonExactScopeChild(scope) and
basicBlockInNonExactScope(TBasicBlock(result), TControlFlowElement(scope)) and
exactScope = false
or
scope.isExact() and
Expand Down
17 changes: 14 additions & 3 deletions csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import semmle.code.csharp.Type
private import semmle.code.csharp.ExprOrStmtParent
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.TypeRef
private import internal.Expr

/**
* An expression. Either an access (`Access`), a call (`Call`), an object or
Expand Down Expand Up @@ -64,14 +65,24 @@ class Expr extends ControlFlowElement, @expr {
/** Gets the enclosing callable of this expression, if any. */
override Callable getEnclosingCallable() { enclosingCallable(this, result) }

pragma[nomagic]
private predicate isExpandedAssignmentRValueDescendant() {
this =
any(AssignOperation op).getExpandedAssignment().getRValue().getChildExpr(0).getAChildExpr()
or
exists(Expr parent |
parent.isExpandedAssignmentRValueDescendant() and
this = parent.getAChildExpr()
)
}

/**
* Holds if this expression is generated by the compiler and does not appear
* explicitly in the source code.
*/
final predicate isImplicit() {
compiler_generated(this) or
this =
any(AssignOperation op).getExpandedAssignment().getRValue().getChildExpr(0).getAChildExpr+()
this.isExpandedAssignmentRValueDescendant()
}

/**
Expand Down Expand Up @@ -1133,7 +1144,7 @@ class TupleExpr extends Expr, @tuple_expr {
/** Holds if this expression is a tuple construction. */
predicate isConstruction() {
not this = getAnAssignOrForeachChild() and
not this = any(PatternExpr pe).getAChildExpr*()
not isPatternExprDescendant(this)
}

override string getAPrimaryQlClass() { result = "TupleExpr" }
Expand Down
11 changes: 11 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/exprs/internal/Expr.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
private import csharp

pragma[nomagic]
predicate isPatternExprDescendant(Expr e) {
e instanceof PatternExpr
or
exists(Expr parent |
isPatternExprDescendant(parent) and
e = parent.getAChildExpr()
)
}
51 changes: 32 additions & 19 deletions csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,6 @@

import csharp

/** An expression containing a qualified member access, a method call, or an array access. */
class DangerousExpression extends Expr {
DangerousExpression() {
exists(Expr e | this = e.getParent*() |
exists(Expr q | q = e.(MemberAccess).getQualifier() |
not q instanceof ThisAccess and
not q instanceof BaseAccess
)
or
e instanceof MethodCall
or
e instanceof ArrayAccess
) and
not exists(Expr e | this = e.getParent*() | e.(Call).getTarget().getAParameter().isOutOrRef())
}
}

/** A use of `&` or `|` on operands of type boolean. */
class NonShortCircuit extends BinaryBitwiseOperation {
NonShortCircuit() {
Expand All @@ -42,10 +25,40 @@ class NonShortCircuit extends BinaryBitwiseOperation {
) and
not exists(AssignBitwiseOperation abo | abo.getExpandedAssignment().getRValue() = this) and
this.getLeftOperand().getType() instanceof BoolType and
this.getRightOperand().getType() instanceof BoolType and
this.getRightOperand() instanceof DangerousExpression
this.getRightOperand().getType() instanceof BoolType
}

pragma[nomagic]
private predicate hasRightOperandDescendant(Expr e) {
e = this.getRightOperand()
or
exists(Expr parent |
this.hasRightOperandDescendant(parent) and
e.getParent() = parent
)
}

/**
* Holds if this non-short-circuit expression contains a qualified member access,
* a method call, or an array access inside the right operand.
*/
predicate isDangerous() {
exists(Expr e | this.hasRightOperandDescendant(e) |
exists(Expr q | q = e.(MemberAccess).getQualifier() |
not q instanceof ThisAccess and
not q instanceof BaseAccess
)
or
e instanceof MethodCall
or
e instanceof ArrayAccess
) and
not exists(Expr e | this.hasRightOperandDescendant(e) |
e.(Call).getTarget().getAParameter().isOutOrRef()
)
}
}

from NonShortCircuit e
where e.isDangerous()
select e, "Potentially dangerous use of non-short circuit logic."