Skip to content

Commit a27c505

Browse files
SONARJAVA-6003 Fix FP on S2055 when superclass has a Lombok generated constructor (#5518)
1 parent 53e29e6 commit a27c505

File tree

4 files changed

+63
-4
lines changed

4 files changed

+63
-4
lines changed

its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,7 @@
740740
{
741741
"ruleKey": "S2055",
742742
"hasTruePositives": true,
743-
"falseNegatives": 0,
743+
"falseNegatives": 1,
744744
"falsePositives": 0
745745
},
746746
{
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"ruleKey": "S2055",
33
"hasTruePositives": true,
4-
"falseNegatives": 0,
4+
"falseNegatives": 1,
55
"falsePositives": 0
6-
}
6+
}

java-checks-test-sources/default/src/main/java/checks/serialization/SerializableSuperConstructorCheckSample.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.io.ObjectStreamException;
44
import java.io.Serializable;
5+
import lombok.AccessLevel;
6+
import lombok.NoArgsConstructor;
57

68
class NonSerializableWithoutConstructor {}
79

@@ -46,3 +48,34 @@ private S2055_Az() {}
4648
class S2055_Bz2 extends S2055_Az<String> implements Serializable {
4749
S2055_Bz2(String arg1) { super(arg1); }
4850
}
51+
52+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
53+
class NonSerializableWithLombokPrivateNoArgsConstructor {
54+
int field;
55+
56+
public NonSerializableWithLombokPrivateNoArgsConstructor(int field) {
57+
this.field = field;
58+
}
59+
}
60+
61+
@NoArgsConstructor
62+
class NonSerializableWithLombokNoArgsConstructor {
63+
int field;
64+
65+
public NonSerializableWithLombokNoArgsConstructor(int field) {
66+
this.field = field;
67+
}
68+
}
69+
70+
class S2055_LombokPrivate extends NonSerializableWithLombokPrivateNoArgsConstructor implements Serializable { // Noncompliant {{Add a no-arg constructor to "NonSerializableWithLombokPrivateNoArgsConstructor".}}
71+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
72+
S2055_LombokPrivate(int field) {
73+
super(field);
74+
}
75+
}
76+
77+
class S2055_LombokPublic extends NonSerializableWithLombokNoArgsConstructor implements Serializable { // Compliant
78+
S2055_LombokPublic(int field) {
79+
super(field);
80+
}
81+
}

java-checks/src/main/java/org/sonar/java/checks/serialization/SerializableSuperConstructorCheck.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,20 @@
2121
import java.util.List;
2222
import javax.annotation.Nullable;
2323
import org.sonar.check.Rule;
24+
import org.sonar.java.checks.helpers.AnnotationsHelper;
2425
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
2526
import org.sonar.plugins.java.api.semantic.MethodMatchers;
2627
import org.sonar.plugins.java.api.semantic.Symbol;
28+
import org.sonar.plugins.java.api.semantic.SymbolMetadata;
2729
import org.sonar.plugins.java.api.semantic.Type;
2830
import org.sonar.plugins.java.api.tree.ClassTree;
2931
import org.sonar.plugins.java.api.tree.Tree;
3032

3133
@Rule(key = "S2055")
3234
public class SerializableSuperConstructorCheck extends IssuableSubscriptionVisitor {
3335

36+
private static final String LOMBOK_NO_ARGS_CONSTRUCTOR_ANNOTATION = "lombok.NoArgsConstructor";
37+
3438
private static final MethodMatchers WRITE_REPLACE = MethodMatchers.create()
3539
.ofAnyType()
3640
.names("writeReplace")
@@ -60,7 +64,8 @@ private static boolean isNotSerializableMissingNoArgConstructor(@Nullable Type s
6064
return superclass != null
6165
&& !superclass.isUnknown()
6266
&& !isSerializable(superclass)
63-
&& !hasNonPrivateNoArgConstructor(superclass);
67+
&& !hasNonPrivateNoArgConstructor(superclass)
68+
&& !hasCompliantGeneratedNoArgConstructor(superclass);
6469
}
6570

6671
private static boolean isSerializable(Type type) {
@@ -80,6 +85,27 @@ private static boolean hasNonPrivateNoArgConstructor(Type type) {
8085
return constructors.isEmpty();
8186
}
8287

88+
private static boolean hasCompliantGeneratedNoArgConstructor(Type type) {
89+
return type.symbol()
90+
.metadata()
91+
.annotations()
92+
.stream()
93+
.anyMatch(annotation -> isLombokNoArgConstructorGenerator(annotation.symbol().type()) && !hasPrivateAccess(annotation));
94+
}
95+
96+
private static boolean isLombokNoArgConstructorGenerator(Type symbolType) {
97+
if (symbolType.isUnknown()) {
98+
return AnnotationsHelper.annotationTypeIdentifier(LOMBOK_NO_ARGS_CONSTRUCTOR_ANNOTATION).equals(symbolType.name());
99+
}
100+
return LOMBOK_NO_ARGS_CONSTRUCTOR_ANNOTATION.equals(symbolType.fullyQualifiedName());
101+
}
102+
103+
private static boolean hasPrivateAccess(SymbolMetadata.AnnotationInstance annotation) {
104+
return annotation.values()
105+
.stream()
106+
.anyMatch(v -> "access".equals(v.name()) && "PRIVATE".equals(((Symbol) v.value()).name()));
107+
}
108+
83109
private static boolean implementsSerializableMethods(Symbol.TypeSymbol classSymbol) {
84110
return classSymbol.memberSymbols().stream().anyMatch(WRITE_REPLACE::matches);
85111
}

0 commit comments

Comments
 (0)