-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathNonSerializableInnerClass.ql
91 lines (84 loc) · 3.35 KB
/
NonSerializableInnerClass.ql
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
/**
* @name Serializable inner class of non-serializable class
* @description A class that is serializable with an enclosing class that is not serializable
* causes serialization to fail.
* @kind problem
* @problem.severity warning
* @precision medium
* @id java/non-serializable-inner-class
* @tags reliability
* maintainability
* language-features
*/
import java
predicate isSerializable(RefType t) { t.getAnAncestor() instanceof TypeSerializable }
predicate withinStaticContext(NestedClass c) {
c.isStatic() or
c.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable().isStatic() // JLS 15.9.2
}
RefType enclosingInstanceType(Class inner) {
not withinStaticContext(inner) and
result = inner.(NestedClass).getEnclosingType()
}
predicate castTo(ClassInstanceExpr cie, RefType to) {
exists(LocalVariableDeclExpr lvd | lvd.getInit() = cie | to = lvd.getType())
or
exists(Assignment a | a.getSource() = cie | to = a.getType())
or
exists(Call call, int n | call.getArgument(n) = cie | to = call.getCallee().getParameterType(n))
or
exists(ReturnStmt ret | ret.getResult() = cie | to = ret.getEnclosingCallable().getReturnType())
or
exists(ArrayCreationExpr ace | ace.getInit().getAnInit() = cie |
to = ace.getType().(Array).getComponentType()
)
}
predicate exceptions(NestedClass inner) {
inner instanceof AnonymousClass
or
// Serializable objects with custom `readObject` or `writeObject` methods may write out
// the "non-serializable" fields in a different way.
inner.declaresMethod("readObject")
or
inner.declaresMethod("writeObject")
or
// Exclude cases where serialization warnings are deliberately suppressed.
inner.suppressesWarningsAbout("serial")
or
// The class `inner` is a local class or non-public member class and
// all its instance expressions are cast to non-serializable types.
(inner instanceof LocalClass or not inner.isPublic()) and
forall(ClassInstanceExpr cie, RefType target |
cie.getConstructedType() = inner and castTo(cie, target)
|
not isSerializable(target)
) and
// Exception 1: the expression is used as an argument to `writeObject()`.
not exists(Call writeCall, ClassInstanceExpr cie | cie.getConstructedType() = inner |
writeCall.getCallee().hasName("writeObject") and
writeCall.getAnArgument() = cie
) and
// Exception 2: the expression is thrown as an exception (exceptions should be serializable
// due to use cases such as remote procedure calls, logging, etc.)
not exists(ThrowStmt ts, ClassInstanceExpr cie |
cie.getConstructedType() = inner and
ts.getExpr() = cie
) and
// Exception 3: if the programmer added a `serialVersionUID`, we interpret that
// as an intent to make the class serializable.
not exists(Field f | f.getDeclaringType() = inner | f.hasName("serialVersionUID"))
}
from NestedClass inner, Class outer, string advice
where
inner.getFile().isJavaSourceFile() and
isSerializable(inner) and
outer = enclosingInstanceType+(inner) and
not isSerializable(outer) and
not exceptions(inner) and
(
if inner instanceof LocalClass
then advice = "Consider implementing readObject() and writeObject()."
else advice = "Consider making the class static or implementing readObject() and writeObject()."
)
select inner, "Serializable inner class of non-serializable class $@. " + advice, outer,
outer.getName()