Files
codeql/java/ql/src/Likely Bugs/Serialization/NonSerializableField.ql
2018-10-11 11:31:37 +02:00

105 lines
3.4 KiB
Plaintext

/**
* @name Non-serializable field
* @description A non-transient field in a serializable class must also be serializable
* otherwise it causes the class to fail to serialize with a 'NotSerializableException'.
* @kind problem
* @problem.severity warning
* @precision low
* @id java/non-serializable-field
* @tags reliability
* maintainability
* language-features
*/
import java
import semmle.code.java.JDKAnnotations
import semmle.code.java.Collections
import semmle.code.java.Maps
import semmle.code.java.frameworks.javaee.ejb.EJB
predicate externalizable(Interface interface) {
interface.hasQualifiedName("java.io", "Externalizable")
}
predicate serializableOrExternalizable(Interface interface) {
externalizable(interface) or
interface instanceof TypeSerializable
}
predicate collectionOrMapType(RefType t) { t instanceof CollectionType or t instanceof MapType }
predicate serializableType(RefType t) {
exists(RefType sup | sup = t.getASupertype*() | serializableOrExternalizable(sup))
or
(
// Collection interfaces are not serializable, but their implementations are
// likely to be.
collectionOrMapType(t) and
forall(RefType param | param = t.(ParameterizedType).getATypeArgument() |
serializableType(param)
)
)
or
exists(BoundedType bt | bt = t | serializableType(bt.getUpperBoundType()))
}
RefType reasonForNonSerializableCollection(ParameterizedType par) {
collectionOrMapType(par) and
result = par.getATypeArgument() and
not serializableType(result)
}
string nonSerialReason(RefType t) {
not serializableType(t) and
if exists(reasonForNonSerializableCollection(t))
then result = reasonForNonSerializableCollection(t).getName() + " is not serializable"
else result = t.getName() + " is not serializable"
}
predicate exceptions(Class c, Field f) {
f.getDeclaringType() = c and
(
// `Serializable` objects with custom `readObject` or `writeObject` methods
// may write out the "non-serializable" fields in a different way.
c.declaresMethod("readObject")
or
c.declaresMethod("writeObject")
or
// Exclude classes with suppressed warnings.
c.suppressesWarningsAbout("serial")
or
// Exclude anonymous classes whose `ClassInstanceExpr` is assigned to
// a variable on which serialization warnings are suppressed.
exists(Variable v |
v.getAnAssignedValue() = c.(AnonymousClass).getClassInstanceExpr() and
v.suppressesWarningsAbout("serial")
)
or
f.isTransient()
or
f.isStatic()
or
// Classes that implement `Externalizable` completely take over control during serialization.
externalizable(c.getASupertype+())
or
// Stateless session beans are not normally serialized during their usual life-cycle
// but are forced by their expected supertype to be serializable.
// Arguably, warnings for their non-serializable fields can therefore be suppressed in practice.
c instanceof StatelessSessionEJB
or
// Enum types are serialized by name, so it doesn't matter if they have non-serializable fields.
c instanceof EnumType
)
}
from Class c, Field f, string reason
where
c.fromSource() and
c.getASupertype+() instanceof TypeSerializable and
f.getDeclaringType() = c and
not exceptions(c, f) and
reason = nonSerialReason(f.getType())
select f,
"This field is in a serializable class, " + " but is not serializable itself because " + reason +
"."