-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathVisibleForTestingAbuse.ql
More file actions
129 lines (121 loc) · 5.09 KB
/
VisibleForTestingAbuse.ql
File metadata and controls
129 lines (121 loc) · 5.09 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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* @id java/visible-for-testing-abuse
* @name Accessing any method, field or class annotated with `@VisibleForTesting` from production code is discouraged
* @description Accessing any method, field or class annotated with `@VisibleForTesting` from
* production code goes against the intention of the annotation and may indicate
* programmer error.
* @kind problem
* @precision high
* @problem.severity warning
* @tags quality
* maintainability
* readability
*/
import java
/**
* A `Callable` is within some `RefType`
*/
predicate isWithinType(Callable c, RefType t) { c.getDeclaringType() = t }
/**
* A `Callable` is within same package as the `RefType`
*/
predicate isWithinPackage(Expr e, RefType t) {
e.getCompilationUnit().getPackage() = t.getPackage()
}
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()
}
class OuterClass extends Class {
OuterClass() { this = enclosingInstanceType+(_) }
}
/**
* An innerclass is accessed outside of its outerclass
* and also outside of its fellow inner parallel classes
*/
predicate isWithinDirectOuterClassOrSiblingInner(
Callable classInstanceEnclosing, RefType typeBeingConstructed
) {
exists(NestedClass inner, OuterClass outer |
outer = enclosingInstanceType(inner) and
typeBeingConstructed = inner and
// where the inner is called from the outer class
classInstanceEnclosing.getDeclaringType() = outer
)
or
// and inner is called from the a parallel inner
exists(NestedClass inner, OuterClass outer, NestedClass otherinner |
typeBeingConstructed = inner and
outer = enclosingInstanceType(otherinner) and
outer = enclosingInstanceType(inner) and
classInstanceEnclosing.getDeclaringType() = otherinner
)
}
from Annotatable annotated, Annotation annotation, Expr e
where
annotation.getType().hasName("VisibleForTesting") and
annotated.getAnAnnotation() = annotation and
(
// field access
exists(FieldAccess v |
v = e and
v.getField() = annotated and
// depending on the visiblity of the field, using the annotation to abuse the visibility may/may not be occurring
(
// if its package protected report when its used outside its class bc it should have been private (class only permitted)
v.getField().isPackageProtected() and
not isWithinType(v.getEnclosingCallable(), v.getField().getDeclaringType())
or
// if public or protected report when its used outside its package because package protected should have been enough (package only permitted)
(v.getField().isPublic() or v.getField().isProtected()) and
not isWithinPackage(v, v.getField().getDeclaringType())
)
)
or
// class instantiation
exists(ClassInstanceExpr c |
c = e and
c.getConstructedType() = annotated and
// depending on the visiblity of the class, using the annotation to abuse the visibility may/may not be occurring
// if public report when its used outside its package because package protected should have been enough (package only permitted)
(
c.getConstructedType().isPublic() and
not isWithinPackage(c, c.getConstructedType())
or
// if its package protected report when its used outside its outer class bc it should have been private (outer class only permitted)
c.getConstructedType().hasNoModifier() and
// and the class is an innerclass, because otherwise recommending a lower accessibility makes no sense (only inner classes can be private)
exists(enclosingInstanceType(c.getConstructedType())) and
not isWithinDirectOuterClassOrSiblingInner(c.getEnclosingCallable(), c.getConstructedType())
)
)
or
// method access
exists(MethodCall c |
c = e and
c.getMethod() = annotated and
// depending on the visiblity of the method, using the annotation to abuse the visibility may/may not be occurring
(
// if its package protected report when its used outside its class bc it should have been private (class only permitted)
c.getMethod().isPackageProtected() and
not isWithinType(c.getEnclosingCallable(), c.getMethod().getDeclaringType())
or
// if public or protected report when its used outside its package because package protected should have been enough (package only permitted)
(c.getMethod().isPublic() or c.getMethod().isProtected()) and
not isWithinPackage(c, c.getMethod().getDeclaringType())
)
)
) and
// not in a test where use is appropriate
not e.getEnclosingCallable() instanceof LikelyTestMethod and
// also omit our own ql unit test where it is acceptable
not e.getEnclosingCallable()
.getFile()
.getAbsolutePath()
.matches("%java/ql/test/query-tests/%Test.java")
select e, "Access of $@ annotated with VisibleForTesting found in production code.", annotated,
"element"