-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathNonSerializableInnerClass.ql
More file actions
98 lines (89 loc) · 3.45 KB
/
NonSerializableInnerClass.ql
File metadata and controls
98 lines (89 loc) · 3.45 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
/**
* @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
import semmle.code.java.JDKAnnotations
predicate isSerializable(RefType t) {
exists(TypeSerializable ts | ts = t.getASupertype*())
}
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.fromSource() 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()