Files
codeql/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql
2018-08-02 17:53:23 +01:00

167 lines
5.0 KiB
Plaintext

/**
* @name Useless assignment to local variable
* @description An assignment to a local variable that is not used later on, or whose value is always
* overwritten, has no effect.
* @kind problem
* @problem.severity warning
* @id cs/useless-assignment-to-local
* @tags maintainability
* external/cwe/cwe-563
* @precision very-high
*/
import csharp
/**
* Gets a callable that either directly captures local variable `v`, or which
* is enclosed by the callable that declares `v` and encloses a callable that
* captures `v`.
*/
Callable getACapturingCallableAncestor(LocalVariable v) {
result = v.getACapturingCallable()
or
exists(Callable mid |
mid = getACapturingCallableAncestor(v) |
result = mid.getEnclosingCallable() and
not v.getEnclosingCallable() = result
)
}
Expr getADelegateExpr(Callable c) {
c = result.(CallableAccess).getTarget()
or
result = c.(AnonymousFunctionExpr)
}
/**
* Holds if `c` is a call where any delegate argument is evaluated immediately.
*/
predicate nonEscapingCall(Call c) {
exists(string name |
c.getTarget().hasName(name) |
name = "ForEach" or
name = "Count" or
name = "Any" or
name = "All" or
name = "Average" or
name = "Aggregate" or
name = "First" or
name = "Last" or
name = "FirstOrDefault" or
name = "LastOrDefault" or
name = "LongCount" or
name = "Max" or
name = "Single" or
name = "SingleOrDefault" or
name = "Sum"
)
}
/**
* Holds if `v` is a captured local variable, and one of the callables capturing
* `v` may escape the local scope.
*/
predicate mayEscape(LocalVariable v) {
exists(Callable c, Expr e, Expr succ |
c = getACapturingCallableAncestor(v) |
e = getADelegateExpr(c) and
DataFlow::localFlow(DataFlow::exprNode(e), DataFlow::exprNode(succ)) and
not succ = any(DelegateCall dc).getDelegateExpr() and
not succ = any(Cast cast).getExpr() and
not succ = any(Call call | nonEscapingCall(call)).getAnArgument() and
not succ = any(AssignableDefinition ad | ad.getTarget() instanceof LocalVariable).getSource()
)
}
class RelevantDefinition extends AssignableDefinition {
RelevantDefinition() {
this instanceof AssignableDefinitions::AssignmentDefinition
or
this instanceof AssignableDefinitions::MutationDefinition
or
this instanceof AssignableDefinitions::TupleAssignmentDefinition
// Discards in out assignments are only possible from C# 7 (2017), so we disable this case
// for now
//or
//this.(AssignableDefinitions::OutRefDefinition).getTargetAccess().isOutArgument()
or
this.(AssignableDefinitions::LocalVariableDefinition).getDeclaration() = any(LocalVariableDeclExpr lvde |
lvde = any(SpecificCatchClause scc).getVariableDeclExpr() or
lvde = any(ForeachStmt fs).getVariableDeclExpr()
)
or
this instanceof AssignableDefinitions::IsPatternDefinition
or
this instanceof AssignableDefinitions::TypeCasePatternDefinition
}
/** Holds if this assignment may be live. */
private predicate isMaybeLive() {
exists(LocalVariable v |
v = this.getTarget() |
// SSA definitions are only created for live variables
this = any(Ssa::ExplicitDefinition ssaDef).getADefinition()
or
mayEscape(v)
)
}
/** Holds if this definition is a variable initializer, for example `string s = null`. */
private predicate isInitializer() {
this.getSource() = this.getTarget().(LocalVariable).getInitializer()
}
/**
* Holds if this definition is a default-like variable initializer, for example
* `string s = null` or `int i = 0`, but not `string s = "Hello"`.
*/
private predicate isDefaultLikeInitializer() {
this.isInitializer() and
exists(Expr e |
e = this.getSource() |
exists(string val |
val = e.getValue() |
val = "0" or
val = "-1" or
val = "" or
val = "false"
)
or
e instanceof NullLiteral
or
e = any(Field f | f.isStatic() and (f.isReadOnly() or f.isConst())).getAnAccess()
or
e instanceof DefaultValueExpr
or
e instanceof AnonymousObjectCreation
)
}
/** Holds if this definition is dead and we want to report it. */
predicate isDead() {
// Ensure that the definition is not in dead code
exists(this.getAControlFlowNode()) and
not this.isMaybeLive() and
(
// Allow dead initializer assignments, such as `string s = string.Empty`, but only
// if the initializer expression assigns a default-like value, and there exists another
// definition of the same variable
this.isInitializer()
implies
(
not this.isDefaultLikeInitializer()
or
not exists(AssignableDefinition other |
other.getTarget() = this.getTarget() |
other != this
)
)
)
}
}
from RelevantDefinition def, LocalVariable v
where v = def.getTarget()
and def.isDead()
select def, "This assignment to $@ is useless, since its value is never read.", v, v.getName()