3
3
import edu .umd .cs .findbugs .BugInstance ;
4
4
import edu .umd .cs .findbugs .BugReporter ;
5
5
import edu .umd .cs .findbugs .OpcodeStack ;
6
+ import edu .umd .cs .findbugs .ba .AnalysisContext ;
6
7
import edu .umd .cs .findbugs .ba .XMethod ;
7
8
import edu .umd .cs .findbugs .bcel .OpcodeStackDetector ;
8
9
10
+ import java .util .Arrays ;
11
+ import java .util .Optional ;
12
+ import java .util .stream .Stream ;
13
+
9
14
import org .apache .bcel .Const ;
15
+ import org .apache .bcel .classfile .Code ;
10
16
import org .apache .bcel .classfile .ExceptionTable ;
17
+ import org .apache .bcel .classfile .JavaClass ;
11
18
import org .apache .bcel .classfile .Method ;
19
+ import org .apache .commons .lang3 .StringUtils ;
12
20
13
21
14
22
public class ThrowingExceptions extends OpcodeStackDetector {
@@ -18,19 +26,63 @@ public ThrowingExceptions(BugReporter bugReporter) {
18
26
this .bugReporter = bugReporter ;
19
27
}
20
28
29
+ private JavaClass klass ;
30
+ private String exceptionThrown = null ;
31
+
32
+ @ Override
33
+ public void visit (JavaClass obj ) {
34
+ exceptionThrown = null ;
35
+ klass = obj ;
36
+ }
37
+
21
38
@ Override
22
39
public void visit (Method obj ) {
23
- ExceptionTable exceptionTable = obj .getExceptionTable ();
24
- if (exceptionTable != null ) {
25
- String [] exceptionNames = exceptionTable .getExceptionNames ();
26
- for (String exception : exceptionNames ) {
27
- if ("java.lang.Exception" .equals (exception )) {
28
- reportBug ("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION" , getXMethod ());
29
- } else if ("java.lang.Throwable" .equals (exception )) {
30
- reportBug ("THROWS_METHOD_THROWS_CLAUSE_THROWABLE" , getXMethod ());
31
- }
40
+ exceptionThrown = null ;
41
+
42
+ if (obj .isSynthetic ()) {
43
+ return ;
44
+ }
45
+
46
+ Stream <String > exceptionStream = null ;
47
+
48
+ String signature = obj .getGenericSignature ();
49
+ if (signature != null ) {
50
+ String [] exceptions = StringUtils .substringsBetween (signature , "^" , ";" );
51
+ if (exceptions != null ) {
52
+ exceptionStream = Arrays .stream (exceptions )
53
+ .filter (s -> s .charAt (0 ) == 'L' )
54
+ .map (s -> s .substring (1 ).replace ('/' , '.' ));
55
+ }
56
+ } else {
57
+ ExceptionTable exceptionTable = obj .getExceptionTable ();
58
+ if (exceptionTable != null ) {
59
+ exceptionStream = Arrays .stream (exceptionTable .getExceptionNames ());
32
60
}
33
61
}
62
+
63
+ if (exceptionStream != null ) {
64
+ Optional <String > exception = exceptionStream
65
+ .filter (s -> "java.lang.Exception" .equals (s ) || "java.lang.Throwable" .equals (s ))
66
+ .findAny ();
67
+ if (exception .isPresent () && !parentThrows (obj , exception .get ())) {
68
+ exceptionThrown = exception .get ();
69
+ }
70
+ }
71
+
72
+ if (obj .getCode () == null ) {
73
+ if (exceptionThrown != null ) {
74
+ reportBug ("java.lang.Exception" .equals (exceptionThrown ) ? "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"
75
+ : "THROWS_METHOD_THROWS_CLAUSE_THROWABLE" , getXMethod ());
76
+ }
77
+ }
78
+ }
79
+
80
+ @ Override
81
+ public void visitAfter (Code obj ) {
82
+ if (exceptionThrown != null ) {
83
+ reportBug ("java.lang.Exception" .equals (exceptionThrown ) ? "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"
84
+ : "THROWS_METHOD_THROWS_CLAUSE_THROWABLE" , getXMethod ());
85
+ }
34
86
}
35
87
36
88
@ Override
@@ -42,10 +94,121 @@ public void sawOpcode(int seen) {
42
94
reportBug ("THROWS_METHOD_THROWS_RUNTIMEEXCEPTION" , getXMethod ());
43
95
}
44
96
}
97
+ } else if (exceptionThrown != null &&
98
+ seen == Const .INVOKEVIRTUAL ||
99
+ seen == Const .INVOKEINTERFACE ||
100
+ seen == Const .INVOKESTATIC ) {
101
+ XMethod calledMethod = getXMethodOperand ();
102
+ if (calledMethod == null ) {
103
+ return ;
104
+ }
105
+
106
+ String [] thrownExceptions = calledMethod .getThrownExceptions ();
107
+ if (thrownExceptions != null && Arrays .stream (thrownExceptions )
108
+ .anyMatch (s -> s .replace ('/' , '.' ).equals (exceptionThrown ))) {
109
+ exceptionThrown = null ;
110
+ }
111
+
45
112
}
46
113
}
47
114
48
115
private void reportBug (String bugName , XMethod method ) {
49
- bugReporter .reportBug (new BugInstance (this , bugName , NORMAL_PRIORITY ).addClass (this ).addMethod (method ));
116
+ bugReporter .reportBug (new BugInstance (this , bugName , LOW_PRIORITY ).addClass (this ).addMethod (method ));
117
+ }
118
+
119
+ private boolean parentThrows (Method method , String exception ) {
120
+ return parentThrows (klass , method , exception );
121
+ }
122
+
123
+ private boolean parentThrows (JavaClass clazz , Method method , String exception ) {
124
+ JavaClass ancestor ;
125
+ boolean throwsEx = false ;
126
+ try {
127
+ ancestor = clazz .getSuperClass ();
128
+ if (ancestor != null ) {
129
+ Optional <Method > superMethod = Arrays .stream (ancestor .getMethods ())
130
+ .filter (m -> method .getName ().equals (m .getName ()) && sameSignature (method , m ))
131
+ .findAny ();
132
+ if (superMethod .isPresent ()) {
133
+ throwsEx = Arrays .stream (superMethod .get ().getExceptionTable ().getExceptionNames ())
134
+ .anyMatch (s -> exception .equals (s ));
135
+ } else {
136
+ throwsEx = parentThrows (ancestor , method , exception );
137
+ }
138
+ }
139
+
140
+ for (JavaClass intf : clazz .getInterfaces ()) {
141
+ Optional <Method > superMethod = Arrays .stream (intf .getMethods ())
142
+ .filter (m -> method .getName ().equals (m .getName ()) && sameSignature (method , m ))
143
+ .findAny ();
144
+ if (superMethod .isPresent ()) {
145
+ throwsEx |= Arrays .stream (superMethod .get ().getExceptionTable ().getExceptionNames ())
146
+ .anyMatch (s -> exception .equals (s ));
147
+ } else {
148
+ throwsEx |= parentThrows (intf , method , exception );
149
+ }
150
+ }
151
+ } catch (ClassNotFoundException e ) {
152
+ AnalysisContext .reportMissingClass (e );
153
+ }
154
+ return throwsEx ;
155
+ }
156
+
157
+ private boolean sameSignature (Method child , Method parent ) {
158
+ String genSig = parent .getGenericSignature ();
159
+ if (genSig == null ) {
160
+ return child .getSignature ().equals (parent .getSignature ());
161
+ }
162
+
163
+ String sig = child .getSignature ();
164
+ int genIndex = 1 ;
165
+ char genSigChar = genSig .charAt (genIndex );
166
+ int index = 1 ;
167
+ char sigChar = sig .charAt (index );
168
+ while (genSigChar != ')' && sigChar != ')' ) {
169
+ switch (genSigChar ) {
170
+ default :
171
+ if (sigChar != genSigChar ) {
172
+ return false ;
173
+ }
174
+ ++index ;
175
+ ++genIndex ;
176
+ break ;
177
+ case 'L' :
178
+ int genSigObjEnd = genSig .indexOf (';' , genIndex );
179
+ int sigObjEnd = sig .indexOf (';' , index );
180
+ if (!genSig .substring (genIndex , genSigObjEnd - genIndex )
181
+ .equals (sig .substring (index , sigObjEnd - index ))) {
182
+ return false ;
183
+ }
184
+ genIndex = genSigObjEnd + 1 ;
185
+ index = sigObjEnd + 1 ;
186
+ break ;
187
+ case 'T' :
188
+ if (sigChar != 'L' ) {
189
+ return false ;
190
+ }
191
+ genSigObjEnd = genSig .indexOf (';' , genIndex );
192
+ sigObjEnd = sig .indexOf (';' , index );
193
+ genIndex = genSigObjEnd + 1 ;
194
+ index = sigObjEnd + 1 ;
195
+ break ;
196
+ }
197
+ genSigChar = genSig .charAt (genIndex );
198
+ sigChar = sig .charAt (index );
199
+ }
200
+
201
+ if (sigChar != genSigChar ) {
202
+ return false ;
203
+ }
204
+
205
+ genSigChar = genSig .charAt (++genIndex );
206
+ sigChar = sig .charAt (++index );
207
+
208
+ if (genSigChar == 'T' ) {
209
+ return sigChar == 'L' ;
210
+ } else {
211
+ return genSig .substring (genIndex ).equals (sig .substring (index ));
212
+ }
50
213
}
51
214
}
0 commit comments