Skip to content

Commit 6c9e827

Browse files
SONARJAVA-5697 S2441 and S2118 Fix FP with missing semantics of Serializable (#5260)
1 parent f684952 commit 6c9e827

6 files changed

Lines changed: 107 additions & 0 deletions

File tree

java-checks-common/src/main/java/org/sonar/java/checks/helpers/ExpressionsHelper.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
import static org.sonar.java.checks.helpers.ReassignmentFinder.getInitializerOrExpression;
4949
import static org.sonar.java.checks.helpers.ReassignmentFinder.getReassignments;
50+
import static org.sonar.java.model.JUtils.hasUnknownTypeInHierarchy;
5051

5152
public class ExpressionsHelper {
5253

@@ -169,6 +170,12 @@ public List<JavaFileScannerContext.Location> valuePath() {
169170
}
170171
}
171172

173+
/**
174+
* Checks if the expression is non-serializable.
175+
*
176+
* <p> If the result cannot be determined due to incomplete semantics,
177+
* the method returns false.
178+
*/
172179
public static boolean isNotSerializable(ExpressionTree expression) {
173180
Type symbolType = expression.symbolType();
174181
if (symbolType.isUnknown()) {
@@ -197,6 +204,9 @@ private static boolean isNonSerializable(Type type) {
197204
type.isSubtypeOf("java.util.Enumeration")) {
198205
return false;
199206
}
207+
if(hasUnknownTypeInHierarchy(type.symbol())) {
208+
return false;
209+
}
200210
Type erasedType = type.erasure();
201211
return erasedType.equals(type) || isNonSerializable(erasedType);
202212
}

java-checks-common/src/test/java/org/sonar/java/checks/helpers/ExpressionsHelperTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
import javax.annotation.Nullable;
2121
import org.junit.jupiter.api.Test;
2222
import org.sonar.plugins.java.api.semantic.Symbol;
23+
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
24+
import org.sonar.plugins.java.api.tree.ExpressionTree;
2325
import org.sonar.plugins.java.api.tree.IdentifierTree;
26+
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
2427
import org.sonar.plugins.java.api.tree.MethodTree;
2528

2629
import static org.assertj.core.api.Assertions.assertThat;
@@ -149,4 +152,53 @@ private <T> void assertValueResolution(String code, @Nullable T target) {
149152
Boolean value = ExpressionsHelper.getConstantValueAsBoolean(a).value();
150153
assertThat(value).isEqualTo(target);
151154
}
155+
156+
@Test
157+
void isNonSerializable_nonSerializable() {
158+
String code = newCode(
159+
"static class C {}",
160+
"private C c;",
161+
"void f() {",
162+
" System.out.println(c);",
163+
"}"
164+
);
165+
ExpressionTree expr = getCallArgument(code);
166+
assertThat(ExpressionsHelper.isNotSerializable(expr)).isTrue();
167+
}
168+
169+
@Test
170+
void isNonSerializable_javaIoSerializable() {
171+
String code = newCode(
172+
"static class C implements java.io.Serializable {}",
173+
"private C c;",
174+
"void f() {",
175+
" System.out.println(c);",
176+
"}"
177+
);
178+
ExpressionTree expr = getCallArgument(code);
179+
assertThat(ExpressionsHelper.isNotSerializable(expr)).isFalse();
180+
}
181+
182+
@Test
183+
void isNonSerializable_missingImportSerializable() {
184+
String code = newCode(
185+
"static class C implements Serializable {}",
186+
"private C c;",
187+
"void f() {",
188+
" System.out.println(c);",
189+
"}"
190+
);
191+
ExpressionTree expr = getCallArgument(code);
192+
// We want "false" in case we cannot resolve implemented interfaces,
193+
// to avoid FPs in the checks that use this helper.
194+
assertThat(ExpressionsHelper.isNotSerializable(expr)).isFalse();
195+
}
196+
197+
/** Returns the {@code c} argument to {@code System.out.println(c)}. */
198+
private static ExpressionTree getCallArgument(String code) {
199+
var methodTree = (MethodTree) classTree(code).members().get(2);
200+
var exprStmtTree = (ExpressionStatementTree) methodTree.block().body().get(0);
201+
var methodInvocationTree = (MethodInvocationTree) exprStmtTree.expression();
202+
return methodInvocationTree.arguments().get(0);
203+
}
152204
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package checks.serialization;
2+
3+
import java.io.IOException;
4+
import java.io.ObjectOutputStream;
5+
6+
class NonSerializableWriteCheckMissingImportSample {
7+
public record R(String foo, Boolean bar) implements Serializable {}
8+
9+
public void writeOut(ObjectOutputStream oos) throws IOException {
10+
R r = new R("foo", true);
11+
oos.writeObject(r);
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package checks.serialization;
2+
3+
import javax.servlet.http.HttpSession;
4+
5+
class SerializableObjectInSessionCheckMissingImportSample {
6+
public static record R(String foo, Boolean bar) implements Serializable {}
7+
8+
private HttpSession session = null;
9+
10+
public void usage() {
11+
R r = new R("foo", true);
12+
session.setAttribute("foo", r);
13+
}
14+
}

java-checks/src/test/java/org/sonar/java/checks/serialization/NonSerializableWriteCheckTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.sonar.java.checks.verifier.CheckVerifier;
2121

2222
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
23+
import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath;
2324

2425
class NonSerializableWriteCheckTest {
2526

@@ -31,6 +32,14 @@ void test() {
3132
.verifyIssues();
3233
}
3334

35+
@Test
36+
void test_missing_import() {
37+
CheckVerifier.newVerifier()
38+
.onFile(nonCompilingTestSourcesPath("checks/serialization/NonSerializableWriteCheckMissingImportSample.java"))
39+
.withCheck(new NonSerializableWriteCheck())
40+
.verifyNoIssues();
41+
}
42+
3443
@Test
3544
void unresolved() {
3645
CheckVerifier.newVerifier()

java-checks/src/test/java/org/sonar/java/checks/serialization/SerializableObjectInSessionCheckTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.sonar.java.checks.verifier.CheckVerifier;
2121

2222
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
23+
import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath;
2324

2425
class SerializableObjectInSessionCheckTest {
2526

@@ -31,6 +32,14 @@ void test() {
3132
.verifyIssues();
3233
}
3334

35+
@Test
36+
void test_missing_import() {
37+
CheckVerifier.newVerifier()
38+
.onFile(nonCompilingTestSourcesPath("checks/serialization/SerializableObjectInSessionCheckMissingImportSample.java"))
39+
.withCheck(new SerializableObjectInSessionCheck())
40+
.verifyNoIssues();
41+
}
42+
3443
@Test
3544
void unresolved() {
3645
CheckVerifier.newVerifier()

0 commit comments

Comments
 (0)