-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathDefineEqualsWhenAddingFields.ql
More file actions
92 lines (85 loc) · 3.47 KB
/
DefineEqualsWhenAddingFields.ql
File metadata and controls
92 lines (85 loc) · 3.47 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
/**
* @name Inherited equals() in subclass with added fields
* @description If a class overrides 'Object.equals', and a subclass defines additional fields
* to those it inherits but does not re-define 'equals', the results of 'equals'
* may be wrong.
* @kind problem
* @problem.severity warning
* @precision low
* @id java/inherited-equals-with-added-fields
* @tags reliability
* correctness
*/
import java
predicate okForEquals(Class c) {
c.getAMethod() instanceof EqualsMethod
or
not exists(c.getAField()) and
okForEquals(c.getASupertype())
}
/** Holds if method `em` implements a reference equality check. */
predicate checksReferenceEquality(EqualsMethod em) {
// `java.lang.Object.equals` is the prototypical reference equality implementation.
em.getDeclaringType() instanceof TypeObject
or
// Custom reference equality implementations observed in open-source projects.
exists(SingletonBlock blk, EQExpr eq |
blk = em.getBody() and
eq.getAnOperand() instanceof ThisAccess and
eq.getAnOperand().(VarAccess).getVariable() = em.getParameter(0) and
(
// `{ return (ojb==this); }`
eq = blk.getStmt().(ReturnStmt).getExpr()
or
// `{ if (ojb==this) return true; else return false; }`
exists(IfStmt ifStmt | ifStmt = blk.getStmt() |
eq = ifStmt.getCondition() and
ifStmt.getThen().(ReturnStmt).getExpr().(BooleanLiteral).getBooleanValue() = true and
ifStmt.getElse().(ReturnStmt).getExpr().(BooleanLiteral).getBooleanValue() = false
)
)
)
or
// Check whether `em` delegates to another method checking reference equality.
// More precisely, we check whether the body of `em` is of the form `return super.equals(o);`,
// where `o` is the (only) parameter of `em`, and the invoked method is a reference equality check.
exists(SuperMethodCall sup |
sup = em.getBody().(SingletonBlock).getStmt().(ReturnStmt).getExpr() and
sup.getArgument(0) = em.getParameter(0).getAnAccess() and
checksReferenceEquality(sup.getCallee())
)
}
predicate unsupportedEquals(EqualsMethod em) {
em.getBody().(SingletonBlock).getStmt() instanceof ThrowStmt
}
predicate overridesDelegateEquals(EqualsMethod em, Class c) {
exists(Method override, Method delegate |
// The `equals` method (declared in the supertype) contains
// a call to a `delegate` method on the same type ...
em.calls(delegate) and
delegate.getDeclaringType() = em.getDeclaringType() and
// ... and the `delegate` method is overridden in the subtype `c`
// by a method that reads at least one added field.
override.getDeclaringType() = c and
exists(Method overridden | overridden.getSourceDeclaration() = delegate |
override.overrides(overridden)
) and
readsOwnField(override)
)
}
predicate readsOwnField(Method m) { m.reads(m.getDeclaringType().getAField()) }
from Class c, InstanceField f, EqualsMethod em
where
not exists(EqualsMethod m | m.getDeclaringType() = c) and
okForEquals(c.getASupertype()) and
exists(Method m | m.getSourceDeclaration() = em | c.inherits(m)) and
exists(em.getBody()) and
not checksReferenceEquality(em) and
not unsupportedEquals(em) and
not overridesDelegateEquals(em, c) and
f.getDeclaringType() = c and
c.fromSource() and
not c instanceof EnumType and
not f.isFinal()
select c, c.getName() + " inherits $@ but adds $@.", em.getSourceDeclaration(),
em.getDeclaringType().getName() + "." + em.getName(), f, "the field " + f.getName()