mirror of
https://github.com/github/codeql.git
synced 2026-03-03 06:14:22 +01:00
167 lines
5.0 KiB
Plaintext
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()
|