forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDeadCode.qll
More file actions
317 lines (296 loc) · 11.5 KB
/
DeadCode.qll
File metadata and controls
317 lines (296 loc) · 11.5 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
import java
import semmle.code.java.deadcode.DeadEnumConstant
import semmle.code.java.deadcode.DeadCodeCustomizations
import semmle.code.java.deadcode.DeadField
import semmle.code.java.deadcode.EntryPoints
/**
* Holds if the given callable has any liveness causes.
*/
predicate isLive(Callable c) {
exists(EntryPoint e | c = e.getALiveCallable())
or
exists(Callable live | isLive(live) | live = possibleLivenessCause(c))
}
/**
* Compute a list of callables such that the liveness of any result
* would imply the liveness of `c`.
*/
Callable possibleLivenessCause(Callable c, string reason) {
c.(Method).overridesOrInstantiates(result.(Method)) and
reason = "is overridden or instantiated by"
or
result.calls(c) and reason = "calls"
or
result.callsConstructor(c.(Constructor)) and reason = "calls constructor"
or
exists(ClassInstanceExpr e | e.getEnclosingCallable() = result |
e.getConstructor() = c and reason = "constructs"
)
or
c = result.getSourceDeclaration() and c != result and reason = "instantiates"
or
c.hasName("<clinit>") and
reason = "class initialization" and
exists(RefType clintedType | c = clintedType.getASupertype*().getACallable() |
result.getDeclaringType() = clintedType or
result.getAnAccessedField().getDeclaringType() = clintedType
)
or
c.hasName("<obinit>") and
reason = "object initialization" and
result = c.getDeclaringType().getAConstructor()
}
Callable possibleLivenessCause(Callable c) { result = possibleLivenessCause(c, _) }
/**
* A dead root is not live, and has no liveness causes.
*
* Dead roots are reported for dead classes and dead methods to help verify that classes and
* methods with dependencies are actually dead. A dead class or method may have no dead roots, if
* it is involved in a dead code cycle.
*/
class DeadRoot extends Callable {
DeadRoot() {
not isLive(this) and
// Not a dead root if there exists at least one liveness cause that is not this method.
not exists(Callable c | c = possibleLivenessCause(this) and c != this)
}
}
/**
* For a dead callable, we identify all the possible dead roots.
*
* For dead callables which are either part of dead code cycles, or are only depended upon by
* callables in dead cycles, there will be no dead roots.
*/
DeadRoot getADeadRoot(Callable c) {
not isLive(c) and
(
result = c or
result = getADeadRoot(possibleLivenessCause(c))
)
}
/**
* A constructor that is only declared to override the public accessibility of
* the default constructor generated by the compiler.
*/
class SuppressedConstructor extends Constructor {
SuppressedConstructor() {
// Must be private or protected to suppress it.
(
isPrivate()
or
// A protected, suppressed constructor only makes sense in a non-abstract class.
isProtected() and not getDeclaringType().isAbstract()
) and
// Must be no-arg in order to replace the compiler generated default constructor.
getNumberOfParameters() = 0 and
// Not the compiler-generated constructor itself.
not isDefaultConstructor() and
// Verify that there is only one statement, which is the `super()` call. This exists
// even for empty constructors.
getBody().(Block).getNumStmt() = 1 and
getBody().(Block).getAStmt().(SuperConstructorInvocationStmt).getNumArgument() = 0 and
// A constructor that is called is not acting to suppress the default constructor. We permit
// calls from suppressed and default constructors - in both cases, they can only come from
// sub-class constructors.
not exists(Call c |
c.getCallee().getSourceDeclaration() = this and
not c.getCaller() instanceof SuppressedConstructor and
not c.getCaller().(Constructor).isDefaultConstructor()
) and
// If other constructors are declared, then no compiler-generated constructor is added, so
// this constructor is not acting to suppress the default compiler-generated constructor.
not exists(Constructor other | other = getDeclaringType().getAConstructor() and other != this)
}
}
/**
* A namespace class is one that is used purely as a container for static classes, methods and fields.
*/
class NamespaceClass extends RefType {
NamespaceClass() {
fromSource() and
// All members, apart from the default constructor and, if present, a "suppressed" constructor
// must be static. There must be at least one member apart from the permitted constructors.
forex(Member m |
m.getDeclaringType() = this and
not m.(Constructor).isDefaultConstructor() and
not m instanceof SuppressedConstructor
|
m.isStatic()
) and
// Must only extend other namespace classes, or `Object`.
forall(RefType r | r = getASupertype() | r instanceof TypeObject or r instanceof NamespaceClass)
}
}
/**
* A `ClassOrInterface` type that is from source.
*
* This represents the set of classes and interfaces for which we will determine liveness. Each
* `SourceClassOrInterfacce` will either be a `LiveClass` or `DeadClass`.
*/
library class SourceClassOrInterface extends ClassOrInterface {
SourceClassOrInterface() { this.fromSource() }
}
/**
* A source class or interface is live if it fulfills one of the following criteria:
*
* - It, or a sub-class, contains a live callable.
* - It contains a live field.
* - It is a namespace class and it contains a live nested class.
* - It is a whitelisted class.
* - It is an annotation class - these are assumed to be always live.
* - It is an anonymous class - these classes are dead if and only if the outer method is dead.
*/
class LiveClass extends SourceClassOrInterface {
LiveClass() {
exists(Callable c | c.getDeclaringType().getASupertype*().getSourceDeclaration() = this |
isLive(c)
)
or
exists(LiveField f | f.getDeclaringType() = this |
// A `serialVersionUID` field is considered to be a live field, but is
// not be enough to be make this class live.
not f instanceof SerialVersionUIDField
)
or
// If this is a namespace class, it is live if there is at least one live nested class.
// The definition of `NamespaceClass` is such, that the nested classes must all be static.
// Static methods are handled above.
this instanceof NamespaceClass and
exists(NestedType r | r.getEnclosingType() = this | r instanceof LiveClass)
or
// An annotation on the class is reflectively accessed.
exists(ReflectiveAnnotationAccess reflectiveAnnotationAccess |
this = reflectiveAnnotationAccess.getInferredClassType() and
isLive(reflectiveAnnotationAccess.getEnclosingCallable())
)
or
this instanceof AnonymousClass
or
this instanceof WhitelistedLiveClass
or
this instanceof AnnotationType
}
}
/**
* A class is dead if it is from source, and contains no live callables and no live fields. Nested
* classes make the outer class live if and only if the outer class is considered to be present for
* namespace purposes only, and the nested class is static.
*
* Nested instance classes require no special handling. If the nested instance class accesses fields
* or methods on the outer class, then these will already be marked as live fields and methods. If
* it accesses no methods or fields from the outer, then the nested class can be made static, and
* moved into another file.
*/
class DeadClass extends SourceClassOrInterface {
DeadClass() { not this instanceof LiveClass }
/**
* Identify all the "dead" roots of this dead class.
*/
DeadRoot getADeadRoot() { result = getADeadRoot(getACallable()) }
/**
* Holds if this dead class is only used within the class itself.
*/
predicate isUnusedOutsideClass() {
// Accessed externally if any callable in the class has a possible liveness cause outside the
// class. Only one step is required.
not exists(Callable c |
c = possibleLivenessCause(getACallable()) and
not c = getACallable()
)
}
}
/**
* A class which is dead, but should be considered as live.
*
* This should be used for cases where the class is dead, but should not be removed - for example,
* because it may be useful in the future. If a class is marked as dead when it is live, the
* callable or field that makes the class live should be marked as an entry point by either
* extending `CallableEntryPoint` or `ReflectivelyReadField`, instead of whitelisting the class.
*/
abstract class WhitelistedLiveClass extends RefType { }
/**
* A method is dead if it is from source, has no liveness causes, is not a compiler generated
* method and is not a dead method with a purpose, such as a constructor designed to suppress the
* default constructor.
*/
class DeadMethod extends Callable {
DeadMethod() {
fromSource() and
not isLive(this) and
not this.(Constructor).isDefaultConstructor() and
// Ignore `SuppressedConstructor`s in `NamespaceClass`es. There is no reason to use a suppressed
// constructor in other cases.
not (
this instanceof SuppressedConstructor and this.getDeclaringType() instanceof NamespaceClass
) and
not (
this.(Method).isAbstract() and
exists(Method m | m.overridesOrInstantiates+(this.(Method)) | isLive(m))
) and
// A getter or setter associated with a live JPA field.
//
// These getters and setters are often generated in an ad-hoc way by the developer, which leads to
// methods that are theoretically dead, but uninteresting. We therefore ignore them, so long as
// they are "simple".
not exists(JPAReadField readField | this.getDeclaringType() = readField.getDeclaringType() |
this.(GetterMethod).getField() = readField or
this.(SetterMethod).getField() = readField
)
}
/**
* Holds if this dead method is already within the scope of a dead class.
*/
predicate isInDeadScope() {
// We do not need to consider whitelisting because whitelisted classes should not have dead
// methods reported.
this.getDeclaringType() instanceof DeadClass
}
/**
* Identify all the "dead" roots of this dead callable.
*/
DeadRoot getADeadRoot() { result = getADeadRoot(this) }
}
class RootdefCallable extends Callable {
RootdefCallable() {
this.fromSource() and
not this.(Method).overridesOrInstantiates(_)
}
Parameter unusedParameter() {
exists(int i | result = this.getParameter(i) |
not exists(result.getAnAccess()) and
not overrideAccess(this, i)
)
}
predicate whitelisted() {
// Main methods must have a `String[]` argument.
this instanceof MainMethod
or
// Premain methods must have certain arguments.
this instanceof PreMainMethod
or
// Abstract, native and interface methods obviously won't access their own
// parameters, so don't flag unless we can see an overriding method with
// a body that also doesn't.
not hasUsefulBody(this) and
not exists(Method m | hasUsefulBody(m) | m.overridesOrInstantiates+(this))
or
// Methods that are the target of a member reference need to implement
// the exact signature of the resulting functional interface.
exists(MemberRefExpr mre | mre.getReferencedCallable() = this)
or
this.getAnAnnotation() instanceof OverrideAnnotation
}
}
pragma[nomagic]
private predicate overrideAccess(Callable c, int i) {
exists(Method m | m.overridesOrInstantiates+(c) | exists(m.getParameter(i).getAnAccess()))
}
/**
* A predicate to find non-trivial method implementations.
* (A trivial implementation is either abstract, or it just
* throws `UnsupportedOperationException` or similar.)
*/
predicate hasUsefulBody(Callable c) {
exists(c.getBody()) and
not c.getBody().getAChild() instanceof ThrowStmt
}