Skip to content

Commit 4b51016

Browse files
authored
SONARJAVA-4395 Rule S1114: In ObjectFinalizeOverridenCallsSuperFinalizeCheck lastStatementTree field is not always cleaned (#4276)
1 parent 6e45082 commit 4b51016

File tree

2 files changed

+141
-40
lines changed

2 files changed

+141
-40
lines changed

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ protected void finalize() throws Throwable { // Noncompliant {{Add a call to su
118118
}
119119
}
120120

121+
abstract class S1114_Class4 extends S1114_Class1 {
122+
@Override
123+
abstract protected void finalize() throws Throwable;
124+
}
125+
121126
class S1114_Class1 {
122127
@Override
123128
protected void finalize() throws Throwable { // Compliant, superclass is java.lang.Object
@@ -134,3 +139,77 @@ protected void finalize() throws Throwable {
134139
super.finalize();
135140
}
136141
}
142+
143+
class CutControlFlowInnerClass extends S1114_Class1 {
144+
@Override
145+
protected void finalize() throws Throwable { // Noncompliant, there is no call to finalize here
146+
class InnerClass {
147+
void foo() throws Throwable {
148+
super.finalize();
149+
}
150+
}
151+
}
152+
}
153+
154+
class CutControlFlowLambda extends S1114_Class1 {
155+
@Override
156+
protected void finalize() throws Throwable { // Noncompliant, there is no call to finalize here
157+
158+
ThrowingRunnable r = () -> super.finalize();
159+
foo();
160+
}
161+
162+
@FunctionalInterface
163+
private static interface ThrowingRunnable {
164+
public void run() throws Throwable;
165+
}
166+
}
167+
168+
class ScanAlsoInnerClasses extends S1114_Class1 {
169+
@Override
170+
protected void finalize() throws Throwable { // Noncompliant, there is no call to finalize here
171+
class InnerClassCompliant extends S1114_Class1 {
172+
@Override
173+
public void finalize() throws Throwable { // Compliant, this call is not inside the control flow of a finalize method
174+
super.finalize();
175+
}
176+
}
177+
178+
class InnerClassNonCompliant extends S1114_Class1 {
179+
@Override
180+
public void finalize() throws Throwable { // Noncompliant, there is no call to finalize here
181+
foo();
182+
}
183+
}
184+
}
185+
186+
protected void bar() throws Throwable { // Compliant, this is not a finalize method.
187+
class InnerClassCompliant extends S1114_Class1 {
188+
@Override
189+
public void finalize() throws Throwable { // Compliant, this call is not inside the control flow of a finalize method
190+
super.finalize();
191+
}
192+
}
193+
194+
class InnerClassNonCompliant extends S1114_Class1 {
195+
@Override
196+
public void finalize() throws Throwable { // Noncompliant, there is no call to finalize here
197+
foo();
198+
}
199+
}
200+
}
201+
202+
class InnerClassCompliant extends S1114_Class1 {
203+
@Override
204+
public void finalize() throws Throwable { // Compliant, this call is not inside the control flow of a finalize method
205+
super.finalize();
206+
}
207+
}
208+
209+
class InnerClassNonCompliant extends S1114_Class1 {
210+
@Override
211+
public void finalize() throws Throwable { // Noncompliant, there is no call to finalize here
212+
foo();
213+
}
214+
}
215+
}

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

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@
1919
*/
2020
package org.sonar.java.checks;
2121

22-
import java.util.Arrays;
2322
import java.util.List;
2423
import javax.annotation.Nullable;
2524
import org.sonar.check.Rule;
2625
import org.sonarsource.analyzer.commons.collections.ListUtils;
2726
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
2827
import org.sonar.plugins.java.api.semantic.MethodMatchers;
2928
import org.sonar.plugins.java.api.semantic.Symbol;
29+
import org.sonar.plugins.java.api.semantic.Symbol.TypeSymbol;
3030
import org.sonar.plugins.java.api.semantic.Type;
31+
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
3132
import org.sonar.plugins.java.api.tree.BlockTree;
33+
import org.sonar.plugins.java.api.tree.ClassTree;
3234
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
3335
import org.sonar.plugins.java.api.tree.IdentifierTree;
36+
import org.sonar.plugins.java.api.tree.LambdaExpressionTree;
3437
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
3538
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
3639
import org.sonar.plugins.java.api.tree.MethodTree;
@@ -47,76 +50,95 @@ public class ObjectFinalizeOverridenCallsSuperFinalizeCheck extends IssuableSubs
4750
private static final MethodMatchers FINALIZE_MATCHER = MethodMatchers.create()
4851
.ofAnyType().names(FINALIZE).addWithoutParametersMatcher().build();
4952

50-
private MethodInvocationTree lastStatementTree;
51-
5253
@Override
5354
public List<Tree.Kind> nodesToVisit() {
54-
return Arrays.asList(Tree.Kind.METHOD, Tree.Kind.METHOD_INVOCATION);
55+
return List.of(Tree.Kind.METHOD);
5556
}
5657

5758
@Override
5859
public void visitNode(Tree tree) {
59-
if (tree.is(Tree.Kind.METHOD_INVOCATION)) {
60-
MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;
61-
if (methodInvocationTree.methodSelect().is(Tree.Kind.MEMBER_SELECT)) {
62-
MemberSelectExpressionTree mset = (MemberSelectExpressionTree) methodInvocationTree.methodSelect();
63-
if (FINALIZE.equals(mset.identifier().name()) && mset.expression().is(Tree.Kind.IDENTIFIER) && "super".equals(((IdentifierTree) mset.expression()).name())) {
64-
lastStatementTree = methodInvocationTree;
60+
61+
MethodTree methodTree = (MethodTree) tree;
62+
if (isFinalizeOverriddenMethod(methodTree)) {
63+
BlockTree blockTree = methodTree.block();
64+
if (blockTree != null) {
65+
MethodInvocationTree lastSuperFinalizeInvocation = findLastSuperFinalizeInvocation(blockTree);
66+
if (lastSuperFinalizeInvocation == null) {
67+
reportIssue(methodTree.simpleName(), "Add a call to super.finalize() at the end of this Object.finalize() implementation.");
68+
} else if (!isLastStatement(blockTree, lastSuperFinalizeInvocation)) {
69+
reportIssue(lastSuperFinalizeInvocation, "Move this super.finalize() call to the end of this Object.finalize() implementation.");
6570
}
6671
}
67-
} else if (FINALIZE_MATCHER.matches((MethodTree) tree)) {
68-
lastStatementTree = null;
6972
}
7073
}
7174

72-
@Override
73-
public void leaveNode(Tree tree) {
74-
if (tree.is(Tree.Kind.METHOD)) {
75-
MethodTree methodTree = (MethodTree) tree;
76-
if (FINALIZE_MATCHER.matches(methodTree) && doesOverrideFinalize(methodTree.symbol().owner())) {
77-
if (lastStatementTree == null) {
78-
reportIssue(methodTree.simpleName(), "Add a call to super.finalize() at the end of this Object.finalize() implementation.");
79-
} else if (!isLastStatement(methodTree, lastStatementTree)) {
80-
reportIssue(lastStatementTree, "Move this super.finalize() call to the end of this Object.finalize() implementation.");
81-
}
75+
private static boolean isFinalizeOverriddenMethod(MethodTree methodTree) {
76+
return FINALIZE_MATCHER.matches(methodTree) && doesOverrideFinalize((TypeSymbol) methodTree.symbol().owner());
77+
}
78+
79+
private static boolean doesOverrideFinalize(TypeSymbol typeSymbol) {
80+
Type superClassType = typeSymbol.superClass();
81+
while (superClassType != null && !superClassType.is("java.lang.Object")) {
82+
Symbol.TypeSymbol currentClass = superClassType.symbol();
83+
if (currentClass.lookupSymbols(FINALIZE).stream().anyMatch(FINALIZE_MATCHER::matches)) {
84+
return true;
8285
}
86+
superClassType = currentClass.superClass();
8387
}
88+
return false;
8489
}
8590

86-
private static boolean isLastStatement(MethodTree methodTree, MethodInvocationTree lastStatementTree) {
87-
BlockTree blockTree = methodTree.block();
88-
if (blockTree != null
89-
&& blockTree.body().stream().anyMatch(statement ->
90-
statement.is(Tree.Kind.TRY_STATEMENT)&& isLastStatement(((TryStatementTree) statement).finallyBlock(), lastStatementTree))) {
91+
@Nullable
92+
private static MethodInvocationTree findLastSuperFinalizeInvocation(BlockTree blockTree) {
93+
FindLastSuperFinalizeInvocationVisitor visitor = new FindLastSuperFinalizeInvocationVisitor();
94+
blockTree.accept(visitor);
95+
return visitor.lastSuperFinalizeInvocation;
96+
}
97+
98+
private static boolean isLastStatement(BlockTree blockTree, MethodInvocationTree lastStatementTree) {
99+
if (blockTree.body().stream().anyMatch(statement ->
100+
statement.is(Tree.Kind.TRY_STATEMENT) && isLastStatementInner(((TryStatementTree) statement).finallyBlock(), lastStatementTree)
101+
)) {
91102
return true;
92103
}
93-
return isLastStatement(blockTree, lastStatementTree);
104+
return isLastStatementInner(blockTree, lastStatementTree);
94105
}
95106

96-
private static boolean isLastStatement(@Nullable BlockTree blockTree, MethodInvocationTree lastStatementTree) {
107+
private static boolean isLastStatementInner(@Nullable BlockTree blockTree, MethodInvocationTree lastStatementTree) {
97108
if (blockTree != null) {
98109
StatementTree last = ListUtils.getLast(blockTree.body());
99110
if (last.is(Tree.Kind.EXPRESSION_STATEMENT)) {
100111
return lastStatementTree.equals(((ExpressionStatementTree) last).expression());
101112
} else if (last.is(Tree.Kind.TRY_STATEMENT)) {
102-
return isLastStatement(((TryStatementTree) last).finallyBlock(), lastStatementTree);
113+
return isLastStatementInner(((TryStatementTree) last).finallyBlock(), lastStatementTree);
103114
}
104115
}
105116
return false;
106117
}
107118

108-
private static boolean doesOverrideFinalize(Symbol classSymbol) {
109-
if (classSymbol.isTypeSymbol()) {
110-
Type superClassType = ((Symbol.TypeSymbol) classSymbol).superClass();
111-
while (superClassType != null && !superClassType.is("java.lang.Object")) {
112-
Symbol.TypeSymbol currentClass = superClassType.symbol();
113-
if (currentClass.lookupSymbols(FINALIZE).stream().anyMatch(FINALIZE_MATCHER::matches)) {
114-
return true;
119+
private static class FindLastSuperFinalizeInvocationVisitor extends BaseTreeVisitor {
120+
121+
@Nullable
122+
public MethodInvocationTree lastSuperFinalizeInvocation;
123+
124+
@Override
125+
public void visitMethodInvocation(MethodInvocationTree tree) {
126+
if (tree.methodSelect().is(Tree.Kind.MEMBER_SELECT)) {
127+
MemberSelectExpressionTree mset = (MemberSelectExpressionTree) tree.methodSelect();
128+
if (FINALIZE.equals(mset.identifier().name()) && mset.expression().is(Tree.Kind.IDENTIFIER) && "super".equals(((IdentifierTree) mset.expression()).name())) {
129+
lastSuperFinalizeInvocation = tree;
115130
}
116-
superClassType = currentClass.superClass();
117131
}
118132
}
119-
return false;
120-
}
121133

134+
@Override
135+
public void visitClass(ClassTree tree) {
136+
// cut analysis on inner and anonymous classes, because we want to analyze actual control flow of finalize method only
137+
}
138+
139+
@Override
140+
public void visitLambdaExpression(LambdaExpressionTree lambdaExpressionTree) {
141+
// cut analysis on lambda function bodies, because we want to analyze actual control flow of finalize method only
142+
}
143+
}
122144
}

0 commit comments

Comments
 (0)