Skip to content

Commit b1109b5

Browse files
kaufcoleveretka
authored andcommitted
SONARJAVA-4406 FP on S2142 when the InterruptedException is caught in an inner try-catch
1 parent 4b51016 commit b1109b5

File tree

3 files changed

+118
-33
lines changed

3 files changed

+118
-33
lines changed

java-checks-test-sources/src/main/java/checks/InterruptedExceptionCheck.java

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void run1 () {
2525
}catch (java.io.IOException e) {
2626
LOGGER.log(Level.WARN, "Interrupted!", e);
2727
}catch (InterruptedException e) { // Noncompliant [[sc=13;ec=35]] {{Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.}}
28-
LOGGER.log(Level.WARN, "Interrupted!", e);
28+
LOGGER.log(Level.WARN, "Interrupted!", e);
2929
}
3030
}
3131

@@ -57,10 +57,10 @@ public void runInterrupted() {
5757
try {
5858
throw new InterruptedException();
5959
} catch (InterruptedException e) {
60-
LOGGER.log(Level.WARN, "Interrupted!", e);
61-
// clean up state...
62-
Thread.currentThread().interrupt();
63-
}
60+
LOGGER.log(Level.WARN, "Interrupted!", e);
61+
// clean up state...
62+
Thread.currentThread().interrupt();
63+
}
6464
try {
6565
while (true) {
6666
throw new InterruptedException();
@@ -71,7 +71,7 @@ public void runInterrupted() {
7171
// clean up state...
7272
new Interruptable().interrupt();
7373
}
74-
}
74+
}
7575

