-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathDeadStoreOfLocal.ql
More file actions
147 lines (136 loc) · 4.75 KB
/
DeadStoreOfLocal.ql
File metadata and controls
147 lines (136 loc) · 4.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* @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 quality
* maintainability
* useless-code
* 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", "Count", "Any", "All", "Average", "Aggregate", "First", "Last", "FirstOrDefault",
"LastOrDefault", "LongCount", "Max", "Single", "SingleOrDefault", "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::localExprFlow(e, succ) and
not succ = any(DelegateCall dc).getExpr() 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.(AssignableDefinitions::AssignmentDefinition).getAssignment() =
any(Assignment a | not a = any(UsingDeclStmt uds).getAVariableDeclExpr())
or
this instanceof AssignableDefinitions::MutationDefinition
or
this instanceof AssignableDefinitions::TupleAssignmentDefinition
or
// Discards in out assignments are only possible from C# 7 (2017), so we disable this case
// for now
//or
//this.(AssignableDefinitions::OutRefDefinition).getTargetAccess().isOutArgument()
this.(AssignableDefinitions::LocalVariableDefinition).getDeclaration() =
any(LocalVariableDeclExpr lvde |
lvde = any(SpecificCatchClause scc).getVariableDeclExpr()
or
lvde = any(ForeachStmt fs).getVariableDeclExpr() and
not lvde.getName() = "_"
)
or
this instanceof AssignableDefinitions::PatternDefinition
}
/** 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)
or
v.isCaptured()
)
}
/** 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().stripCasts() |
e.getValue() = ["0", "-1", "", "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.getExpr().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
if this.isDefaultLikeInitializer()
then this = unique(AssignableDefinition def | def.getTarget() = this.getTarget())
else any()
}
}
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()