Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/Duration.java
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/Instant.java
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/LocalDate.java
Original file line number Diff line number Diff line change
Expand Up @@ -2203,6 +2203,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/LocalDateTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -2006,6 +2006,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/LocalTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/MonthDay.java
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/OffsetDateTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/OffsetTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/Period.java
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,7 @@ private Object writeReplace() {
* @throws java.io.InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/Year.java
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/YearMonth.java
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
1 change: 1 addition & 0 deletions src/java.base/share/classes/java/time/ZonedDateTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,7 @@ private Object writeReplace() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ public int hashCode() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other uses of @SuppressSerial in the JDK tend to have a comment explaining why the warning is being suppressed.

private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ public int hashCode() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ public int hashCode() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ public int hashCode() {
* @throws InvalidObjectException always
*/
@java.io.Serial
@SuppressWarnings("serial")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Deserialization via serialization delegate");
}
Expand Down
103 changes: 60 additions & 43 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
Original file line number Diff line number Diff line change
Expand Up @@ -5054,7 +5054,8 @@ public Void visitTypeAsClass(TypeElement e,

// Check declarations of serialization-related methods and
// fields
final boolean[] hasWriteReplace = {false};
final Map<String, Symbol> declaredSerialMethodNames = new HashMap<>();
final boolean[] isSerialMethodCorrect = new boolean[] { false };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is a one-element boolean array being used as opposed to a (non-final) boolean?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is passed to a lambda expression, which requires final local variables.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is no longer used outside of runUnderLint, we can convert it to a local at the switch where it is assigned.

for(Symbol el : c.getEnclosedElements()) {
runUnderLint(el, p, (enclosed, tree) -> {
String name = null;
Expand Down Expand Up @@ -5128,19 +5129,22 @@ public Void visitTypeAsClass(TypeElement e,
name = method.getSimpleName().toString();
if (serialMethodNames.contains(name)) {
switch (name) {
case "writeObject" -> checkWriteObject(tree, e, method);
case "writeReplace" -> {hasWriteReplace[0] = true; hasAppropriateWriteReplace(tree, method, true);}
case "readObject" -> checkReadObject(tree,e, method);
case "readObjectNoData" -> checkReadObjectNoData(tree, e, method);
case "readResolve" -> checkReadResolve(tree, e, method);
case "writeObject" -> isSerialMethodCorrect[0] = hasAppropriateWriteObject(tree, e, method);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some changes in this method and in the methods invoked here were not strictly necessary but were done for the sake of uniformity

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we convert this to a switch expression, like:

isSerialMethodCorrect[0] = switch (name) {
    case "writeObject"      -> hasAppropriateWriteObject(tree, e, method);

and so on.

case "writeReplace" -> isSerialMethodCorrect[0] = hasAppropriateWriteReplace(tree, method, true);
case "readObject" -> isSerialMethodCorrect[0] = hasAppropriateReadObject(tree, e, method);
case "readObjectNoData" -> isSerialMethodCorrect[0] = hasAppropriateReadObjectNoData(tree, e, method);
case "readResolve" -> isSerialMethodCorrect[0] = hasAppropriateReadResolve(tree, e, method);
default -> throw new AssertionError();
}
if (isSerialMethodCorrect[0]) {
declaredSerialMethodNames.put(name, el);
}
}
}
}
});
}
if (!hasWriteReplace[0] &&
if (declaredSerialMethodNames.get("writeReplace") == null &&
(c.isValueClass() || hasAbstractValueSuperClass(c, Set.of(syms.numberType.tsym))) &&
!c.isAbstract() && !c.isRecord() &&
types.unboxedType(c.type) == Type.noType) {
Expand All @@ -5160,6 +5164,14 @@ public Void visitTypeAsClass(TypeElement e,
LintWarnings.SerializableValueClassWithoutWriteReplace2);
}
}
if (c.isValueClass()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a short comment here explaining the rules for serializable value classes would be helpful.

for (Map.Entry<String, Symbol> entry : declaredSerialMethodNames.entrySet()) {
String key = entry.getKey();
if (key.equals("writeObject") || key.equals("readObject") || key.equals("readObjectNoData")) {
log.warning(TreeInfo.diagnosticPositionFor(entry.getValue(), p), LintWarnings.IneffectualSerialMethodValueClass(key));
}
}
}
return null;
}

Expand Down Expand Up @@ -5299,17 +5311,17 @@ private void checkSerialPersistentFields(JCClassDecl tree, Element e, VarSymbol
}
}

