Java: Add 'Useless serialization member in record class' query

This commit is contained in:
Tamas Vajk
2025-07-02 11:18:30 +02:00
parent 52abf3ba02
commit e0cb1792bd
6 changed files with 127 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
## Overview
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.
## Recommendation
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.
## Example
```java
record T1() implements Serializable {
@Serial
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; // NON_COMPLIANT
@Serial
private void writeObject(ObjectOutputStream out) throws IOException {} // NON_COMPLIANT
@Serial
private void readObject(ObjectOutputStream out) throws IOException {}// NON_COMPLIANT
@Serial
private void readObjectNoData(ObjectOutputStream out) throws IOException { // NON_COMPLIANT
}
}
record T2() implements Externalizable {
@Override
public void writeExternal(ObjectOutput out) throws IOException { // NON_COMPLIANT
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // NON_COMPLIANT
}
}
record T3() implements Serializable {
public Object writeReplace(ObjectOutput out) throws ObjectStreamException { // COMPLIANT
return new Object();
}
public Object readResolve(ObjectInput in) throws ObjectStreamException { // COMPLIANT
return new Object();
}
}
```
## References
- Oracle Serialization Documentation: [Serialization of Records](https://docs.oracle.com/en/java/javase/16/docs/specs/serialization/serial-arch.html#serialization-of-records)
- Java Record: [Feature Specification](https://openjdk.org/jeps/395)

View File

@@ -0,0 +1,23 @@
/**
* @id java/useless-members-of-the-records-class
* @name Useless serialization members of `Records`
* @description Using certain members of the `Records` class during serialization will result in
* those members being ignored.
* @kind problem
* @precision very-high
* @problem.severity warning
* @tags quality
* reliability
* correctness
*/
import java
from Record record, Member m
where
record.getAMember() = m and
m.hasName([
"writeObject", "readObject", "readObjectNoData", "writeExternal", "readExternal",
"serialPersistentFields"
])
select record, "Declaration of useless member $@ found.", m, m.getName()

View File

@@ -0,0 +1,42 @@
import java.io.*;
public class Test {
record T1() implements Serializable {
@Serial
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; // NON_COMPLIANT
@Serial
private void writeObject(ObjectOutputStream out) throws IOException {} // NON_COMPLIANT
@Serial
private void readObject(ObjectOutputStream out) throws IOException {}// NON_COMPLIANT
@Serial
private void readObjectNoData(ObjectOutputStream out) throws IOException { // NON_COMPLIANT
}
}
record T2() implements Externalizable {
@Override
public void writeExternal(ObjectOutput out) throws IOException { // NON_COMPLIANT
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // NON_COMPLIANT
}
}
record T3() implements Serializable {
public Object writeReplace(ObjectOutput out) throws ObjectStreamException { // COMPLIANT
return new Object();
}
public Object readResolve(ObjectInput in) throws ObjectStreamException { // COMPLIANT
return new Object();
}
}}

View File

@@ -0,0 +1,6 @@
| Test.java:4:12:4:13 | T1 | Declaration of useless member $@ found. | Test.java:7:46:7:67 | serialPersistentFields | serialPersistentFields |
| Test.java:4:12:4:13 | T1 | Declaration of useless member $@ found. | Test.java:10:18:10:28 | writeObject | writeObject |
| Test.java:4:12:4:13 | T1 | Declaration of useless member $@ found. | Test.java:13:18:13:27 | readObject | readObject |
| Test.java:4:12:4:13 | T1 | Declaration of useless member $@ found. | Test.java:16:18:16:33 | readObjectNoData | readObjectNoData |
| Test.java:21:12:21:13 | T2 | Declaration of useless member $@ found. | Test.java:24:17:24:29 | writeExternal | writeExternal |
| Test.java:21:12:21:13 | T2 | Declaration of useless member $@ found. | Test.java:28:17:28:28 | readExternal | readExternal |

View File

@@ -0,0 +1 @@
Violations of Best Practice/Records/UselessMembersOfTheRecordsClass.ql

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -source 16 -target 16