-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathSafePublication.ql
More file actions
93 lines (87 loc) · 2.8 KB
/
SafePublication.ql
File metadata and controls
93 lines (87 loc) · 2.8 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
/**
* @name Safe publication
* @description A field of a thread-safe class is not safely published.
* @kind problem
* @problem.severity warning
* @precision high
* @id java/safe-publication
* @tags quality
* reliability
* concurrency
*/
import java
import semmle.code.java.ConflictingAccess
/**
* Holds if `v` should be the default value for the field `f`.
* That is, `v` is an initial (or constructor) assignment of `f`.
*/
predicate shouldBeDefaultValueFor(Field f, Expr v) {
v = f.getAnAssignedValue() and
(
v = f.getInitializer()
or
v.getEnclosingCallable() = f.getDeclaringType().getAConstructor()
)
}
/**
* Gets the default value for the field `f`.
* See https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
* for the default values of the primitive types.
* The default value for non-primitive types is null.
*/
bindingset[result]
Expr getDefaultValue(Field f) {
f.getType().hasName("byte") and result.(IntegerLiteral).getIntValue() = 0
or
f.getType().hasName("short") and result.(IntegerLiteral).getIntValue() = 0
or
f.getType().hasName("int") and result.(IntegerLiteral).getIntValue() = 0
or
f.getType().hasName("long") and
(
result.(LongLiteral).getValue() = "0" or
result.(IntegerLiteral).getValue() = "0"
)
or
f.getType().hasName("float") and result.(FloatLiteral).getValue() = "0.0"
or
f.getType().hasName("double") and result.(DoubleLiteral).getValue() = "0.0"
or
f.getType().hasName("char") and result.(CharacterLiteral).getCodePointValue() = 0
or
f.getType().hasName("boolean") and result.(BooleanLiteral).getBooleanValue() = false
or
not f.getType().getName() in [
"byte", "short", "int", "long", "float", "double", "char", "boolean"
] and
result instanceof NullLiteral
}
/**
* Holds if all constructor or initial assignments (if any) are to the default value.
* That is, assignments by the declaration:
* int x = 0; OK
* int x = 3; not OK
* or inside a constructor:
* public c(a) {
* x = 0; OK
* x = 3; not OK
* x = a; not OK
* }
*/
predicate isAssignedDefaultValue(Field f) {
forall(Expr v | shouldBeDefaultValueFor(f, v) | v = getDefaultValue(f))
}
predicate isSafelyPublished(Field f) {
f.isFinal() or // NOTE: For non-primitive types, 'final' alone does not guarantee safe publication unless the object is immutable or safely constructed. Consider reviewing the handling of non-primitive fields for safe publication.
f.isStatic() or
f.isVolatile() or
isThreadSafeType(f.getType()) or
isThreadSafeType(f.getInitializer().getType()) or
isAssignedDefaultValue(f)
}
from Field f, ClassAnnotatedAsThreadSafe c
where
f = c.getAField() and
not isSafelyPublished(f)
select f, "The class $@ is marked as thread-safe, but this field is not safely published.", c,
c.getName()