Files
codeql/java/ql/lib/semmle/code/java/deadcode/DeadField.qll
2022-02-21 17:05:00 +00:00

172 lines
5.5 KiB
Plaintext

import java
import semmle.code.java.deadcode.DeadCode
import semmle.code.java.frameworks.javaee.Persistence
import semmle.code.java.frameworks.JAXB
import semmle.code.java.frameworks.jackson.JacksonSerializability
/**
* A field that is from a source file.
*
* This defines the set of fields for which we will determine liveness.
*/
library class SourceField extends Field {
SourceField() { this.fromSource() }
}
/**
* A field is dead if it is never read by a live callable and it is neither reflectively accessed,
* nor whitelisted.
*/
class DeadField extends SourceField {
DeadField() { not this instanceof LiveField }
/**
* Holds if this dead field is already within the scope of a dead class, or reported by a dead
* enum constant.
*/
predicate isInDeadScope() {
// `EnumConstant`s, and fields in dead classes, are reported in other queries.
this.getDeclaringType() instanceof DeadClass or
this instanceof EnumConstant
}
}
/**
* A field is live if it is read by a live callable, accessed by an annotation on a live element,
* reflectively read, or whitelisted as read.
*/
class LiveField extends SourceField {
LiveField() {
exists(FieldRead access | access = this.getAnAccess() |
isLive(access.getEnclosingCallable())
or
exists(Annotation a |
// This is an access used in an annotation, either directly, or within the expression.
a.getValue(_) = access.getParent*()
|
// The annotated element is a live callable.
isLive(a.getAnnotatedElement())
or
// The annotated element is in a live callable.
isLive(a.getAnnotatedElement().(LocalVariableDecl).getEnclosingCallable())
or
// The annotated element is a live field.
a.getAnnotatedElement() instanceof LiveField
or
// The annotated element is a live source class or interface.
// Note: We ignore annotation values on library classes, because they should only refer to
// fields in library classes, not `fromSource()` fields.
a.getAnnotatedElement() instanceof LiveClass
)
)
or
this instanceof ReflectivelyReadField
or
this instanceof WhitelistedLiveField
}
}
/**
* A field that may be read reflectively.
*/
abstract class ReflectivelyReadField extends Field { }
/**
* A field which is dead, but should be considered as live.
*
* This should be used for cases where the field is dead, but should not be removed - for example,
* because it may be useful in the future. If the field is live, but is not marked as a such, then
* either a new `EntryPoint` should be added, or, if the field is accessed reflectively, this should
* be identified by extending `ReflectivelyReadField` instead.
*
* Whitelisting a field will automatically cause the containing class to be considered as live.
*/
abstract class WhitelistedLiveField extends Field { }
/**
* A static, final, long field named `serialVersionUID` in a class that extends `Serializable` acts as
* a version number for the serialization framework.
*/
class SerialVersionUIDField extends ReflectivelyReadField {
SerialVersionUIDField() {
this.hasName("serialVersionUID") and
this.isStatic() and
this.isFinal() and
this.getType().hasName("long") and
this.getDeclaringType().getAnAncestor() instanceof TypeSerializable
}
}
/**
* A field is read by the JAXB during serialization if it is a JAXB bound field, and if the
* containing class is considered "live".
*/
class LiveJaxbBoundField extends ReflectivelyReadField, JaxbBoundField {
LiveJaxbBoundField() {
// If the class is considered live, it must have at least one live constructor.
exists(Constructor c | c = this.getDeclaringType().getAConstructor() | isLive(c))
}
}
/**
* A field with an annotation which implies that it will be read by `JUnit` when running tests
* within this class.
*/
class JUnitAnnotatedField extends ReflectivelyReadField {
JUnitAnnotatedField() {
this.hasAnnotation("org.junit.experimental.theories", "DataPoint") or
this.hasAnnotation("org.junit.experimental.theories", "DataPoints") or
this.hasAnnotation("org.junit.runners", "Parameterized$Parameter") or
this.hasAnnotation("org.junit", "Rule") or
this.hasAnnotation("org.junit", "ClassRule")
}
}
/**
* A field that is reflectively read via a call to `Class.getField(...)`.
*/
class ClassReflectivelyReadField extends ReflectivelyReadField {
ClassReflectivelyReadField() {
exists(ReflectiveFieldAccess fieldAccess | this = fieldAccess.inferAccessedField())
}
}
/**
* Consider all `JacksonSerializableField`s as reflectively read.
*/
class JacksonSerializableReflectivelyReadField extends ReflectivelyReadField,
JacksonSerializableField { }
/**
* A field that is used when applying Jackson mixins.
*/
class JacksonMixinReflextivelyReadField extends ReflectivelyReadField {
JacksonMixinReflextivelyReadField() {
exists(JacksonMixinType mixinType, JacksonAddMixinCall mixinCall |
this = mixinType.getAMixedInField() and
mixinType = mixinCall.getAMixedInType()
|
isLive(mixinCall.getEnclosingCallable())
)
}
}
/**
* A field which is read by a JPA compatible Java persistence framework.
*/
class JPAReadField extends ReflectivelyReadField {
JPAReadField() {
exists(PersistentEntity entity |
this = entity.getAField() and
(
entity.getAccessType() = "field" or
this.hasAnnotation("javax.persistence", "Access")
)
|
not this.hasAnnotation("javax.persistence", "Transient") and
not this.isStatic() and
not this.isFinal()
)
}
}