-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathVisibleForTestingAbuse.ql
More file actions
142 lines (134 loc) · 5.4 KB
/
VisibleForTestingAbuse.ql
File metadata and controls
142 lines (134 loc) · 5.4 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
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
* @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
/**
* Holds if a `Callable` is within some `RefType` (including through lambdas and inner classes)
*/
predicate isWithinType(Callable c, RefType t) {
c.getDeclaringType() = t
or
c.getDeclaringType().getEnclosingType*() = t
}
/**
* Holds if a `Callable` is within same package as the `RefType`
*/
predicate isWithinPackage(Expr e, RefType t) {
e.getCompilationUnit().getPackage() = t.getPackage()
}
/**
* Holds if a nested class is within a static context
*/
predicate withinStaticContext(NestedClass c) {
c.isStatic() or
c.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable().isStatic() // JLS 15.9.2
}
/**
* Gets the enclosing instance type for a non-static inner class
*/
RefType enclosingInstanceType(Class inner) {
not withinStaticContext(inner) and
result = inner.(NestedClass).getEnclosingType()
}
/**
* A class that encloses one or more inner classes
*/
class OuterClass extends Class {
OuterClass() { this = enclosingInstanceType+(_) }
}
/**
* Holds if 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"