Skip to content

Commit f60948a

Browse files
committed
SONARJAVA-1573 Fix FP on RSPEC-2638 to respect LSP
1 parent 5fe4db7 commit f60948a

3 files changed

Lines changed: 32 additions & 17 deletions

File tree

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.sonar.java.tag.Tag;
2929
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
3030
import org.sonar.plugins.java.api.semantic.Symbol;
31+
import org.sonar.plugins.java.api.tree.AnnotationTree;
3132
import org.sonar.plugins.java.api.tree.Tree;
3233
import org.sonar.squidbridge.annotations.ActivatedByDefault;
3334
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;
@@ -71,13 +72,22 @@ private void checkContractChange(MethodTreeImpl methodTree, JavaSymbol.MethodJav
7172
for (int i = 0; i < methodTree.parameters().size(); i++) {
7273
Symbol paramSymbol = methodTree.parameters().get(i).symbol();
7374
Symbol overrideeParamSymbol = overridee.getParameters().scopeSymbols().get(i);
74-
if (nonNullVsNull(paramSymbol, overrideeParamSymbol) || nonNullVsNull(overrideeParamSymbol, paramSymbol)) {
75-
reportIssue(methodTree.parameters().get(i), "The \"" + paramSymbol.name()
76-
+ "\" parameter nullability is different in the superclass method, and that should not be changed.");
75+
if (nonNullVsNull(paramSymbol, overrideeParamSymbol)) {
76+
Tree reportTree = methodTree.parameters().get(i);
77+
for (AnnotationTree annotationTree : methodTree.parameters().get(i).modifiers().annotations()) {
78+
if(annotationTree.symbolType().is("javax.annotation.Nonnull")) {
79+
reportTree = annotationTree;
80+
}
81+
}
82+
reportIssue(reportTree, "Remove this \"Nonnull\" annotation to honor the overridden method's contract.");
7783
}
7884
}
79-
if (nonNullVsNull(methodTree.symbol(), overridee) || nonNullVsNull(overridee, methodTree.symbol())) {
80-
reportIssue(methodTree.returnType(), "The return value nullability of this method is different in the superclass, and that should not be changed.");
85+
if (nonNullVsNull(overridee, methodTree.symbol())) {
86+
for (AnnotationTree annotationTree : methodTree.modifiers().annotations()) {
87+
if(annotationTree.symbolType().is("javax.annotation.Nullable") || annotationTree.symbolType().is("javax.annotation.CheckForNull")) {
88+
reportIssue(annotationTree, "Remove this \""+ annotationTree.symbolType().name() +"\" annotation to honor the overridden method's contract.");
89+
}
90+
}
8191
}
8292
}
8393

java-checks/src/main/resources/org/sonar/l10n/java/rules/squid/S2638.html

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<p>Because a subclass instance may be cast to and treated as an instance of the superclass, overriding methods should uphold the same contracts as the ones in the superclass. Specifically, if the parameters or return type of the superclass method are marked with any of the following, that should not be changed in a subclass: <code>@Nullable</code>, <code>@CheckForNull</code>, <code>@NotNull</code>, <code>@NonNull</code>, and <code>@Nonnull</code>.</p>
1+
<p>Because a subclass instance may be cast to and treated as an instance of the superclass, overriding methods should uphold the aspects of the superclass contract that relate to the Liskov Substitution Principle. Specifically, if the parameters or return type of the superclass method are marked with any of the following: <code>@Nullable</code>, <code>@CheckForNull</code>, <code>@NotNull</code>, <code>@NonNull</code>, and <code>@Nonnull</code>, then subclass parameters are not allowed to tightened the contract, and return values are not allowed to loosen it.</p>
22

33
<h2>Noncompliant Code Example</h2>
44
<pre>
@@ -7,7 +7,7 @@ <h2>Noncompliant Code Example</h2>
77
private Season ripe;
88
private String color;
99

10-
public void setRipe(@NotNull Season ripe) {
10+
public void setRipe(@Nullable Season ripe) {
1111
this.ripe = ripe;
1212
}
1313

@@ -18,7 +18,7 @@ <h2>Noncompliant Code Example</h2>
1818

1919
public class Raspberry extends Fruit {
2020

21-
public void setRipe(@Nullable Season ripe) { // Noncompliant
21+
public void setRipe(@NotNull Season ripe) { // Noncompliant
2222
this.ripe = ripe;
2323
}
2424

@@ -28,3 +28,6 @@ <h2>Noncompliant Code Example</h2>
2828
}
2929
</pre>
3030

31+
<h2>See also</h2>
32+
<p>https://en.wikipedia.org/wiki/Liskov_substitution_principle</p>
33+

java-checks/src/test/files/checks/ChangeMethodContractCheck.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,32 @@ void foo1(@javax.annotation.CheckForNull Object a) { }
2727
void foo2(@javax.annotation.Nullable Object a) { }
2828

2929
@Override
30-
void foo3(@javax.annotation.CheckForNull Object a) { } // Noncompliant [[sc=15;ec=54]] {{The "a" parameter nullability is different in the superclass method, and that should not be changed.}}
30+
void foo3(@javax.annotation.CheckForNull Object a) { }
3131
@javax.annotation.Nullable
3232
void foo4(Object a) { }
3333
@javax.annotation.CheckForNull
3434
void foo5(Object a) { }
35-
@javax.annotation.CheckForNull
36-
void foo6(Object a) { } // Noncompliant
35+
@javax.annotation.CheckForNull // Noncompliant
36+
void foo6(Object a) { }
3737
public boolean equals(Object o) { } // compliant
3838
}
3939

4040
class C extends A {
4141
@Override
42-
void foo1(@javax.annotation.Nonnull Object a) { } // Noncompliant
42+
void foo1(@javax.annotation.Nonnull Object a) { } // Noncompliant {{Remove this "Nonnull" annotation to honor the overridden method's contract.}}
4343
@Override
4444
void foo2(@javax.annotation.Nonnull Object a) { } // Noncompliant
4545
@Override
46-
void foo3(@javax.annotation.Nullable Object a) { } // Noncompliant
46+
void foo3(@javax.annotation.Nullable Object a) { }
4747

4848
@javax.annotation.Nonnull
49-
void foo4(Object a) { } // Noncompliant
49+
@Deprecated
50+
void foo4(Object a) { }
5051
@javax.annotation.Nonnull
51-
void foo5(Object a) { } // Noncompliant
52-
@javax.annotation.Nullable
53-
void foo6(Object a) { } // Noncompliant [[sc=5;ec=9]] {{The return value nullability of this method is different in the superclass, and that should not be changed.}}
52+
void foo5(Object a) { }
53+
@Deprecated
54+
@javax.annotation.Nullable // Noncompliant [[sc=5;ec=31]] {{Remove this "Nullable" annotation to honor the overridden method's contract.}}
55+
void foo6(Object a) { }
5456

5557
public boolean equals(@javax.annotation.Nonnull Object o) { } // Noncompliant {{Equals method should accept null parameters and return false.}}
5658
}

0 commit comments

Comments
 (0)