7676
public Object getNextTask(BlockingQueue<Object> queue) {
7777
boolean interrupted = false;
@@ -96,14 +96,14 @@ class Interruptable {
9696
static final Log LOGGER = null;
9797

9898
void interrupt() {
99-
99+
100100
}
101101

102102
private static void waitForNextExecution(Set<Runnable> running, LongSupplier waitTimeoutMillis) {
103103
try {
104104
Thread.sleep(waitTimeoutMillis.getAsLong());
105105
} catch (InterruptedException e) { //Compliant
106-
cancelAllSubTasksAndInterrupt(running);
106+
cancelAllSubTasksAndInterrupt(running);
107107
}
108108
}
109109

@@ -113,28 +113,28 @@ private static void cancelAllSubTasksAndInterrupt(Set<Runnable> subTasks) {
113113
}
114114
Thread.currentThread().interrupt();
115115
}
116-
116+
117117
private static void waitForNextExecution1(Set<Runnable> running, LongSupplier waitTimeoutMillis) {
118118
try {
119119
Thread.sleep(waitTimeoutMillis.getAsLong());
120120
} catch (InterruptedException e) { // Noncompliant, too many levels
121-
cancelAllSubTasksAndInterrupt1(running);
121+
cancelAllSubTasksAndInterrupt1(running);
122122
}
123123
}
124124

125125
private static void cancelAllSubTasksAndInterrupt1(Set<Runnable> subTasks) {
126126
cancelAllSubTasksAndInterrupt2(subTasks);
127127
}
128128

129-
private static void cancelAllSubTasksAndInterrupt2(Set<Runnable> subTasks) {
129+
private static void cancelAllSubTasksAndInterrupt2(Set<Runnable> subTasks) {
130130
cancelAllSubTasksAndInterrupt3(subTasks);
131131
}
132132

133133
private static void cancelAllSubTasksAndInterrupt3(Set<Runnable> subTasks) {
134134
cancelAllSubTasksAndInterrupt4(subTasks);
135135
if (LOGGER != null )throw new RuntimeException();
136136
}
137-
137+
138138
private static void cancelAllSubTasksAndInterrupt4(Set<Runnable> subTasks) {
139139
for (Runnable task : subTasks) {
140140
System.out.println("--- waitForNextExecution: Service interrupted. Cancel execution of task {}.");
@@ -275,7 +275,6 @@ public void throwNewInterruptedExceptionFromFunction() throws InterruptedExcepti
275275
}
276276

277277
public void throwNewCustomizedInterruptedExceptionFromFunction() throws InterruptedException {
278-
279278
try {
280279
throwsInterruptedException();
281280
} catch (InterruptedException e) { // Compliant
@@ -302,7 +301,6 @@ public void throwNewCustomizedInterruptedExceptionFromFunction() throws Interrup
302301
}
303302

304303
public void rethrowSameException() throws InterruptedException {
305-
306304
try {
307305
throwsInterruptedException();
308306
} catch (InterruptedException e) { // Compliant
@@ -340,7 +338,6 @@ public void rethrowSameException() throws InterruptedException {
340338
}
341339

342340
public void cutControlFlow() throws InterruptedException {
343-
344341
try {
345342
throwsInterruptedException();
346343
} catch (InterruptedException e) { // Noncompliant, because neither foo nor bar belong to the control flow of this catch block.
@@ -359,11 +356,74 @@ void bar() throws InterruptedException {
359356
try {
360357
throwsInterruptedException();
361358
} catch (InterruptedException e) { // Noncompliant, because neither foo, nor bar belong to the control flow of this catch block.
362-
Runnable foo = () -> Thread.currentThread().interrupt();
363-
Action bar = () -> {
364-
throw new InterruptedException();
365-
};
359+
Runnable foo = () -> Thread.currentThread().interrupt();
360+
Action bar = () -> {
361+
throw new InterruptedException();
362+
};
363+
}
364+
}
365+
366+
void falsePositivesSonarjava4406() {
367+
try {
368+
try {
369+
throwsInterruptedException();
370+
} catch (InterruptedException ie) { // Noncompliant, because interruption state is lost.
371+
}
372+
} catch (Exception e) { // Compliant, because inner try does not throw an InterruptedException
373+
}
374+
375+
try {
376+
try {
377+
throwsInterruptedException();
378+
} catch (InterruptedException ie) { // Compliant
379+
Thread.currentThread().interrupt();
380+
}
381+
} catch (Exception e) { // Compliant, because inner try does not throw an InterruptedException
366382
}
383+
384+
try {
385+
try {
386+
throwsInterruptedException();
387+
} catch (InterruptedException ie) { // Compliant
388+
throw new InterruptedException();
389+
}
390+
} catch (InterruptedException e) { // Noncompliant, because explicitly catch InterruptedException
391+
}
392+
393+
try {
394+
try {
395+
throwsInterruptedException();
396+
} catch (RuntimeException ie) { // Compliant
397+
}
398+
} catch (Exception e) { // Noncompliant, because inner try may throw an InterruptedException
399+
}
400+
401+
}
402+
403+
public void throwInterruptedExceptionFromResourceList() throws InterruptedException {
404+
try {
405+
try(AutoCloseable autocloseable = getAutoCloseableThrowsInterruptedException()) {
406+
doSomething();
407+
} catch (IOException ie) {
408+
doSomething();
409+
}
410+
} catch (Exception e) { // Noncompliant
411+
}
412+
}
413+
414+
public void throwInterruptedExceptionWithinThrowStatement() throws InterruptedException {
415+
try {
416+
throw getRuntimeExceptionThrowsInterruptedException();
417+
} catch (Exception e) { // Noncompliant
418+
}
419+
}
420+
421+
private AutoCloseable getAutoCloseableThrowsInterruptedException() throws InterruptedException {
422+
throw getInterruptedException();
423+
}
424+
425+
private RuntimeException getRuntimeExceptionThrowsInterruptedException() throws InterruptedException {
426+
throw getInterruptedException();
367427
}
368428

369429
@FunctionalInterface
@@ -414,15 +474,11 @@ private static void interruptByThrowingCustomizedInterruptedExceptionL4() throws
414474
throw new CustomizedInterruptedException();
415475
}
416476

417-
public void throwsException() throws Exception {
477+
public static void throwsException() throws Exception {
418478
throw new Exception();
419479
}
420480

421-
public void throwsInterruptedException() throws InterruptedException {
422-
throw new InterruptedException();
423-
}
424-
425-
public void throwsCustomizedInterruptedException() throws InterruptedException {
481+
public static void throwsInterruptedException() throws InterruptedException {
426482
throw new InterruptedException();
427483
}
428484

java-checks/src/main/java/org/sonar/java/checks/InterruptedExceptionCheck.java

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@
3333
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
3434
import org.sonar.plugins.java.api.JavaFileScannerContext;
3535
import org.sonar.plugins.java.api.semantic.MethodMatchers;
36-
import org.sonar.plugins.java.api.semantic.Symbol;
3736
import org.sonar.plugins.java.api.semantic.Type;
3837
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
3938
import org.sonar.plugins.java.api.tree.BlockTree;
4039
import org.sonar.plugins.java.api.tree.CatchTree;
4140
import org.sonar.plugins.java.api.tree.ClassTree;
41+
import org.sonar.plugins.java.api.tree.ExpressionTree;
4242
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
4343
import org.sonar.plugins.java.api.tree.MethodTree;
4444
import org.sonar.plugins.java.api.tree.ThrowStatementTree;
@@ -84,6 +84,7 @@ public void visitNode(Tree tree) {
8484
TryStatementTree tryStatementTree = (TryStatementTree) tree;
8585

8686
withinInterruptingFinally.addFirst(isFinallyInterrupting(tryStatementTree.finallyBlock()));
87+
8788
for (CatchTree catchTree : tryStatementTree.catches()) {
8889
VariableTree catchParameter = catchTree.parameter();
8990
List<Type> caughtTypes = getCaughtTypes(catchParameter);
@@ -102,14 +103,14 @@ public void visitNode(Tree tree) {
102103
}
103104

104105
private void reportIfThrowInterruptInBlock(BlockTree blockTree, CatchTree catchTree) {
105-
MethodTreeUtils.MethodInvocationCollector collector = new MethodTreeUtils.MethodInvocationCollector(InterruptedExceptionCheck::throwInterruptedException);
106+
InterruptingStatementCollector collector = new InterruptingStatementCollector();
106107
blockTree.accept(collector);
107108
List<Tree> invocationInterrupting = collector.getInvocationTree();
108109

109110
if (!invocationInterrupting.isEmpty() && wasNotInterrupted(catchTree)) {
110111
reportIssue(catchTree.parameter(), String.format(MESSAGE, "InterruptedException"),
111112
invocationInterrupting.stream()
112-
.map(t -> new JavaFileScannerContext.Location("Method invocation throwing InterruptedException.", t))
113+
.map(t -> new JavaFileScannerContext.Location("Statement throwing InterruptedException.", t))
113114
.collect(Collectors.toList()),
114115
null);
115116
}
@@ -148,9 +149,36 @@ private static boolean isFinallyInterrupting(@Nullable BlockTree blockTree) {
148149
return blockVisitor.threadInterrupted;
149150
}
150151

151-
private static boolean throwInterruptedException(Symbol.MethodSymbol symbol) {
152-
return !symbol.isUnknown()
153-
&& symbol.thrownTypes().stream().anyMatch(INTERRUPTING_TYPE_PREDICATE);
152+
private static boolean isInterruptingExceptionExpression(ExpressionTree expressionTree) {
153+
return INTERRUPTING_TYPE_PREDICATE.test(expressionTree.symbolType());
154+
}
155+
156+
private static class InterruptingStatementCollector extends MethodTreeUtils.MethodInvocationCollector {
157+
158+
public InterruptingStatementCollector() {
159+
super(
160+
symbol -> symbol.thrownTypes().stream().anyMatch(INTERRUPTING_TYPE_PREDICATE)
161+
);
162+
}
163+
164+
@Override
165+
public void visitTryStatement(TryStatementTree tryStatementTree) {
166+
// If inner `try` statement catches interrupting types: cut analysis of its try block, because possible
167+
// interruptions thrown there are handled within the scope of the catch block.
168+
// Yet, all other blocks (resources, catch, finally) of inner `try`s must still be analyzed, because possible
169+
// interruptions thrown there must be handled within the scope of the outer try.
170+
171+
boolean isCatchingAnyInterruptingTypes = tryStatementTree.catches().stream().anyMatch(catchTree ->
172+
getCaughtTypes(catchTree.parameter()).stream().anyMatch(INTERRUPTING_TYPE_PREDICATE)
173+
);
174+
175+
scan(tryStatementTree.resourceList());
176+
if (!isCatchingAnyInterruptingTypes) {
177+
scan(tryStatementTree.block());
178+
}
179+
scan(tryStatementTree.catches());
180+
scan(tryStatementTree.finallyBlock());
181+
}
154182
}
155183

156184
private static class BlockVisitor extends BaseTreeVisitor {
@@ -185,7 +213,7 @@ public void visitMethodInvocation(MethodInvocationTree tree) {
185213

186214
@Override
187215
public void visitThrowStatement(ThrowStatementTree tree) {
188-
if (threadInterrupted || INTERRUPTING_TYPE_PREDICATE.test(tree.expression().symbolType())) {
216+
if (threadInterrupted || isInterruptingExceptionExpression(tree.expression())) {
189217
threadInterrupted = true;
190218
} else {
191219
super.visitThrowStatement(tree);
@@ -202,4 +230,5 @@ public void visitLambdaExpression(LambdaExpressionTree tree) {
202230
// Cut visit on lambdas, because we only want to analyze actual control flow.
203231
}
204232
}
233+
205234
}

java-checks/src/main/java/org/sonar/java/checks/helpers/MethodTreeUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ static boolean hasKind(@Nullable Tree tree, Tree.Kind kind) {
139139
}
140140

141141
public static class MethodInvocationCollector extends BaseTreeVisitor {
142-
private final List<Tree> invocationTree = new ArrayList<>();
142+
protected final List<Tree> invocationTree = new ArrayList<>();
143143
private final Predicate<Symbol.MethodSymbol> collectPredicate;
144144

145145
public MethodInvocationCollector(Predicate<Symbol.MethodSymbol> collectPredicate) {

0 commit comments

Comments
 (0)