Skip to content

Commit e0cb179

Browse files
committed
Java: Add 'Useless serialization member in record class' query
1 parent 52abf3b commit e0cb179

File tree

6 files changed

+127
-0
lines changed

6 files changed

+127
-0
lines changed
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 that is an alternative to regular classes. Record classes behave slightly differently during serialization however, 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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @id java/useless-members-of-the-records-class
3+
* @name Useless serialization members of `Records`
4+
* @description Using certain members of the `Records` class during serialization will result in
5+
* those members being ignored.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity warning
9+
* @tags quality
10+
* reliability
11+
* correctness
12+
*/
13+
14+
import java
15+
16+
from Record record, Member m
17+
where
18+
record.getAMember() = m and
19+
m.hasName([
20+
"writeObject", "readObject", "readObjectNoData", "writeExternal", "readExternal",
21+
"serialPersistentFields"
22+
])
23+
select record, "Declaration of useless member $@ found.", m, m.getName()
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]; // NON_COMPLIANT
8+
9+
@Serial
10+
private void writeObject(ObjectOutputStream out) throws IOException {} // NON_COMPLIANT
11+
12+
@Serial
13+
private void readObject(ObjectOutputStream out) throws IOException {}// NON_COMPLIANT
14+
15+
@Serial
16+
private void readObjectNoData(ObjectOutputStream out) throws IOException { // NON_COMPLIANT
17+
}
18+
19+
}
20+
21+
record T2() implements Externalizable {
22+
23+
@Override
24+
public void writeExternal(ObjectOutput out) throws IOException { // NON_COMPLIANT
25+
}
26+
27+
@Override
28+
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // NON_COMPLIANT
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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| Test.java:4:12:4:13 | T1 | Declaration of useless member $@ found. | Test.java:7:46:7:67 | serialPersistentFields | serialPersistentFields |
2+
| Test.java:4:12:4:13 | T1 | Declaration of useless member $@ found. | Test.java:10:18:10:28 | writeObject | writeObject |
3+
| Test.java:4:12:4:13 | T1 | Declaration of useless member $@ found. | Test.java:13:18:13:27 | readObject | readObject |
4+
| Test.java:4:12:4:13 | T1 | Declaration of useless member $@ found. | Test.java:16:18:16:33 | readObjectNoData | readObjectNoData |
5+
| Test.java:21:12:21:13 | T2 | Declaration of useless member $@ found. | Test.java:24:17:24:29 | writeExternal | writeExternal |
6+
| Test.java:21:12:21:13 | T2 | Declaration of useless member $@ found. | Test.java:28:17:28:28 | readExternal | readExternal |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Violations of Best Practice/Records/UselessMembersOfTheRecordsClass.ql
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)