Skip to content

Commit f940cb2

Browse files
authored
Merge pull request #19950 from tamasvajk/quality/useless-record-member
Java: Add 'Useless serialization member in record class' query
2 parents 411aa6d + 813ce7d commit f940cb2

File tree

8 files changed

+131
-0
lines changed

8 files changed

+131
-0
lines changed

java/ql/integration-tests/java/query-suite/java-code-quality-extended.qls.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNam
7777
ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql
7878
ql/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql
7979
ql/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql
80+
ql/java/ql/src/Violations of Best Practice/Records/IgnoredSerializationMembersOfRecordClass.ql
8081
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql
8182
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql
8283
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/DoNotCallFinalize.ql

java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNam
7575
ql/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql
7676
ql/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql
7777
ql/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql
78+
ql/java/ql/src/Violations of Best Practice/Records/IgnoredSerializationMembersOfRecordClass.ql
7879
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql
7980
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql
8081
ql/java/ql/src/Violations of Best Practice/Undesirable Calls/DoNotCallFinalize.ql
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
## Overview
2+
3+
Record types were introduced in Java 16 as a mechanism to provide simpler data handling as an alternative to regular classes. However, record classes behave slightly differently during serialization. Namely any `writeObject`, `readObject`, `readObjectNoData`, `writeExternal`, and `readExternal` methods and `serialPersistentFields` fields declared in these classes cannot be used to affect the serialization process of any `Record` data type.
4+
5+
## Recommendation
6+
7+
Some level of serialization customization is offered by the Java 16 Record feature. The `writeReplace` and `readResolve` methods in a record that implements `java.io.Serializable` can be used to replace the object to be serialized. Otherwise, no further customization of serialization of records is possible, and it is better to consider using a regular class implementing `java.io.Serializable` or `java.io.Externalizable` when customization is needed.
8+
9+
## Example
10+
11+
```java
12+
record T1() implements Serializable {
13+
14+
@Serial
15+
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; // NON_COMPLIANT
16+
17+
@Serial
18+
private void writeObject(ObjectOutputStream out) throws IOException {} // NON_COMPLIANT
19+
20+
@Serial
21+
private void readObject(ObjectOutputStream out) throws IOException {}// NON_COMPLIANT
22+
23+
@Serial
24+
private void readObjectNoData(ObjectOutputStream out) throws IOException { // NON_COMPLIANT
25+
}
26+
}
27+
28+
record T2() implements Externalizable {
29+
30+
@Override
31+
public void writeExternal(ObjectOutput out) throws IOException { // NON_COMPLIANT
32+
}
33+
34+
@Override
35+
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // NON_COMPLIANT
36+
}
37+
}
38+
39+
record T3() implements Serializable {
40+
41+
public Object writeReplace(ObjectOutput out) throws ObjectStreamException { // COMPLIANT
42+
return new Object();
43+
}
44+
45+
public Object readResolve(ObjectInput in) throws ObjectStreamException { // COMPLIANT
46+
return new Object();
47+
}
48+
}
49+
```
50+
51+
## References
52+
53+
- Oracle Serialization Documentation: [Serialization of Records](https://docs.oracle.com/en/java/javase/16/docs/specs/serialization/serial-arch.html#serialization-of-records)
54+
- Java Record: [Feature Specification](https://openjdk.org/jeps/395)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @id java/ignored-serialization-member-of-record-class
3+
* @name Ignored serialization member of record class
4+
* @description Using certain members of a record class during serialization will result in
5+
* those members being ignored.
6+
* @previous-id java/useless-members-of-the-records-class
7+
* @kind problem
8+
* @precision very-high
9+
* @problem.severity warning
10+
* @tags quality
11+
* reliability
12+
* correctness
13+
*/
14+
15+
import java
16+
17+
from Record record, Member m
18+
where
19+
record.getAMember() = m and
20+
m.hasName([
21+
"writeObject", "readObject", "readObjectNoData", "writeExternal", "readExternal",
22+
"serialPersistentFields"
23+
])
24+
select m, "Ignored serialization member found in record class $@.", record, record.getName()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| Test.java:7:46:7:67 | serialPersistentFields | Ignored serialization member found in record class $@. | Test.java:4:12:4:13 | T1 | T1 |
2+
| Test.java:10:18:10:28 | writeObject | Ignored serialization member found in record class $@. | Test.java:4:12:4:13 | T1 | T1 |
3+
| Test.java:13:18:13:27 | readObject | Ignored serialization member found in record class $@. | Test.java:4:12:4:13 | T1 | T1 |
4+
| Test.java:16:18:16:33 | readObjectNoData | Ignored serialization member found in record class $@. | Test.java:4:12:4:13 | T1 | T1 |
5+
| Test.java:24:17:24:29 | writeExternal | Ignored serialization member found in record class $@. | Test.java:21:12:21:13 | T2 | T2 |
6+
| Test.java:28:17:28:28 | readExternal | Ignored serialization member found in record class $@. | Test.java:21:12:21:13 | T2 | T2 |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
query: Violations of Best Practice/Records/IgnoredSerializationMembersOfRecordClass.ql
2+
postprocess: utils/test/InlineExpectationsTestQuery.ql
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import java.io.*;
2+
3+
public class Test {
4+
record T1() implements Serializable {
5+
6+
@Serial
7+
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; // $ Alert
8+
9+
@Serial
10+
private void writeObject(ObjectOutputStream out) throws IOException {} // $ Alert
11+
12+
@Serial
13+
private void readObject(ObjectOutputStream out) throws IOException {} // $ Alert
14+
15+
@Serial
16+
private void readObjectNoData(ObjectOutputStream out) throws IOException { // $ Alert
17+
}
18+
19+
}
20+
21+
record T2() implements Externalizable {
22+
23+
@Override
24+
public void writeExternal(ObjectOutput out) throws IOException { // $ Alert
25+
}
26+
27+
@Override
28+
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // $ Alert
29+
}
30+
31+
}
32+
33+
record T3() implements Serializable {
34+
35+
public Object writeReplace(ObjectOutput out) throws ObjectStreamException { // COMPLIANT
36+
return new Object();
37+
}
38+
39+
public Object readResolve(ObjectInput in) throws ObjectStreamException { // COMPLIANT
40+
return new Object();
41+
}
42+
}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//semmle-extractor-options: --javac-args -source 16 -target 16

0 commit comments

Comments
 (0)