private void checkWriteObject(JCClassDecl tree, Element e, MethodSymbol method) {
private boolean hasAppropriateWriteObject(JCClassDecl tree, Element e, MethodSymbol method) {
// The "synchronized" modifier is seen in the wild on
// readObject and writeObject methods and is generally
// innocuous.

// private void writeObject(ObjectOutputStream stream) throws IOException
checkPrivateNonStaticMethod(tree, method);
isExpectedReturnType(tree, method, syms.voidType, true);
checkOneArg(tree, e, method, syms.objectOutputStreamType);
hasExpectedExceptions(tree, method, true, syms.ioExceptionType);
checkExternalizable(tree, e, method);
return isPrivateNonStaticMethod(tree, method) & // no short-circuit we need to evaluate all
isExpectedReturnType(tree, method, syms.voidType, true) &
hasExpectedArg(tree, method, syms.objectOutputStreamType) &
hasExpectedExceptions(tree, method, true, syms.ioExceptionType) &
checkExternalizable(tree, e, method);
}

private boolean hasAppropriateWriteReplace(JCClassDecl tree, MethodSymbol method, boolean warn) {
Expand All @@ -5318,45 +5330,45 @@ private boolean hasAppropriateWriteReplace(JCClassDecl tree, MethodSymbol method

// Excluding abstract, could have a more complicated
// rule based on abstract-ness of the class
return isConcreteInstanceMethod(tree, method, warn) &&
isExpectedReturnType(tree, method, syms.objectType, warn) &&
hasNoArgs(tree, method, warn) &&
return isConcreteInstanceMethod(tree, method, warn) & // no short-circuit we need to evaluate all
isExpectedReturnType(tree, method, syms.objectType, warn) &
hasNoArgs(tree, method, warn) &
hasExpectedExceptions(tree, method, warn, syms.objectStreamExceptionType);
}

private void checkReadObject(JCClassDecl tree, Element e, MethodSymbol method) {
private boolean hasAppropriateReadObject(JCClassDecl tree, Element e, MethodSymbol method) {
// The "synchronized" modifier is seen in the wild on
// readObject and writeObject methods and is generally
// innocuous.

// private void readObject(ObjectInputStream stream)
// throws IOException, ClassNotFoundException
checkPrivateNonStaticMethod(tree, method);
isExpectedReturnType(tree, method, syms.voidType, true);
checkOneArg(tree, e, method, syms.objectInputStreamType);
hasExpectedExceptions(tree, method, true, syms.ioExceptionType, syms.classNotFoundExceptionType);
checkExternalizable(tree, e, method);
return isPrivateNonStaticMethod(tree, method) & // no short-circuit we need to evaluate all
isExpectedReturnType(tree, method, syms.voidType, true) &
hasExpectedArg(tree, method, syms.objectInputStreamType) &
hasExpectedExceptions(tree, method, true, syms.ioExceptionType, syms.classNotFoundExceptionType) &
checkExternalizable(tree, e, method);
}

private void checkReadObjectNoData(JCClassDecl tree, Element e, MethodSymbol method) {
private boolean hasAppropriateReadObjectNoData(JCClassDecl tree, Element e, MethodSymbol method) {
// private void readObjectNoData() throws ObjectStreamException
checkPrivateNonStaticMethod(tree, method);
isExpectedReturnType(tree, method, syms.voidType, true);
hasNoArgs(tree, method, true);
hasExpectedExceptions(tree, method, true, syms.objectStreamExceptionType);
checkExternalizable(tree, e, method);
return isPrivateNonStaticMethod(tree, method) &
isExpectedReturnType(tree, method, syms.voidType, true) &
hasNoArgs(tree, method, true) &
hasExpectedExceptions(tree, method, true, syms.objectStreamExceptionType) &
checkExternalizable(tree, e, method);
}

private void checkReadResolve(JCClassDecl tree, Element e, MethodSymbol method) {
private boolean hasAppropriateReadResolve(JCClassDecl tree, Element e, MethodSymbol method) {
// ANY-ACCESS-MODIFIER Object readResolve()
// throws ObjectStreamException

// Excluding abstract, could have a more complicated
// rule based on abstract-ness of the class
isConcreteInstanceMethod(tree, method, true);
isExpectedReturnType(tree, method, syms.objectType, true);
hasNoArgs(tree, method, true);
hasExpectedExceptions(tree, method, true, syms.objectStreamExceptionType);
return isConcreteInstanceMethod(tree, method, true) & // no short-circuit we need to evaluate all
isExpectedReturnType(tree, method, syms.objectType, true) &
hasNoArgs(tree, method, true) &
hasExpectedExceptions(tree, method, true, syms.objectStreamExceptionType);
}

private void checkWriteExternalRecord(JCClassDecl tree, Element e, MethodSymbol method, boolean isExtern) {
Expand All @@ -5378,19 +5390,23 @@ private void checkExternMethodRecord(JCClassDecl tree, Element e, MethodSymbol m
}
}

void checkPrivateNonStaticMethod(JCClassDecl tree, MethodSymbol method) {
boolean isPrivateNonStaticMethod(JCClassDecl tree, MethodSymbol method) {
var flags = method.flags();
boolean result = true;
if ((flags & PRIVATE) == 0) {
log.warning(
TreeInfo.diagnosticPositionFor(method, tree),
LintWarnings.SerialMethodNotPrivate(method.getSimpleName()));
result = false;
}

if ((flags & STATIC) != 0) {
log.warning(
TreeInfo.diagnosticPositionFor(method, tree),
LintWarnings.SerialMethodStatic(method.getSimpleName()));
result = false;
}
return result;
}

/**
Expand Down Expand Up @@ -5607,7 +5623,7 @@ public Void visitTypeAsRecord(TypeElement e,
var method = (MethodSymbol)enclosed;
switch(name) {
case "writeReplace" -> hasAppropriateWriteReplace(tree, method, true);
case "readResolve" -> checkReadResolve(tree, e, method);
case "readResolve" -> hasAppropriateReadResolve(tree, e, method);

case "writeExternal" -> checkWriteExternalRecord(tree, e, method, isExtern);
case "readExternal" -> checkReadExternalRecord(tree, e, method, isExtern);
Expand Down Expand Up @@ -5660,19 +5676,17 @@ private boolean isExpectedReturnType(JCClassDecl tree,
return true;
}

private void checkOneArg(JCClassDecl tree,
Element enclosing,
MethodSymbol method,
Type expectedType) {
String name = method.getSimpleName().toString();
private boolean hasExpectedArg(JCClassDecl tree,
MethodSymbol method,
Type expectedType) {

var parameters= method.getParameters();

if (parameters.size() != 1) {
log.warning(
TreeInfo.diagnosticPositionFor(method, tree),
LintWarnings.SerialMethodOneArg(method.getSimpleName(), parameters.size()));
return;
return false;
}

Type parameterType = parameters.get(0).asType();
Expand All @@ -5682,7 +5696,9 @@ private void checkOneArg(JCClassDecl tree,
LintWarnings.SerialMethodParameterType(method.getSimpleName(),
expectedType,
parameterType));
return false;
}
return true;
}

private boolean hasExactlyOneArgWithType(JCClassDecl tree,
Expand All @@ -5708,14 +5724,15 @@ boolean hasNoArgs(JCClassDecl tree, MethodSymbol method, boolean warn) {
return true;
}

private void checkExternalizable(JCClassDecl tree, Element enclosing, MethodSymbol method) {
private boolean checkExternalizable(JCClassDecl tree, Element enclosing, MethodSymbol method) {
// If the enclosing class is externalizable, warn for the method
if (isExternalizable((Type)enclosing.asType())) {
log.warning(
TreeInfo.diagnosticPositionFor(method, tree),
LintWarnings.IneffectualSerialMethodExternalizable(method.getSimpleName()));
return false;
}
return;
return true;
}

private boolean hasExpectedExceptions(JCClassDecl tree,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2176,6 +2176,11 @@ compiler.warn.serializable.value.class.without.write.replace.1=\
compiler.warn.serializable.value.class.without.write.replace.2=\
serializable class does not declare, or inherits, a writeReplace method

# 0: string
# lint: serial
compiler.warn.ineffectual.serial.method.value.class=\
serialization-related method ''{0}'' is not effective in a value class

# 0: symbol, 1: symbol, 2: symbol, 3: symbol
# lint: overloads
compiler.warn.potentially.ambiguous.overload=\
Expand Down
Loading