Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@
{
"ruleKey": "S2055",
"hasTruePositives": true,
"falseNegatives": 0,
"falseNegatives": 1,
"falsePositives": 0
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ruleKey": "S2055",
"hasTruePositives": true,
"falseNegatives": 0,
"falseNegatives": 1,
"falsePositives": 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.ObjectStreamException;
import java.io.Serializable;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

class NonSerializableWithoutConstructor {}

Expand Down Expand Up @@ -46,3 +48,34 @@ private S2055_Az() {}
class S2055_Bz2 extends S2055_Az<String> implements Serializable {
S2055_Bz2(String arg1) { super(arg1); }
}

@NoArgsConstructor(access = AccessLevel.PRIVATE)
class NonSerializableWithLombokPrivateNoArgsConstructor {
int field;

public NonSerializableWithLombokPrivateNoArgsConstructor(int field) {
this.field = field;
}
}

@NoArgsConstructor
class NonSerializableWithLombokNoArgsConstructor {
int field;

public NonSerializableWithLombokNoArgsConstructor(int field) {
this.field = field;
}
}

class S2055_LombokPrivate extends NonSerializableWithLombokPrivateNoArgsConstructor implements Serializable { // Noncompliant {{Add a no-arg constructor to "NonSerializableWithLombokPrivateNoArgsConstructor".}}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
S2055_LombokPrivate(int field) {
super(field);
}
}

class S2055_LombokPublic extends NonSerializableWithLombokNoArgsConstructor implements Serializable { // Compliant
S2055_LombokPublic(int field) {
super(field);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@
import java.util.List;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.AnnotationsHelper;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.SymbolMetadata;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.Tree;

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

private static final String LOMBOK_NO_ARGS_CONSTRUCTOR_ANNOTATION = "lombok.NoArgsConstructor";

private static final MethodMatchers WRITE_REPLACE = MethodMatchers.create()
.ofAnyType()
.names("writeReplace")
Expand Down Expand Up @@ -60,7 +64,8 @@ private static boolean isNotSerializableMissingNoArgConstructor(@Nullable Type s
return superclass != null
&& !superclass.isUnknown()
&& !isSerializable(superclass)
&& !hasNonPrivateNoArgConstructor(superclass);
&& !hasNonPrivateNoArgConstructor(superclass)
&& !hasCompliantGeneratedNoArgConstructor(superclass);
}

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

private static boolean hasCompliantGeneratedNoArgConstructor(Type type) {
return type.symbol()
.metadata()
.annotations()
.stream()
.anyMatch(annotation -> isLombokNoArgConstructorGenerator(annotation.symbol().type()) && !hasPrivateAccess(annotation));
}

private static boolean isLombokNoArgConstructorGenerator(Type symbolType) {
if (symbolType.isUnknown()) {
return AnnotationsHelper.annotationTypeIdentifier(LOMBOK_NO_ARGS_CONSTRUCTOR_ANNOTATION).equals(symbolType.name());
}
return LOMBOK_NO_ARGS_CONSTRUCTOR_ANNOTATION.equals(symbolType.fullyQualifiedName());
}

private static boolean hasPrivateAccess(SymbolMetadata.AnnotationInstance annotation) {
return annotation.values()
.stream()
.anyMatch(v -> "access".equals(v.name()) && "PRIVATE".equals(((Symbol) v.value()).name()));
}

private static boolean implementsSerializableMethods(Symbol.TypeSymbol classSymbol) {
return classSymbol.memberSymbols().stream().anyMatch(WRITE_REPLACE::matches);
}
Expand Down
Loading