mirror of
https://github.com/github/codeql.git
synced 2026-02-01 07:42:57 +01:00
Packaging: Java refactoring
Split java pack into `codeql/java-all` and `codeql/java-queries`.
This commit is contained in:
317
java/ql/lib/semmle/code/java/deadcode/DeadCode.qll
Normal file
317
java/ql/lib/semmle/code/java/deadcode/DeadCode.qll
Normal file
@@ -0,0 +1,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().(BlockStmt).getNumStmt() = 1 and
|
||||
getBody().(BlockStmt).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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
78
java/ql/lib/semmle/code/java/deadcode/DeadEnumConstant.qll
Normal file
78
java/ql/lib/semmle/code/java/deadcode/DeadEnumConstant.qll
Normal file
@@ -0,0 +1,78 @@
|
||||
import java
|
||||
import semmle.code.java.JDKAnnotations
|
||||
|
||||
/**
|
||||
* Direct flow of values (i.e. object references) through expressions.
|
||||
*/
|
||||
Expr valueFlow(Expr src) {
|
||||
result = src
|
||||
or
|
||||
result.(ConditionalExpr).getABranchExpr() = src
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an access to an enum constant, where the reference to the constant may
|
||||
* be stored and used by the enclosing program rather than just being
|
||||
* compared and discarded.
|
||||
*/
|
||||
VarAccess valueAccess(EnumConstant e) {
|
||||
result = e.getAnAccess() and
|
||||
(
|
||||
exists(Call c |
|
||||
c.getAnArgument() = valueFlow+(result) or
|
||||
c.(MethodAccess).getQualifier() = valueFlow+(result)
|
||||
)
|
||||
or
|
||||
exists(Assignment a | a.getSource() = valueFlow+(result))
|
||||
or
|
||||
exists(ReturnStmt r | r.getResult() = valueFlow+(result))
|
||||
or
|
||||
exists(LocalVariableDeclExpr v | v.getInit() = valueFlow+(result))
|
||||
or
|
||||
exists(AddExpr a | a.getAnOperand() = valueFlow+(result))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exceptions to the "must have its value used" rule.
|
||||
*/
|
||||
predicate exception(EnumConstant e) {
|
||||
exists(EnumType t | t = e.getDeclaringType() |
|
||||
// It looks like a method is trying to return the right constant for a string.
|
||||
exists(Method fromString | fromString = t.getAMethod() |
|
||||
fromString.isStatic() and
|
||||
fromString.getReturnType() = t and
|
||||
exists(EnhancedForStmt s | s.getEnclosingCallable() = fromString |
|
||||
s.getVariable().getType() = t
|
||||
)
|
||||
)
|
||||
or
|
||||
// A method iterates over the values of an enum.
|
||||
exists(MethodAccess values | values.getMethod().getDeclaringType() = t |
|
||||
values.getParent() instanceof EnhancedForStmt or
|
||||
values.getParent().(MethodAccess).getMethod().hasName("findThisIn")
|
||||
)
|
||||
or
|
||||
// The `valueOf` method is called, meaning that depending on the string any constant
|
||||
// could be retrieved.
|
||||
exists(MethodAccess valueOf | valueOf.getMethod().getDeclaringType() = t |
|
||||
valueOf.getMethod().hasName("valueOf")
|
||||
)
|
||||
or
|
||||
// Entire `Enum` annotated with reflective annotation.
|
||||
exists(ReflectiveAccessAnnotation ann | ann = t.getAnAnnotation())
|
||||
)
|
||||
or
|
||||
// Enum field annotated with reflective annotation.
|
||||
e.getAnAnnotation() instanceof ReflectiveAccessAnnotation
|
||||
}
|
||||
|
||||
class UnusedEnumConstant extends EnumConstant {
|
||||
UnusedEnumConstant() {
|
||||
not exists(valueAccess(this)) and
|
||||
this.fromSource() and
|
||||
not exception(this)
|
||||
}
|
||||
|
||||
predicate whitelisted() { none() }
|
||||
}
|
||||
171
java/ql/lib/semmle/code/java/deadcode/DeadField.qll
Normal file
171
java/ql/lib/semmle/code/java/deadcode/DeadField.qll
Normal file
@@ -0,0 +1,171 @@
|
||||
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() { 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.
|
||||
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 = 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() {
|
||||
hasName("serialVersionUID") and
|
||||
isStatic() and
|
||||
isFinal() and
|
||||
getType().hasName("long") and
|
||||
getDeclaringType().getASupertype*() 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 = 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() {
|
||||
hasAnnotation("org.junit.experimental.theories", "DataPoint") or
|
||||
hasAnnotation("org.junit.experimental.theories", "DataPoints") or
|
||||
hasAnnotation("org.junit.runners", "Parameterized$Parameter") or
|
||||
hasAnnotation("org.junit", "Rule") or
|
||||
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 isStatic() and
|
||||
not isFinal()
|
||||
)
|
||||
}
|
||||
}
|
||||
452
java/ql/lib/semmle/code/java/deadcode/EntryPoints.qll
Normal file
452
java/ql/lib/semmle/code/java/deadcode/EntryPoints.qll
Normal file
@@ -0,0 +1,452 @@
|
||||
import java
|
||||
import semmle.code.java.deadcode.DeadCode
|
||||
import semmle.code.java.deadcode.frameworks.CamelEntryPoints
|
||||
import semmle.code.java.deadcode.frameworks.GigaSpacesXAPEntryPoints
|
||||
import semmle.code.java.deadcode.SpringEntryPoints
|
||||
import semmle.code.java.deadcode.StrutsEntryPoints
|
||||
import semmle.code.java.deadcode.TestEntryPoints
|
||||
import semmle.code.java.deadcode.WebEntryPoints
|
||||
import semmle.code.java.frameworks.javaee.JavaServerFaces
|
||||
import semmle.code.java.frameworks.JAXB
|
||||
import semmle.code.java.frameworks.JaxWS
|
||||
import semmle.code.java.JMX
|
||||
import semmle.code.java.Reflection
|
||||
import semmle.code.java.frameworks.JavaxAnnotations
|
||||
import semmle.code.java.frameworks.Selenium
|
||||
|
||||
/**
|
||||
* An entry point into our system, marking some number of `Callable`s
|
||||
* as live.
|
||||
*/
|
||||
abstract class EntryPoint extends Top {
|
||||
/**
|
||||
* One of the `Callable`s associated with this entry point.
|
||||
*/
|
||||
abstract Callable getALiveCallable();
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry point corresponding to a single method or constructor.
|
||||
*/
|
||||
abstract class CallableEntryPoint extends EntryPoint, Callable {
|
||||
override Callable getALiveCallable() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry point that is a single method, that is only live if there is a live constructor on the
|
||||
* class.
|
||||
*/
|
||||
abstract class CallableEntryPointOnConstructedClass extends EntryPoint {
|
||||
CallableEntryPointOnConstructedClass() {
|
||||
exists(Constructor c | c = this.(Callable).getDeclaringType().getAConstructor() and isLive(c))
|
||||
}
|
||||
|
||||
override Callable getALiveCallable() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable which is dead, but should be considered as live.
|
||||
*
|
||||
* This should be used for cases where the callable is dead, but should not be removed - for
|
||||
* example, because it may be useful in the future. If the callable is live, but is not marked as a
|
||||
* such, then a new `CallableEntryPoint` should be added instead.
|
||||
*
|
||||
* Whitelisting a callable will automatically cause the containing class to be considered as live.
|
||||
*/
|
||||
abstract class WhitelistedLiveCallable extends CallableEntryPoint { }
|
||||
|
||||
/**
|
||||
* A `public static void main(String[] args)` method.
|
||||
*/
|
||||
class MainMethodEntry extends CallableEntryPoint {
|
||||
MainMethodEntry() { this instanceof MainMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that overrides a library method -- the result is
|
||||
* that when the library calls the overridden method, it may
|
||||
* instead call this method, which makes it live even if we
|
||||
* don't directly see the call.
|
||||
*/
|
||||
class LibOverrideMethodEntry extends CallableEntryPoint {
|
||||
LibOverrideMethodEntry() {
|
||||
this.fromSource() and
|
||||
exists(Method libraryMethod | this.(Method).overrides*(libraryMethod) |
|
||||
// The library method must not come from source, either directly, or added automatically.
|
||||
// For example, `values()` and `valueOf(...)` methods are not `fromSource()`, but are added
|
||||
// automatically to source types.
|
||||
not libraryMethod.getDeclaringType().getSourceDeclaration().fromSource()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that may be constructed reflectively, making its default constructor live.
|
||||
*/
|
||||
abstract class ReflectivelyConstructedClass extends EntryPoint, Class {
|
||||
/**
|
||||
* Reflectively constructed classes have a live default constructor.
|
||||
*/
|
||||
override Callable getALiveCallable() {
|
||||
result = this.getAConstructor() and
|
||||
result.getNumberOfParameters() = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes that are deserialized by Jackson are reflectively constructed.
|
||||
*/
|
||||
library class JacksonReflectivelyConstructedClass extends ReflectivelyConstructedClass {
|
||||
JacksonReflectivelyConstructedClass() { this instanceof JacksonDeserializableType }
|
||||
|
||||
override Callable getALiveCallable() {
|
||||
// Constructors may be called by Jackson, if they are a no-arg, they have a suitable annotation,
|
||||
// or inherit a suitable annotation through a mixin.
|
||||
result = getAConstructor() and
|
||||
(
|
||||
result.getNumberOfParameters() = 0 or
|
||||
result.getAnAnnotation() instanceof JacksonAnnotation or
|
||||
result.getAParameter().getAnAnnotation() instanceof JacksonAnnotation or
|
||||
exists(JacksonMixedInCallable mixinCallable | result = mixinCallable.getATargetCallable())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable that is used when applying Jackson mixins.
|
||||
*/
|
||||
class JacksonMixinCallableEntryPoint extends EntryPoint {
|
||||
JacksonMixinCallableEntryPoint() {
|
||||
exists(JacksonMixinType mixinType, JacksonAddMixinCall mixinCall |
|
||||
this = mixinType.getAMixedInCallable() and
|
||||
mixinType = mixinCall.getAMixedInType()
|
||||
|
|
||||
isLive(mixinCall.getEnclosingCallable())
|
||||
)
|
||||
}
|
||||
|
||||
override Callable getALiveCallable() { result = this }
|
||||
}
|
||||
|
||||
class JAXAnnotationReflectivelyConstructedClass extends ReflectivelyConstructedClass {
|
||||
JAXAnnotationReflectivelyConstructedClass() {
|
||||
this instanceof JaxWsEndpoint or
|
||||
this instanceof JaxbXmlRegistry or
|
||||
this instanceof JaxRsResourceClass or
|
||||
this instanceof JaxbXmlEnum
|
||||
}
|
||||
}
|
||||
|
||||
class DeserializedClass extends ReflectivelyConstructedClass {
|
||||
DeserializedClass() {
|
||||
exists(CastExpr cast, ReadObjectMethod readObject |
|
||||
cast.getExpr().(MethodAccess).getMethod() = readObject
|
||||
|
|
||||
hasSubtype*(cast.getType(), this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Class.newInstance()` or `Constructor.newInstance()` which may imply that a number of
|
||||
* constructors are live.
|
||||
*/
|
||||
class NewInstanceCall extends EntryPoint, NewInstance {
|
||||
override Constructor getALiveCallable() {
|
||||
result = getInferredConstructor() and
|
||||
// The `newInstance(...)` call must be used in a live context.
|
||||
isLive(this.getEnclosingCallable())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to either `Class.getMethod(...)` or `Class.getDeclaredMethod(...)`.
|
||||
*/
|
||||
class ReflectiveMethodAccessEntryPoint extends EntryPoint, ReflectiveMethodAccess {
|
||||
override Method getALiveCallable() {
|
||||
result = inferAccessedMethod() and
|
||||
// The `getMethod(...)` call must be used in a live context.
|
||||
isLive(this.getEnclosingCallable())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes that are entry points recognised by annotations.
|
||||
*/
|
||||
abstract class AnnotationEntryPoint extends EntryPoint, Class {
|
||||
/**
|
||||
* By default assume all public methods might be called, but not
|
||||
* constructors -- be sure to register any further subtypes with
|
||||
* `ReflectivelyConstructedClass`.
|
||||
*/
|
||||
override Callable getALiveCallable() {
|
||||
result = this.getAMethod() and
|
||||
result.isPublic()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A JAXB XML registry, used reflectively to construct objects based on
|
||||
* the contents of XML files.
|
||||
*/
|
||||
class JaxbXmlRegistry extends AnnotationEntryPoint {
|
||||
JaxbXmlRegistry() { this.(JaxbAnnotated).hasJaxbAnnotation("XmlRegistry") }
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum annotated with `@XmlEnum` can be used by JAXB when constructing objects reflectively based
|
||||
* on the contents of XML files. Unlike classes, these are never referred to from the `@XmlRegistry`
|
||||
* class, because they do not need to be instantiated, just used. We therefore need to special case
|
||||
* them.
|
||||
*/
|
||||
class JaxbXmlEnum extends AnnotationEntryPoint {
|
||||
JaxbXmlEnum() { this.(JaxbAnnotated).hasJaxbAnnotation("XmlEnum") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A type annotated with `@XmlType`, indicating that this class is used when marshalling or
|
||||
* unmarshalling XML documents.
|
||||
*/
|
||||
class JaxbXmlType extends AnnotationEntryPoint, JaxbType {
|
||||
override Callable getALiveCallable() {
|
||||
// Must have a live no-arg constructor for JAXB to perform marshal/unmarshal.
|
||||
exists(Constructor c | c = getAConstructor() and c.getNumberOfParameters() = 0 | isLive(c)) and
|
||||
result = getACallable() and
|
||||
(
|
||||
// A bound getter or setter.
|
||||
result instanceof JaxbBoundGetterSetter
|
||||
or
|
||||
// Methods called by reflection when unmarshalling or marshalling.
|
||||
result.hasName("afterUnmarshal") and result.paramsString() = "(Unmarshaller, Object)"
|
||||
or
|
||||
result.hasName("beforeUnmarshal") and result.paramsString() = "(Unmarshaller, Object)"
|
||||
or
|
||||
result.hasName("afterMarshal") and result.paramsString() = "(Marshaller, Object)"
|
||||
or
|
||||
result.hasName("beforeMarshal") and result.paramsString() = "(Marshaller, Object)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A JAX WS endpoint is constructed by the container, and its methods
|
||||
* are -- where annotated -- called remotely.
|
||||
*/
|
||||
class JaxWsEndpointEntry extends JaxWsEndpoint, AnnotationEntryPoint {
|
||||
override Callable getALiveCallable() { result = this.getARemoteMethod() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A JAX RS resource class. `@GET` and `@POST` annotated methods are reflectively called by the container. The
|
||||
* class itself may be reflectively constructed by the container.
|
||||
*/
|
||||
class JaxRsResourceClassEntry extends JaxRsResourceClass, AnnotationEntryPoint {
|
||||
override Callable getALiveCallable() { result = this.getAnInjectableCallable() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor that may be called when injecting values into a JaxRS resource class constructor or method.
|
||||
*/
|
||||
class JaxRsBeanParamConstructorEntryPoint extends JaxRsBeanParamConstructor, CallableEntryPoint { }
|
||||
|
||||
/**
|
||||
* Entry point for methods that can be accessed through JMX.
|
||||
*
|
||||
* The instance here is a `ManagedBean` (`MBean` or `MXBean`) implementation class, that is seen to be
|
||||
* registered with the `MBeanServer`, directly or indirectly. The live callables are all the
|
||||
* methods in this class that override something declared in one or more of the managed beans
|
||||
* that this class implements.
|
||||
*/
|
||||
class ManagedBeanImplEntryPoint extends EntryPoint, RegisteredManagedBeanImpl {
|
||||
override Method getALiveCallable() {
|
||||
// Find the method that will be called for each method on each managed bean that this class
|
||||
// implements.
|
||||
this.inherits(result) and
|
||||
result.(Method).overrides(getAnImplementedManagedBean().getAMethod())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for bean classes. Should be extended to define any
|
||||
* project specific types of bean.
|
||||
*/
|
||||
abstract class BeanClass extends EntryPoint, Class {
|
||||
override Callable getALiveCallable() {
|
||||
result = this.getACallable() and
|
||||
(result.(Method).isPublic() or result.(Constructor).getNumberOfParameters() = 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for J2EE beans (`EnterpriseBean`, `EntityBean`, `MessageBean`, `SessionBean`).
|
||||
*/
|
||||
class J2EEBean extends BeanClass {
|
||||
J2EEBean() {
|
||||
this instanceof EnterpriseBean or
|
||||
this instanceof EntityBean or
|
||||
this instanceof MessageBean or
|
||||
this instanceof SessionBean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for Java Server Faces `ManagedBean`s.
|
||||
*/
|
||||
class FacesManagedBeanEntryPoint extends BeanClass, FacesManagedBean { }
|
||||
|
||||
/**
|
||||
* Entry point for methods that may be called by Java Server Faces.
|
||||
*/
|
||||
class FacesAccessibleMethodEntryPoint extends CallableEntryPoint {
|
||||
FacesAccessibleMethodEntryPoint() {
|
||||
exists(FacesAccessibleType accessibleType | this = accessibleType.getAnAccessibleMethod())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Java Server Faces custom component, that is reflectively constructed by the framework when
|
||||
* used in a view (JSP or facelet).
|
||||
*/
|
||||
class FacesComponentReflectivelyConstructedClass extends ReflectivelyConstructedClass {
|
||||
FacesComponentReflectivelyConstructedClass() { this instanceof FacesComponent }
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for EJB home interfaces.
|
||||
*/
|
||||
class EJBHome extends Interface, EntryPoint {
|
||||
EJBHome() { this.getASupertype*().hasQualifiedName("javax.ejb", "EJBHome") }
|
||||
|
||||
override Callable getALiveCallable() { result = this.getACallable() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for EJB object interfaces.
|
||||
*/
|
||||
class EJBObject extends Interface, EntryPoint {
|
||||
EJBObject() { this.getASupertype*().hasQualifiedName("javax.ejb", "EJBObject") }
|
||||
|
||||
override Callable getALiveCallable() { result = this.getACallable() }
|
||||
}
|
||||
|
||||
class GsonDeserializationEntryPoint extends ReflectivelyConstructedClass {
|
||||
GsonDeserializationEntryPoint() {
|
||||
// Assume any class with a gson annotated field can be deserialized.
|
||||
this.getAField().getAnAnnotation().getType().hasQualifiedName("com.google.gson.annotations", _)
|
||||
}
|
||||
}
|
||||
|
||||
class JAXBDeserializationEntryPoint extends ReflectivelyConstructedClass {
|
||||
JAXBDeserializationEntryPoint() {
|
||||
// A class can be deserialized by JAXB if it's an `XmlRootElement`...
|
||||
this.getAnAnnotation().getType().hasQualifiedName("javax.xml.bind.annotation", "XmlRootElement")
|
||||
or
|
||||
// ... or the type of an `XmlElement` field.
|
||||
exists(Field elementField |
|
||||
elementField.getAnAnnotation().getType() instanceof JaxbMemberAnnotation
|
||||
|
|
||||
usesType(elementField.getType(), this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `javax.annotation` for a method that is called after or before dependency injection on a type.
|
||||
*
|
||||
* Consider this to be live if and only if there is a live constructor.
|
||||
*/
|
||||
class PreOrPostDIMethod extends CallableEntryPointOnConstructedClass {
|
||||
PreOrPostDIMethod() {
|
||||
this.(Method).getAnAnnotation() instanceof PostConstructAnnotation or
|
||||
this.(Method).getAnAnnotation() instanceof PreDestroyAnnotation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `javax.annotation` for a method that is called to inject a resource into the class.
|
||||
*
|
||||
* Consider this to be live if and only if there is a live constructor.
|
||||
*/
|
||||
class JavaxResourceAnnotatedMethod extends CallableEntryPointOnConstructedClass {
|
||||
JavaxResourceAnnotatedMethod() { this.(Method).getAnAnnotation() instanceof ResourceAnnotation }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `javax.annotation.ManagedBean` annotated class, which may be constructed by a container of some
|
||||
* description.
|
||||
*/
|
||||
class JavaxManagedBeanReflectivelyConstructed extends ReflectivelyConstructedClass {
|
||||
JavaxManagedBeanReflectivelyConstructed() {
|
||||
getAnAnnotation() instanceof JavaxManagedBeanAnnotation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes marked as Java persistence entities can be reflectively constructed when the data is
|
||||
* loaded.
|
||||
*/
|
||||
class PersistentEntityEntryPoint extends ReflectivelyConstructedClass {
|
||||
PersistentEntityEntryPoint() { this instanceof PersistentEntity }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method (getter or setter) called on a persistent entity class by the persistence framework.
|
||||
*/
|
||||
class PersistencePropertyMethod extends CallableEntryPoint {
|
||||
PersistencePropertyMethod() {
|
||||
exists(PersistentEntity e |
|
||||
this = e.getACallable() and
|
||||
(
|
||||
e.getAccessType() = "property" or
|
||||
this.hasAnnotation("javax.persistence", "Access")
|
||||
) and
|
||||
(
|
||||
this.getName().matches("get%") or
|
||||
this.getName().matches("set%")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods that are registered by annotations as callbacks for certain Java persistence events.
|
||||
*/
|
||||
class PersistenceCallbackMethod extends CallableEntryPoint {
|
||||
PersistenceCallbackMethod() {
|
||||
getAnAnnotation() instanceof PrePersistAnnotation or
|
||||
getAnAnnotation() instanceof PreRemoveAnnotation or
|
||||
getAnAnnotation() instanceof PreUpdateAnnotation or
|
||||
getAnAnnotation() instanceof PostPersistAnnotation or
|
||||
getAnAnnotation() instanceof PostRemoveAnnotation or
|
||||
getAnAnnotation() instanceof PostUpdateAnnotation or
|
||||
getAnAnnotation() instanceof PostLoadAnnotation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A source class which is referred to by fully qualified name in the value of an arbitrary XML
|
||||
* attribute which has a name containing "className" or "ClassName".
|
||||
*/
|
||||
class ArbitraryXMLEntryPoint extends ReflectivelyConstructedClass {
|
||||
ArbitraryXMLEntryPoint() {
|
||||
fromSource() and
|
||||
exists(XMLAttribute attribute |
|
||||
attribute.getName() = "className" or
|
||||
attribute.getName().matches("%ClassName") or
|
||||
attribute.getName() = "class" or
|
||||
attribute.getName().matches("%Class")
|
||||
|
|
||||
attribute.getValue() = getQualifiedName()
|
||||
)
|
||||
}
|
||||
|
||||
override Callable getALiveCallable() {
|
||||
// Any constructor on these classes, as we don't know which may be called.
|
||||
result = getAConstructor()
|
||||
}
|
||||
}
|
||||
|
||||
/** A Selenium PageObject, created by a call to PageFactory.initElements(..). */
|
||||
class SeleniumPageObjectEntryPoint extends ReflectivelyConstructedClass {
|
||||
SeleniumPageObjectEntryPoint() { this instanceof SeleniumPageObject }
|
||||
}
|
||||
128
java/ql/lib/semmle/code/java/deadcode/SpringEntryPoints.qll
Normal file
128
java/ql/lib/semmle/code/java/deadcode/SpringEntryPoints.qll
Normal file
@@ -0,0 +1,128 @@
|
||||
import java
|
||||
import semmle.code.java.deadcode.DeadCode
|
||||
import semmle.code.java.frameworks.spring.Spring
|
||||
|
||||
/**
|
||||
* A method called by Spring to construct a Spring class, or inject a parameter into a Spring class.
|
||||
*/
|
||||
class SpringInjectionCallableEntryPoint extends CallableEntryPoint {
|
||||
SpringInjectionCallableEntryPoint() {
|
||||
// The constructor of a Spring component, constructed by the container in response to context scanning.
|
||||
this instanceof SpringComponentConstructor or
|
||||
// The constructor of a Spring bean, constructed by the container.
|
||||
this instanceof SpringBeanReflectivelyConstructed or
|
||||
// A setter method specified in the context.
|
||||
this instanceof SpringBeanPropertySetterMethod or
|
||||
exists(this.(SpringBeanXMLAutowiredSetterMethod).getInjectedBean()) or
|
||||
this instanceof SpringBeanAutowiredCallable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method called by Spring when a bean is initialized or destroyed.
|
||||
*/
|
||||
class SpringBeanInitDestroyMethod extends CallableEntryPoint {
|
||||
SpringBeanInitDestroyMethod() {
|
||||
exists(SpringBean bean |
|
||||
this = bean.getInitMethod() or
|
||||
this = bean.getDestroyMethod()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory method called to construct an instance of a bean.
|
||||
*/
|
||||
class SpringFactoryMethod extends CallableEntryPoint {
|
||||
SpringFactoryMethod() { exists(SpringBean bean | this = bean.getFactoryMethod()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a Spring bean.
|
||||
*/
|
||||
class SpringBeanAnnotatedMethod extends CallableEntryPoint {
|
||||
SpringBeanAnnotatedMethod() {
|
||||
hasAnnotation("org.springframework.context.annotation", "Bean") and
|
||||
getDeclaringType().(SpringComponent).isLive()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A live entry point within a Spring controller.
|
||||
*/
|
||||
class SpringControllerEntryPoint extends CallableEntryPoint {
|
||||
SpringControllerEntryPoint() { this instanceof SpringControllerMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that is accessible in a response, because it is part of the returned model,
|
||||
* for example when rendering a JSP page.
|
||||
*/
|
||||
class SpringResponseAccessibleMethod extends CallableEntryPoint {
|
||||
SpringResponseAccessibleMethod() {
|
||||
// Must be on a type used in a Model response.
|
||||
getDeclaringType() instanceof SpringModelResponseType and
|
||||
// Must be public.
|
||||
isPublic()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Spring "managed resource" is a JMX bean, where only methods annotated with `@ManagedAttribute`
|
||||
* or `@ManagedOperation` are exposed.
|
||||
*/
|
||||
class SpringManagedResource extends CallableEntryPoint {
|
||||
SpringManagedResource() {
|
||||
(
|
||||
hasAnnotation("org.springframework.jmx.export.annotation", "ManagedAttribute") or
|
||||
hasAnnotation("org.springframework.jmx.export.annotation", "ManagedOperation")
|
||||
) and
|
||||
getDeclaringType().hasAnnotation("org.springframework.jmx.export.annotation", "ManagedResource")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring allows persistence entities to have constructors other than the default constructor.
|
||||
*/
|
||||
class SpringPersistenceConstructor extends CallableEntryPoint {
|
||||
SpringPersistenceConstructor() {
|
||||
hasAnnotation("org.springframework.data.annotation", "PersistenceConstructor") and
|
||||
getDeclaringType() instanceof PersistentEntity
|
||||
}
|
||||
}
|
||||
|
||||
class SpringAspect extends CallableEntryPoint {
|
||||
SpringAspect() {
|
||||
(
|
||||
hasAnnotation("org.aspectj.lang.annotation", "Around") or
|
||||
hasAnnotation("org.aspectj.lang.annotation", "Before")
|
||||
) and
|
||||
getDeclaringType().hasAnnotation("org.aspectj.lang.annotation", "Aspect")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring Shell provides annotations for identifying methods that contribute CLI commands.
|
||||
*/
|
||||
class SpringCLI extends CallableEntryPoint {
|
||||
SpringCLI() {
|
||||
(
|
||||
hasAnnotation("org.springframework.shell.core.annotation", "CliCommand") or
|
||||
hasAnnotation("org.springframework.shell.core.annotation", "CliAvailabilityIndicator")
|
||||
) and
|
||||
getDeclaringType()
|
||||
.getAnAncestor()
|
||||
.hasQualifiedName("org.springframework.shell.core", "CommandMarker")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry point which acts as a remote API for a Flex application to access a Spring application.
|
||||
*/
|
||||
class SpringFlexEntryPoint extends CallableEntryPoint {
|
||||
SpringFlexEntryPoint() {
|
||||
exists(SpringRemotingDestinationClass remotingDestination |
|
||||
this = remotingDestination.getARemotingMethod()
|
||||
)
|
||||
}
|
||||
}
|
||||
90
java/ql/lib/semmle/code/java/deadcode/StrutsEntryPoints.qll
Normal file
90
java/ql/lib/semmle/code/java/deadcode/StrutsEntryPoints.qll
Normal file
@@ -0,0 +1,90 @@
|
||||
import java
|
||||
import semmle.code.java.deadcode.DeadCode
|
||||
import semmle.code.java.frameworks.struts.StrutsActions
|
||||
|
||||
/**
|
||||
* Entry point for apache struts 1.x actions. All methods declared in
|
||||
* `org.apache.struts.action.Action` + the default constructor are assumed
|
||||
* to be live. If this is a `DispatchAction` then all public methods are
|
||||
* live.
|
||||
*/
|
||||
class Struts1ActionEntryPoint extends EntryPoint, Class {
|
||||
Struts1ActionEntryPoint() {
|
||||
this.getASupertype*().hasQualifiedName("org.apache.struts.action", "Action")
|
||||
}
|
||||
|
||||
override Callable getALiveCallable() {
|
||||
result = this.getACallable() and
|
||||
(
|
||||
exists(Method methodFromAction |
|
||||
methodFromAction.getDeclaringType().hasQualifiedName("org.apache.struts.action", "Action")
|
||||
|
|
||||
result.(Method).overrides(methodFromAction)
|
||||
)
|
||||
or
|
||||
this.getASupertype*().hasQualifiedName("org.apache.struts.actions", "DispatchAction") and
|
||||
result.(Method).isPublic()
|
||||
or
|
||||
result.(Constructor).getNumberOfParameters() = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A struts 2 action class that is reflectively constructed.
|
||||
*/
|
||||
class Struts2ReflectivelyConstructedAction extends ReflectivelyConstructedClass {
|
||||
Struts2ReflectivelyConstructedAction() { this instanceof Struts2ActionClass }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method called on a struts 2 action class when the action is activated.
|
||||
*/
|
||||
class Struts2ActionMethodEntryPoint extends CallableEntryPoint {
|
||||
Struts2ActionMethodEntryPoint() { this instanceof Struts2ActionMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method called on a struts 2 action class before an action is activated.
|
||||
*/
|
||||
class Struts2PrepareMethodEntryPoint extends CallableEntryPoint {
|
||||
Struts2PrepareMethodEntryPoint() { this instanceof Struts2PrepareMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class which is accessible - directly or indirectly - from a struts action.
|
||||
*/
|
||||
class ActionAccessibleClass extends Class {
|
||||
ActionAccessibleClass() {
|
||||
// A struts action class is directly accessible.
|
||||
this instanceof Struts2ActionClass or
|
||||
this instanceof Struts1ActionEntryPoint or
|
||||
// Any class returned by a struts action is accessible within the JSP.
|
||||
exists(ActionAccessibleClass actionAccessibleClass |
|
||||
usesType(actionAccessibleClass.getAGetter().getReturnType(), this)
|
||||
)
|
||||
}
|
||||
|
||||
Method getAGetter() {
|
||||
result = this.getAMethod() and
|
||||
result.getName().matches("get%")
|
||||
}
|
||||
|
||||
Method getASetter() {
|
||||
result = this.getAMethod() and
|
||||
result.getName().matches("set%")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Struts getter or setter method is considered to be live, because it can be accessed within
|
||||
* JSP files, for which we have no information.
|
||||
*/
|
||||
class StrutsGetterSetter extends CallableEntryPoint {
|
||||
StrutsGetterSetter() {
|
||||
exists(ActionAccessibleClass actionAccessibleClass |
|
||||
this = actionAccessibleClass.getAGetter() or
|
||||
this = actionAccessibleClass.getASetter()
|
||||
)
|
||||
}
|
||||
}
|
||||
164
java/ql/lib/semmle/code/java/deadcode/TestEntryPoints.qll
Normal file
164
java/ql/lib/semmle/code/java/deadcode/TestEntryPoints.qll
Normal file
@@ -0,0 +1,164 @@
|
||||
import java
|
||||
import semmle.code.java.deadcode.DeadCode
|
||||
import semmle.code.java.frameworks.Cucumber
|
||||
import semmle.code.java.deadcode.frameworks.FitNesseEntryPoints
|
||||
import semmle.code.java.frameworks.Mockito
|
||||
import semmle.code.java.UnitTests
|
||||
|
||||
/**
|
||||
* A test method, suite, or an associated setup/teardown method.
|
||||
*/
|
||||
class TestMethodEntry extends CallableEntryPoint {
|
||||
TestMethodEntry() {
|
||||
this instanceof TestMethod and
|
||||
// Ignored tests are not run
|
||||
not this instanceof JUnitIgnoredMethod
|
||||
or
|
||||
this instanceof JUnit3TestSuite
|
||||
or
|
||||
exists(AnnotationType a | a = this.getAnAnnotation().getType() |
|
||||
a.hasQualifiedName("org.junit.runners", "Parameterized$Parameters") and
|
||||
getDeclaringType() instanceof ParameterizedJUnitTest
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods that are called before or after tests.
|
||||
*/
|
||||
class BeforeOrAfterEntry extends CallableEntryPoint {
|
||||
BeforeOrAfterEntry() {
|
||||
getAnAnnotation() instanceof TestNGBeforeAnnotation or
|
||||
getAnAnnotation() instanceof TestNGAfterAnnotation or
|
||||
getAnAnnotation() instanceof BeforeAnnotation or
|
||||
getAnAnnotation() instanceof BeforeClassAnnotation or
|
||||
getAnAnnotation() instanceof AfterAnnotation or
|
||||
getAnAnnotation() instanceof AfterClassAnnotation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method in a test class that is either a JUnit theory, or a method providing data points for a theory.
|
||||
*/
|
||||
class JUnitTheories extends CallableEntryPoint {
|
||||
JUnitTheories() {
|
||||
exists(AnnotationType a |
|
||||
a = this.getAnAnnotation().getType() and
|
||||
getDeclaringType() instanceof JUnitTheoryTest
|
||||
|
|
||||
a.hasQualifiedName("org.junit.experimental.theories", "Theory") or
|
||||
a.hasQualifiedName("org.junit.experimental.theories", "DataPoint") or
|
||||
a.hasQualifiedName("org.junit.experimental.theories", "DataPoints")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A field which provides a JUnit `DataPoint` for a theory.
|
||||
*/
|
||||
class JUnitDataPointField extends ReflectivelyReadField {
|
||||
JUnitDataPointField() {
|
||||
exists(AnnotationType a | a = this.getAnAnnotation().getType() |
|
||||
(
|
||||
a.hasQualifiedName("org.junit.experimental.theories", "DataPoint") or
|
||||
a.hasQualifiedName("org.junit.experimental.theories", "DataPoints")
|
||||
) and
|
||||
getDeclaringType() instanceof JUnitTheoryTest
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Any types used as a category in a JUnit `@Category` annotation should be considered live.
|
||||
*/
|
||||
class JUnitCategory extends WhitelistedLiveClass {
|
||||
JUnitCategory() { exists(JUnitCategoryAnnotation ca | ca.getACategory() = this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener that will be reflectively constructed by TestNG.
|
||||
*/
|
||||
class TestNGReflectivelyConstructedListener extends ReflectivelyConstructedClass {
|
||||
TestNGReflectivelyConstructedListener() {
|
||||
// Consider any class that implements a TestNG listener interface to be live. Listeners can be
|
||||
// specified on the command line, in `testng.xml` files and in Ant build files, so it is safest
|
||||
// to assume that all such listeners are live.
|
||||
this instanceof TestNGListenerImpl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `@DataProvider` TestNG method which is live because it is accessed by at least one test.
|
||||
*/
|
||||
class TestNGDataProvidersEntryPoint extends CallableEntryPoint {
|
||||
TestNGDataProvidersEntryPoint() {
|
||||
exists(TestNGTestMethod method | this = method.getADataProvider())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `@Factory` TestNG method or constructor which is live.
|
||||
*/
|
||||
class TestNGFactoryEntryPoint extends CallableEntryPoint {
|
||||
TestNGFactoryEntryPoint() { this instanceof TestNGFactoryCallable }
|
||||
}
|
||||
|
||||
class TestRefectivelyConstructedClass extends ReflectivelyConstructedClass {
|
||||
TestRefectivelyConstructedClass() {
|
||||
this.getAnAncestor().getACallable() instanceof TestMethodEntry
|
||||
}
|
||||
}
|
||||
|
||||
class RunWithReflectivelyConstructedClass extends ReflectivelyConstructedClass {
|
||||
RunWithReflectivelyConstructedClass() {
|
||||
exists(AnnotationType a | a = this.getAnAnnotation().getType() |
|
||||
a.hasQualifiedName("org.junit.runner", "RunWith")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callables called by Mockito when performing injection.
|
||||
*/
|
||||
class MockitoCalledByInjection extends CallableEntryPoint {
|
||||
MockitoCalledByInjection() {
|
||||
exists(MockitoInjectedField field | this = field.getAnInvokedCallable())
|
||||
or
|
||||
exists(MockitoSpiedField spyField |
|
||||
spyField.isConstructed() and
|
||||
this = spyField.getType().(RefType).getAConstructor() and
|
||||
this.getNumberOfParameters() = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock fields that are read by Mockito when performing injection.
|
||||
*/
|
||||
class MockitoReadField extends ReflectivelyReadField {
|
||||
MockitoReadField() { this.(MockitoMockedField).isReferencedByInjection() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class constructed by Cucumber.
|
||||
*/
|
||||
class CucumberConstructedClass extends ReflectivelyConstructedClass {
|
||||
CucumberConstructedClass() {
|
||||
this instanceof CucumberStepDefinitionClass or
|
||||
this.getAnAncestor() instanceof CucumberJava8Language
|
||||
}
|
||||
|
||||
override Callable getALiveCallable() {
|
||||
// Consider any constructor to be live - Cucumber calls a runtime-specified dependency
|
||||
// injection framework (possibly an in-built one) to construct these instances, so any
|
||||
// constructor could be called.
|
||||
result = getAConstructor()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A "step definition" that may be called by Cucumber when executing an acceptance test.
|
||||
*/
|
||||
class CucumberStepDefinitionEntryPoint extends CallableEntryPoint {
|
||||
CucumberStepDefinitionEntryPoint() { this instanceof CucumberStepDefinition }
|
||||
}
|
||||
111
java/ql/lib/semmle/code/java/deadcode/WebEntryPoints.qll
Normal file
111
java/ql/lib/semmle/code/java/deadcode/WebEntryPoints.qll
Normal file
@@ -0,0 +1,111 @@
|
||||
import java
|
||||
import semmle.code.java.deadcode.DeadCode
|
||||
import semmle.code.java.frameworks.gwt.GWT
|
||||
import semmle.code.java.frameworks.Servlets
|
||||
|
||||
/**
|
||||
* Any class which extends the `Servlet` interface is intended to be constructed reflectively by a
|
||||
* servlet container.
|
||||
*/
|
||||
class ServletConstructedClass extends ReflectivelyConstructedClass {
|
||||
ServletConstructedClass() {
|
||||
this instanceof ServletClass and
|
||||
// If we have seen any `web.xml` files, this servlet will be considered to be live only if it is
|
||||
// referred to as a servlet-class in at least one. If no `web.xml` files are found, we assume
|
||||
// that XML extraction was not enabled, and therefore consider all `Servlet` classes as live.
|
||||
(
|
||||
isWebXMLIncluded()
|
||||
implies
|
||||
exists(WebServletClass servletClass | this = servletClass.getClass())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A "Servlet listener" is a class that is intended to be constructed reflectively by a servlet
|
||||
* container based upon a `<listener>` tag in the `web.xml` file.
|
||||
*
|
||||
* Servlet listeners extend one of a number of listener classes.
|
||||
*/
|
||||
class ServletListenerClass extends ReflectivelyConstructedClass {
|
||||
ServletListenerClass() {
|
||||
getAnAncestor() instanceof ServletWebXMLListenerType and
|
||||
// If we have seen any `web.xml` files, this listener will be considered to be live only if it is
|
||||
// referred to as a listener-class in at least one. If no `web.xml` files are found, we assume
|
||||
// that XML extraction was not enabled, and therefore consider all listener classes as live.
|
||||
(
|
||||
isWebXMLIncluded()
|
||||
implies
|
||||
exists(WebListenerClass listenerClass | this = listenerClass.getClass())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Any class which extends the `Filter` interface is intended to be constructed reflectively by a
|
||||
* servlet container.
|
||||
*/
|
||||
class ServletFilterClass extends ReflectivelyConstructedClass {
|
||||
ServletFilterClass() {
|
||||
getASupertype*().hasQualifiedName("javax.servlet", "Filter") and
|
||||
// If we have seen any `web.xml` files, this filter will be considered to be live only if it is
|
||||
// referred to as a filter-class in at least one. If no `web.xml` files are found, we assume
|
||||
// that XML extraction was not enabled, and therefore consider all filter classes as live.
|
||||
(isWebXMLIncluded() implies exists(WebFilterClass filterClass | this = filterClass.getClass()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry point into a GWT application.
|
||||
*/
|
||||
class GWTEntryPointConstructedClass extends ReflectivelyConstructedClass {
|
||||
GWTEntryPointConstructedClass() { this.(GwtEntryPointClass).isLive() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Servlets referred to from a GWT module config file.
|
||||
*/
|
||||
class GWTServletClass extends ReflectivelyConstructedClass {
|
||||
GWTServletClass() {
|
||||
this instanceof ServletClass and
|
||||
// There must be evidence that GWT is being used, otherwise missing `*.gwt.xml` files could cause
|
||||
// all `Servlet`s to be live.
|
||||
exists(Package p | p.getName().matches("com.google.gwt%")) and
|
||||
(
|
||||
isGwtXmlIncluded()
|
||||
implies
|
||||
exists(GwtServletElement servletElement |
|
||||
this.getQualifiedName() = servletElement.getClassName()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods that may be called reflectively by the UiHandler framework.
|
||||
*/
|
||||
class GwtUiBinderEntryPoint extends CallableEntryPoint {
|
||||
GwtUiBinderEntryPoint() {
|
||||
this instanceof GwtUiFactory
|
||||
or
|
||||
this instanceof GwtUiHandler
|
||||
or
|
||||
// The UiBinder framework constructs instances of classes specified in the template files. If a
|
||||
// no-arg constructor is present, that may be called automatically. Or, if there is a
|
||||
// constructor marked as a `UiConstructor`, then that may be called instead.
|
||||
this instanceof GwtUiConstructor
|
||||
or
|
||||
exists(GwtComponentTemplateElement componentElement |
|
||||
this.getDeclaringType() = componentElement.getClass() and
|
||||
this instanceof Constructor and
|
||||
this.getNumberOfParameters() = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields that may be reflectively read or written to by the UiBinder framework.
|
||||
*/
|
||||
class GwtUiBinderReflectivelyReadField extends ReflectivelyReadField {
|
||||
GwtUiBinderReflectivelyReadField() { this instanceof GwtUiField }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Apache Camel is a messaging framework, which can integrate with Spring.
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.deadcode.DeadCode
|
||||
import semmle.code.java.frameworks.Camel
|
||||
|
||||
/**
|
||||
* A method that may be called by Apache Camel to process a message.
|
||||
*/
|
||||
class CamelMessageCallableEntryPoint extends CallableEntryPoint {
|
||||
CamelMessageCallableEntryPoint() {
|
||||
exists(CamelTargetClass camelTargetClass | this = camelTargetClass.getACamelCalledMethod()) or
|
||||
exists(CamelConsumeMethod consumeMethod | this = consumeMethod)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import default
|
||||
import semmle.code.java.deadcode.DeadCode
|
||||
import external.ExternalArtifact
|
||||
|
||||
/**
|
||||
* A method in a FIT fixture class, typically used in the fitnesse framework.
|
||||
*/
|
||||
class FitFixtureEntryPoint extends CallableEntryPoint {
|
||||
FitFixtureEntryPoint() { getDeclaringType().getAnAncestor().hasQualifiedName("fit", "Fixture") }
|
||||
}
|
||||
|
||||
/**
|
||||
* FitNesse entry points externally defined.
|
||||
*/
|
||||
class FitNesseSlimEntryPointData extends ExternalData {
|
||||
FitNesseSlimEntryPointData() { getDataPath().matches("fitnesse.csv") }
|
||||
|
||||
/**
|
||||
* Gets the class name.
|
||||
*
|
||||
* This may be a fully qualified name, or just the name of the class. It may also be, or
|
||||
* include, a FitNesse symbol, in which case it can be ignored.
|
||||
*/
|
||||
string getClassName() { result = getField(0) }
|
||||
|
||||
/**
|
||||
* Gets a Class that either has `getClassName()` as the fully qualified name, or as the class name.
|
||||
*/
|
||||
Class getACandidateClass() {
|
||||
result.getQualifiedName().matches(getClassName()) or
|
||||
result.getName() = getClassName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the callable that will be called.
|
||||
*/
|
||||
string getCallableName() { result = getField(1) }
|
||||
|
||||
/**
|
||||
* Gets the number of parameters for the callable that will be called.
|
||||
*/
|
||||
int getNumParameters() { result = getField(2).toInt() }
|
||||
|
||||
/**
|
||||
* Gets a callable on one of the candidate classes that matches the criteria for the method name
|
||||
* and number of arguments.
|
||||
*/
|
||||
Callable getACandidateCallable() {
|
||||
result.getDeclaringType() = getACandidateClass() and
|
||||
result.getName() = getCallableName() and
|
||||
result.getNumberOfParameters() = getNumParameters()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable that is a candidate for being called by a processed Slim FitNesse test. This entry
|
||||
* point requires that the FitNesse tests are processed by the fitnesse-liveness-processor, and
|
||||
* the resulting CSV file is included in the snapshots external data.
|
||||
*/
|
||||
class FitNesseSlimEntryPoint extends EntryPoint {
|
||||
FitNesseSlimEntryPoint() {
|
||||
exists(FitNesseSlimEntryPointData entryPointData |
|
||||
this = entryPointData.getACandidateCallable() and
|
||||
this.(Callable).fromSource()
|
||||
)
|
||||
}
|
||||
|
||||
override Callable getALiveCallable() { result = this }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* GigaSpaces XAP (eXtreme Application Platform) is a distributed in-memory "datagrid".
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.deadcode.DeadCode
|
||||
import semmle.code.java.frameworks.gigaspaces.GigaSpaces
|
||||
|
||||
/**
|
||||
* A method that is called during event processing by GigaSpaces, on an event listener class.
|
||||
*
|
||||
* Note: We do not track registrations of the classes containing these methods. Instead, the method
|
||||
* is considered live if the listener is at some point constructed.
|
||||
*/
|
||||
class GigaSpacesEventCallableEntryPoint extends CallableEntryPointOnConstructedClass {
|
||||
GigaSpacesEventCallableEntryPoint() { isGigaSpacesEventMethod(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An event listener class that is reflectively constructed by GigaSpaces to handle event processing.
|
||||
*/
|
||||
class GigaSpacesEventDrivenReflectivelyConstructed extends ReflectivelyConstructedClass {
|
||||
GigaSpacesEventDrivenReflectivelyConstructed() { isGigaSpacesEventDrivenClass(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that is called when a GigaSpaces "SpaceClass" is written to, or read from, a space.
|
||||
*
|
||||
* Note: We do not track whether the space class is written to or read from a space. Instead, the
|
||||
* methods are considered live if the space class is at some point constructed.
|
||||
*/
|
||||
class GigaSpacesSpaceClassMethodEntryPoint extends CallableEntryPointOnConstructedClass {
|
||||
GigaSpacesSpaceClassMethodEntryPoint() {
|
||||
this instanceof GigaSpacesSpaceIdGetterMethod or
|
||||
this instanceof GigaSpacesSpaceIdSetterMethod or
|
||||
this instanceof GigaSpacesSpaceRoutingMethod
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user