8202056: Expand serial warning to check for bad overloads of serial-related methods and ineffectual fields

8160675: Issue lint warning for non-serializable non-transient instance fields in serializable type

Reviewed-by: erikj, sspitsyn, jlahoda, vromero, rriggs, smarks
This commit is contained in:
Joe Darcy 2021-10-21 21:11:01 +00:00
parent 4e9dd4bddb
commit 6a466fe7ae
46 changed files with 1928 additions and 70 deletions

@ -224,6 +224,14 @@ public class Symtab {
public final Type valueBasedType;
public final Type valueBasedInternalType;
// For serialization lint checking
public final Type objectStreamFieldType;
public final Type objectInputStreamType;
public final Type objectOutputStreamType;
public final Type ioExceptionType;
public final Type objectStreamExceptionType;
public final Type externalizableType;
/** The symbol representing the length field of an array.
*/
public final VarSymbol lengthVar;
@ -590,6 +598,13 @@ public class Symtab {
switchBootstrapsType = enterClass("java.lang.runtime.SwitchBootstraps");
valueBasedType = enterClass("jdk.internal.ValueBased");
valueBasedInternalType = enterSyntheticAnnotation("jdk.internal.ValueBased+Annotation");
// For serialization lint checking
objectStreamFieldType = enterClass("java.io.ObjectStreamField");
objectInputStreamType = enterClass("java.io.ObjectInputStream");
objectOutputStreamType = enterClass("java.io.ObjectOutputStream");
ioExceptionType = enterClass("java.io.IOException");
objectStreamExceptionType = enterClass("java.io.ObjectStreamException");
externalizableType = enterClass("java.io.Externalizable");
synthesizeEmptyInterfaceIfMissing(autoCloseableType);
synthesizeEmptyInterfaceIfMissing(cloneableType);

@ -2955,7 +2955,7 @@ public class Attr extends JCTree.Visitor {
}
if (resultInfo.checkContext.deferredAttrContext().mode == DeferredAttr.AttrMode.CHECK &&
isSerializable(clazztype)) {
rs.isSerializable(clazztype)) {
localEnv.info.isSerializable = true;
}
@ -3080,7 +3080,7 @@ public class Attr extends JCTree.Visitor {
boolean needsRecovery =
resultInfo.checkContext.deferredAttrContext().mode == DeferredAttr.AttrMode.CHECK;
try {
if (needsRecovery && isSerializable(pt())) {
if (needsRecovery && rs.isSerializable(pt())) {
localEnv.info.isSerializable = true;
localEnv.info.isSerializableLambda = true;
}
@ -3581,7 +3581,7 @@ public class Attr extends JCTree.Visitor {
boolean isTargetSerializable =
resultInfo.checkContext.deferredAttrContext().mode == DeferredAttr.AttrMode.CHECK &&
isSerializable(pt());
rs.isSerializable(pt());
TargetInfo targetInfo = getTargetInfo(that, resultInfo, null);
Type currentTarget = targetInfo.target;
Type desc = targetInfo.descriptor;
@ -5366,7 +5366,7 @@ public class Attr extends JCTree.Visitor {
log.error(env.tree.pos(), Errors.EnumTypesNotExtensible);
}
if (isSerializable(c.type)) {
if (rs.isSerializable(c.type)) {
env.info.isSerializable = true;
}
@ -5501,12 +5501,12 @@ public class Attr extends JCTree.Visitor {
// Check for cycles among annotation elements.
chk.checkNonCyclicElements(tree);
// Check for proper use of serialVersionUID
// Check for proper use of serialVersionUID and other
// serialization-related fields and methods
if (env.info.lint.isEnabled(LintCategory.SERIAL)
&& isSerializable(c.type)
&& (c.flags() & (Flags.ENUM | Flags.INTERFACE)) == 0
&& rs.isSerializable(c.type)
&& !c.isAnonymous()) {
checkSerialVersionUID(tree, c, env);
chk.checkSerialStructure(tree, c);
}
if (allowTypeAnnos) {
// Correctly organize the positions of the type annotations
@ -5527,59 +5527,6 @@ public class Attr extends JCTree.Visitor {
return null;
}
/** check if a type is a subtype of Serializable, if that is available. */
boolean isSerializable(Type t) {
try {
syms.serializableType.complete();
}
catch (CompletionFailure e) {
return false;
}
return types.isSubtype(t, syms.serializableType);
}
/** Check that an appropriate serialVersionUID member is defined. */
private void checkSerialVersionUID(JCClassDecl tree, ClassSymbol c, Env<AttrContext> env) {
// check for presence of serialVersionUID
VarSymbol svuid = null;
for (Symbol sym : c.members().getSymbolsByName(names.serialVersionUID)) {
if (sym.kind == VAR) {
svuid = (VarSymbol)sym;
break;
}
}
if (svuid == null) {
if (!c.isRecord())
log.warning(LintCategory.SERIAL, tree.pos(), Warnings.MissingSVUID(c));
return;
}
// Check if @SuppressWarnings("serial") is an annotation of serialVersionUID.
// See JDK-8231622 for more information.
Lint lint = env.info.lint.augment(svuid);
if (lint.isSuppressed(LintCategory.SERIAL)) {
return;
}
// check that it is static final
if ((svuid.flags() & (STATIC | FINAL)) !=
(STATIC | FINAL))
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(svuid, tree), Warnings.ImproperSVUID(c));
// check that it is long
else if (!svuid.type.hasTag(LONG))
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(svuid, tree), Warnings.LongSVUID(c));
// check constant
else if (svuid.getConstValue() == null)
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(svuid, tree), Warnings.ConstantSVUID(c));
}
private Type capture(Type type) {
return types.capture(type);
}

@ -26,6 +26,7 @@
package com.sun.tools.javac.comp;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@ -71,6 +72,13 @@ import static com.sun.tools.javac.code.TypeTag.*;
import static com.sun.tools.javac.code.TypeTag.WILDCARD;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.ElementKindVisitor14;
/** Type checking helper class for the attribution phase.
*
@ -4342,4 +4350,693 @@ public class Check {
wasNonEmptyFallThrough = c.stats.nonEmpty() && completesNormally;
}
}
/** check if a type is a subtype of Externalizable, if that is available. */
boolean isExternalizable(Type t) {
try {
syms.externalizableType.complete();
}
catch (CompletionFailure e) {
return false;
}
return types.isSubtype(t, syms.externalizableType);
}
/**
* Check structure of serialization declarations.
*/
public void checkSerialStructure(JCClassDecl tree, ClassSymbol c) {
(new SerialTypeVisitor()).visit(c, tree);
}
/**
* This visitor will warn if a serialization-related field or
* method is declared in a suspicious or incorrect way. In
* particular, it will warn for cases where the runtime
* serialization mechanism will silently ignore a mis-declared
* entity.
*
* Distinguished serialization-related fields and methods:
*
* Methods:
*
* private void writeObject(ObjectOutputStream stream) throws IOException
* ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException
*
* private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
* private void readObjectNoData() throws ObjectStreamException
* ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException
*
* Fields:
*
* private static final long serialVersionUID
* private static final ObjectStreamField[] serialPersistentFields
*
* Externalizable: methods defined on the interface
* public void writeExternal(ObjectOutput) throws IOException
* public void readExternal(ObjectInput) throws IOException
*/
private class SerialTypeVisitor extends ElementKindVisitor14<Void, JCClassDecl> {
SerialTypeVisitor() {
this.lint = Check.this.lint;
}
private static final Set<String> serialMethodNames =
Set.of("writeObject", "writeReplace",
"readObject", "readObjectNoData",
"readResolve");
private static final Set<String> serialFieldNames =
Set.of("serialVersionUID", "serialPersistentFields");
// Type of serialPersistentFields
private final Type OSF_TYPE = new Type.ArrayType(syms.objectStreamFieldType, syms.arrayClass);
Lint lint;
@Override
public Void defaultAction(Element e, JCClassDecl p) {
throw new IllegalArgumentException(Objects.requireNonNullElse(e.toString(), ""));
}
@Override
public Void visitType(TypeElement e, JCClassDecl p) {
runUnderLint(e, p, (symbol, param) -> super.visitType(symbol, param));
return null;
}
@Override
public Void visitTypeAsClass(TypeElement e,
JCClassDecl p) {
// Anonymous classes filtered out by caller.
ClassSymbol c = (ClassSymbol)e;
checkCtorAccess(p, c);
// Check for missing serialVersionUID; check *not* done
// for enums or records.
VarSymbol svuidSym = null;
for (Symbol sym : c.members().getSymbolsByName(names.serialVersionUID)) {
if (sym.kind == VAR) {
svuidSym = (VarSymbol)sym;
break;
}
}
if (svuidSym == null) {
log.warning(LintCategory.SERIAL, p.pos(), Warnings.MissingSVUID(c));
}
// Check for serialPersistentFields to gate checks for
// non-serializable non-transient instance fields
boolean serialPersistentFieldsPresent =
c.members()
.getSymbolsByName(names.serialPersistentFields, sym -> sym.kind == VAR)
.iterator()
.hasNext();
// Check declarations of serialization-related methods and
// fields
for(Symbol el : c.getEnclosedElements()) {
runUnderLint(el, p, (enclosed, tree) -> {
String name = null;
switch(enclosed.getKind()) {
case FIELD -> {
if (!serialPersistentFieldsPresent) {
var flags = enclosed.flags();
if ( ((flags & TRANSIENT) == 0) &&
((flags & STATIC) == 0)) {
Type varType = enclosed.asType();
if (!canBeSerialized(varType)) {
// Note per JLS arrays are
// serializable even if the
// component type is not.
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(enclosed, tree),
Warnings.NonSerializableInstanceField);
} else if (varType.hasTag(ARRAY)) {
ArrayType arrayType = (ArrayType)varType;
Type elementType = arrayType.elemtype;
while (elementType.hasTag(ARRAY)) {
arrayType = (ArrayType)elementType;
elementType = arrayType.elemtype;
}
if (!canBeSerialized(elementType)) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(enclosed, tree),
Warnings.NonSerializableInstanceFieldArray(elementType));
}
}
}
}
name = enclosed.getSimpleName().toString();
if (serialFieldNames.contains(name)) {
VarSymbol field = (VarSymbol)enclosed;
switch (name) {
case "serialVersionUID" -> checkSerialVersionUID(tree, e, field);
case "serialPersistentFields" -> checkSerialPersistentFields(tree, e, field);
default -> throw new AssertionError();
}
}
}
// Correctly checking the serialization-related
// methods is subtle. For the methods declared to be
// private or directly declared in the class, the
// enclosed elements of the class can be checked in
// turn. However, writeReplace and readResolve can be
// declared in a superclass and inherited. Note that
// the runtime lookup walks the superclass chain
// looking for writeReplace/readResolve via
// Class.getDeclaredMethod. This differs from calling
// Elements.getAllMembers(TypeElement) as the latter
// will also pull in default methods from
// superinterfaces. In other words, the runtime checks
// (which long predate default methods on interfaces)
// do not admit the possibility of inheriting methods
// this way, a difference from general inheritance.
// The current implementation just checks the enclosed
// elements and does not directly check the inherited
// methods. If all the types are being checked this is
// less of a concern; however, there are cases that
// could be missed. In particular, readResolve and
// writeReplace could, in principle, by inherited from
// a non-serializable superclass and thus not checked
// even if compiled with a serializable child class.
case METHOD -> {
var method = (MethodSymbol)enclosed;
name = method.getSimpleName().toString();
if (serialMethodNames.contains(name)) {
switch (name) {
case "writeObject" -> checkWriteObject(tree, e, method);
case "writeReplace" -> checkWriteReplace(tree,e, method);
case "readObject" -> checkReadObject(tree,e, method);
case "readObjectNoData" -> checkReadObjectNoData(tree, e, method);
case "readResolve" -> checkReadResolve(tree, e, method);
default -> throw new AssertionError();
}
}
}
}
});
}
return null;
}
boolean canBeSerialized(Type type) {
return type.isPrimitive() || rs.isSerializable(type);
}
/**
* Check that Externalizable class needs a public no-arg
* constructor.
*
* Check that a Serializable class has access to the no-arg
* constructor of its first nonserializable superclass.
*/
private void checkCtorAccess(JCClassDecl tree, ClassSymbol c) {
if (isExternalizable(c.type)) {
for(var sym : c.getEnclosedElements()) {
if (sym.isConstructor() &&
((sym.flags() & PUBLIC) == PUBLIC)) {
if (((MethodSymbol)sym).getParameters().isEmpty()) {
return;
}
}
}
log.warning(LintCategory.SERIAL, tree.pos(),
Warnings.ExternalizableMissingPublicNoArgCtor);
} else {
// Approximate access to the no-arg constructor up in
// the superclass chain by checking that the
// constructor is not private. This may not handle
// some cross-package situations correctly.
Type superClass = c.getSuperclass();
// java.lang.Object is *not* Serializable so this loop
// should terminate.
while (rs.isSerializable(superClass) ) {
try {
superClass = (Type)((TypeElement)(((DeclaredType)superClass)).asElement()).getSuperclass();
} catch(ClassCastException cce) {
return ; // Don't try to recover
}
}
// Non-Serializable super class
try {
ClassSymbol supertype = ((ClassSymbol)(((DeclaredType)superClass).asElement()));
for(var sym : supertype.getEnclosedElements()) {
if (sym.isConstructor()) {
MethodSymbol ctor = (MethodSymbol)sym;
if (ctor.getParameters().isEmpty()) {
if (((ctor.flags() & PRIVATE) == PRIVATE) ||
// Handle nested classes and implicit this$0
(supertype.getNestingKind() == NestingKind.MEMBER &&
((supertype.flags() & STATIC) == 0)))
log.warning(LintCategory.SERIAL, tree.pos(),
Warnings.SerializableMissingAccessNoArgCtor(supertype.getQualifiedName()));
}
}
}
} catch (ClassCastException cce) {
return ; // Don't try to recover
}
return;
}
}
private void checkSerialVersionUID(JCClassDecl tree, Element e, VarSymbol svuid) {
// To be effective, serialVersionUID must be marked static
// and final, but private is recommended. But alas, in
// practice there are many non-private serialVersionUID
// fields.
if ((svuid.flags() & (STATIC | FINAL)) !=
(STATIC | FINAL)) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(svuid, tree),
Warnings.ImproperSVUID((Symbol)e));
}
// check svuid has type long
if (!svuid.type.hasTag(LONG)) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(svuid, tree),
Warnings.LongSVUID((Symbol)e));
}
if (svuid.getConstValue() == null)
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(svuid, tree),
Warnings.ConstantSVUID((Symbol)e));
}
private void checkSerialPersistentFields(JCClassDecl tree, Element e, VarSymbol spf) {
// To be effective, serialPersisentFields must be private, static, and final.
if ((spf.flags() & (PRIVATE | STATIC | FINAL)) !=
(PRIVATE | STATIC | FINAL)) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(spf, tree), Warnings.ImproperSPF);
}
if (!types.isSameType(spf.type, OSF_TYPE)) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(spf, tree), Warnings.OSFArraySPF);
}
if (isExternalizable((Type)(e.asType()))) {
log.warning(LintCategory.SERIAL, tree.pos(),
Warnings.IneffectualSerialFieldExternalizable);
}
// Warn if serialPersistentFields is initialized to a
// literal null.
JCTree spfDecl = TreeInfo.declarationFor(spf, tree);
if (spfDecl != null && spfDecl.getTag() == VARDEF) {
JCVariableDecl variableDef = (JCVariableDecl) spfDecl;
JCExpression initExpr = variableDef.init;
if (initExpr != null && TreeInfo.isNull(initExpr)) {
log.warning(LintCategory.SERIAL, initExpr.pos(),
Warnings.SPFNullInit);
}
}
}
private void checkWriteObject(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);
checkReturnType(tree, e, method, syms.voidType);
checkOneArg(tree, e, method, syms.objectOutputStreamType);
checkExceptions(tree, e, method, syms.ioExceptionType);
checkExternalizable(tree, e, method);
}
private void checkWriteReplace(JCClassDecl tree, Element e, MethodSymbol method) {
// ANY-ACCESS-MODIFIER Object writeReplace() throws
// ObjectStreamException
// Excluding abstract, could have a more complicated
// rule based on abstract-ness of the class
checkConcreteInstanceMethod(tree, e, method);
checkReturnType(tree, e, method, syms.objectType);
checkNoArgs(tree, e, method);
checkExceptions(tree, e, method, syms.objectStreamExceptionType);
}
private void checkReadObject(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);
checkReturnType(tree, e, method, syms.voidType);
checkOneArg(tree, e, method, syms.objectInputStreamType);
checkExceptions(tree, e, method, syms.ioExceptionType, syms.classNotFoundExceptionType);
checkExternalizable(tree, e, method);
}
private void checkReadObjectNoData(JCClassDecl tree, Element e, MethodSymbol method) {
// private void readObjectNoData() throws ObjectStreamException
checkPrivateNonStaticMethod(tree, method);
checkReturnType(tree, e, method, syms.voidType);
checkNoArgs(tree, e, method);
checkExceptions(tree, e, method, syms.objectStreamExceptionType);
checkExternalizable(tree, e, method);
}
private void checkReadResolve(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
checkConcreteInstanceMethod(tree, e, method);
checkReturnType(tree,e, method, syms.objectType);
checkNoArgs(tree, e, method);
checkExceptions(tree, e, method, syms.objectStreamExceptionType);
}
void checkPrivateNonStaticMethod(JCClassDecl tree, MethodSymbol method) {
var flags = method.flags();
if ((flags & PRIVATE) == 0) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialMethodNotPrivate(method.getSimpleName()));
}
if ((flags & STATIC) != 0) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialMethodStatic(method.getSimpleName()));
}
}
/**
* Per section 1.12 "Serialization of Enum Constants" of
* the serialization specification, due to the special
* serialization handling of enums, any writeObject,
* readObject, writeReplace, and readResolve methods are
* ignored as are serialPersistentFields and
* serialVersionUID fields.
*/
@Override
public Void visitTypeAsEnum(TypeElement e,
JCClassDecl p) {
for(Element el : e.getEnclosedElements()) {
runUnderLint(el, p, (enclosed, tree) -> {
String name = enclosed.getSimpleName().toString();
switch(enclosed.getKind()) {
case FIELD -> {
if (serialFieldNames.contains(name)) {
log.warning(LintCategory.SERIAL, tree.pos(),
Warnings.IneffectualSerialFieldEnum(name));
}
}
case METHOD -> {
if (serialMethodNames.contains(name)) {
log.warning(LintCategory.SERIAL, tree.pos(),
Warnings.IneffectualSerialMethodEnum(name));
}
}
}
});
}
return null;
}
/**
* Most serialization-related fields and methods on interfaces
* are ineffectual or problematic.
*/
@Override
public Void visitTypeAsInterface(TypeElement e,
JCClassDecl p) {
for(Element el : e.getEnclosedElements()) {
runUnderLint(el, p, (enclosed, tree) -> {
String name = null;
switch(enclosed.getKind()) {
case FIELD -> {
var field = (VarSymbol)enclosed;
name = field.getSimpleName().toString();
switch(name) {
case "serialPersistentFields" -> {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(field, tree),
Warnings.IneffectualSerialFieldInterface);
}
case "serialVersionUID" -> {
checkSerialVersionUID(tree, e, field);
}
}
}
case METHOD -> {
var method = (MethodSymbol)enclosed;
name = enclosed.getSimpleName().toString();
if (serialMethodNames.contains(name)) {
switch (name) {
case
"readObject",
"readObjectNoData",
"writeObject" -> checkPrivateMethod(tree, e, method);
case
"writeReplace",
"readResolve" -> checkDefaultIneffective(tree, e, method);
default -> throw new AssertionError();
}
}
}
}
});
}
return null;
}
private void checkPrivateMethod(JCClassDecl tree,
Element e,
MethodSymbol method) {
if ((method.flags() & PRIVATE) == 0) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.NonPrivateMethodWeakerAccess);
}
}
private void checkDefaultIneffective(JCClassDecl tree,
Element e,
MethodSymbol method) {
if ((method.flags() & DEFAULT) == DEFAULT) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.DefaultIneffective);
}
}
@Override
public Void visitTypeAsAnnotationType(TypeElement e,
JCClassDecl p) {
// Per the JLS, annotation types are not serializeable
return null;
}
/**
* From the Java Object Serialization Specification, 1.13
* Serialization of Records:
*
* "The process by which record objects are serialized or
* externalized cannot be customized; any class-specific
* writeObject, readObject, readObjectNoData, writeExternal,
* and readExternal methods defined by record classes are
* ignored during serialization and deserialization. However,
* a substitute object to be serialized or a designate
* replacement may be specified, by the writeReplace and
* readResolve methods, respectively. Any
* serialPersistentFields field declaration is
* ignored. Documenting serializable fields and data for
* record classes is unnecessary, since there is no variation
* in the serial form, other than whether a substitute or
* replacement object is used. The serialVersionUID of a
* record class is 0L unless explicitly declared. The
* requirement for matching serialVersionUID values is waived
* for record classes."
*/
@Override
public Void visitTypeAsRecord(TypeElement e,
JCClassDecl p) {
for(Element el : e.getEnclosedElements()) {
runUnderLint(el, p, (enclosed, tree) -> {
String name = enclosed.getSimpleName().toString();
switch(enclosed.getKind()) {
case FIELD -> {
switch(name) {
case "serialPersistentFields" -> {
log.warning(LintCategory.SERIAL, tree.pos(),
Warnings.IneffectualSerialFieldRecord);
}
case "serialVersionUID" -> {
// Could generate additional warning that
// svuid value is not checked to match for
// records.
checkSerialVersionUID(tree, e, (VarSymbol)enclosed);
}
}
}
case METHOD -> {
var method = (MethodSymbol)enclosed;
switch(name) {
case "writeReplace" -> checkWriteReplace(tree, e, method);
case "readResolve" -> checkReadResolve(tree, e, method);
default -> {
if (serialMethodNames.contains(name)) {
log.warning(LintCategory.SERIAL, tree.pos(),
Warnings.IneffectualSerialMethodRecord(name));
}
}
}
}
}
});
}
return null;
}
void checkConcreteInstanceMethod(JCClassDecl tree,
Element enclosing,
MethodSymbol method) {
if ((method.flags() & (STATIC | ABSTRACT)) != 0) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialConcreteInstanceMethod(method.getSimpleName()));
}
}
private void checkReturnType(JCClassDecl tree,
Element enclosing,
MethodSymbol method,
Type expectedReturnType) {
// Note: there may be complications checking writeReplace
// and readResolve since they return Object and could, in
// principle, have covariant overrides and any synthetic
// bridge method would not be represented here for
// checking.
Type rtype = method.getReturnType();
if (!types.isSameType(expectedReturnType, rtype)) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialMethodUnexpectedReturnType(method.getSimpleName(),
rtype, expectedReturnType));
}
}
private void checkOneArg(JCClassDecl tree,
Element enclosing,
MethodSymbol method,
Type expectedType) {
String name = method.getSimpleName().toString();
var parameters= method.getParameters();
if (parameters.size() != 1) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialMethodOneArg(method.getSimpleName(), parameters.size()));
return;
}
Type parameterType = parameters.get(0).asType();
if (!types.isSameType(parameterType, expectedType)) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialMethodParameterType(method.getSimpleName(),
expectedType,
parameterType));
}
}
private void checkNoArgs(JCClassDecl tree, Element enclosing, MethodSymbol method) {
var parameters = method.getParameters();
if (!parameters.isEmpty()) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(parameters.get(0), tree),
Warnings.SerialMethodNoArgs(method.getSimpleName()));
}
}
private void checkExternalizable(JCClassDecl tree, Element enclosing, MethodSymbol method) {
// If the enclosing class is externalizable, warn for the method
if (isExternalizable((Type)enclosing.asType())) {
log.warning(LintCategory.SERIAL, tree.pos(),
Warnings.IneffectualSerialMethodExternalizable(method.getSimpleName()));
}
return;
}
private void checkExceptions(JCClassDecl tree,
Element enclosing,
MethodSymbol method,
Type... declaredExceptions) {
for (Type thrownType: method.getThrownTypes()) {
// For each exception in the throws clause of the
// method, if not an Error and not a RuntimeException,
// check if the exception is a subtype of a declared
// exception from the throws clause of the
// serialization method in question.
if (types.isSubtype(thrownType, syms.runtimeExceptionType) ||
types.isSubtype(thrownType, syms.errorType) ) {
continue;
} else {
boolean declared = false;
for (Type declaredException : declaredExceptions) {
if (types.isSubtype(thrownType, declaredException)) {
declared = true;
continue;
}
}
if (!declared) {
log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialMethodUnexpectedException(method.getSimpleName(),
thrownType));
}
}
}
return;
}
private <E extends Element> Void runUnderLint(E symbol, JCClassDecl p, BiConsumer<E, JCClassDecl> task) {
Lint prevLint = lint;
try {
lint = lint.augment((Symbol) symbol);
if (lint.isEnabled(LintCategory.SERIAL)) {
task.accept(symbol, p);
}
return null;
} finally {
lint = prevLint;
}
}
}
}

@ -3908,6 +3908,17 @@ public class Resolve {
}
}
/** check if a type is a subtype of Serializable, if that is available.*/
boolean isSerializable(Type t) {
try {
syms.serializableType.complete();
}
catch (CompletionFailure e) {
return false;
}
return types.isSubtype(t, syms.serializableType);
}
/**
* Root class for resolution errors. Subclass of ResolveError
* represent a different kinds of resolution error - as such they must

@ -1846,6 +1846,14 @@ compiler.warn.illegal.char.for.encoding=\
compiler.warn.improper.SVUID=\
serialVersionUID must be declared static final in class {0}
compiler.warn.improper.SPF=\
serialPersistentFields must be declared private static final to be effective
compiler.warn.SPF.null.init=\
serialPersistentFields ineffective if initialized to null.\n\
Initialize to an empty array to indicate no fields
# 0: type, 1: type
compiler.warn.inexact.non-varargs.call=\
non-varargs call of varargs method with inexact argument type for last parameter;\n\
@ -1866,10 +1874,92 @@ compiler.warn.unreachable.catch.1=\
compiler.warn.long.SVUID=\
serialVersionUID must be of type long in class {0}
compiler.warn.OSF.array.SPF=\
serialPersistentFields must be of type java.io.ObjectStreamField[] to be effective
# 0: symbol
compiler.warn.missing.SVUID=\
serializable class {0} has no definition of serialVersionUID
# 0: name
compiler.warn.serializable.missing.access.no.arg.ctor=\
cannot access a no-arg constructor in first non-serializable superclass {0}
# 0: name
compiler.warn.serial.method.not.private=\
serialization-related method {0} not declared private
# 0: name
compiler.warn.serial.concrete.instance.method=\
serialization-related method {0} must be a concrete instance method to be effective, neither abstract nor static
# 0: name
compiler.warn.serial.method.static=\
serialization-related method {0} declared static; must instead be an instance method to be effective
# 0: name
compiler.warn.serial.method.no.args=\
to be effective serialization-related method {0} must have no parameters
# 0: name, 1: number
compiler.warn.serial.method.one.arg=\
to be effective serialization-related method {0} must have exactly one parameter rather than {1} parameters
# 0: name, 1: type, 2: type
compiler.warn.serial.method.parameter.type=\
sole parameter of serialization-related method {0} must have type {1} to be effective rather than type {2}
# 0: name, 1: type, 2: type
compiler.warn.serial.method.unexpected.return.type=\
serialization-related method {0} declared with a return type of {1} rather than expected type {2}.\n\
As declared, the method will be ineffective for serialization
# 0: name, 1: type
compiler.warn.serial.method.unexpected.exception=\
serialization-related method {0} declared to throw an unexpected type {1}
compiler.warn.ineffectual.serial.field.interface=\
serialPersistentFields is not effective in an interface
# 0: string
compiler.warn.ineffectual.serial.field.enum=\
serialization-related field {0} is not effective in an enum class
# 0: string
compiler.warn.ineffectual.serial.method.enum=\
serialization-related method {0} is not effective in an enum class
compiler.warn.ineffectual.serial.field.record=\
serialPersistentFields is not effective in a record class
# 0: string
compiler.warn.ineffectual.serial.method.record=\
serialization-related method {0} is not effective in a record class
# 0: name
compiler.warn.ineffectual.serial.method.externalizable=\
serialization-related method {0} is not effective in an Externalizable class
compiler.warn.ineffectual.serial.field.externalizable=\
serialPersistentFields is not effective in an Externalizable class
compiler.warn.externalizable.missing.public.no.arg.ctor=\
an Externalizable class needs a public no-arg constructor
compiler.warn.non.serializable.instance.field=\
non-transient instance field of a serializable class declared with a non-serializable type
# 0: type
compiler.warn.non.serializable.instance.field.array=\
non-transient instance field of a serializable class declared with an array having a non-serializable base component type {0}
compiler.warn.non.private.method.weaker.access=\
serialization-related method declared non-private in an interface will prevent\n\
classes implementing the interface from declaring the method as private
compiler.warn.default.ineffective=\
serialization-related default method from an interface will not be run by serialization for an implementing class
# 0: symbol, 1: symbol, 2: symbol, 3: symbol
compiler.warn.potentially.ambiguous.overload=\
{0} in {1} is potentially ambiguous with {2} in {3}

@ -240,8 +240,8 @@ javac.opt.Xlint.desc.requires-transitive-automatic=\
Warn about automatic modules in requires transitive.
javac.opt.Xlint.desc.serial=\
Warn about Serializable classes that do not provide a serial version ID. \n\
\ Also warn about access to non-public members from a serializable element.
Warn about Serializable classes that do not have a serialVersionUID field. \n\
\ Also warn about other suspect declarations in Serializable and Externalizable classes and interfaces.
javac.opt.Xlint.desc.static=\
Warn about accessing a static member using an instance.

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -76,6 +76,7 @@ import static jdk.internal.joptsimple.internal.Messages.*;
public abstract class OptionException extends RuntimeException {
private static final long serialVersionUID = -1L;
@SuppressWarnings("serial") // Type of field is not Serializable
private final List<String> options = new ArrayList<>();
protected OptionException( List<String> options ) {

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -100,6 +100,7 @@ public class ConstantPool {
return "value not found: " + value;
}
@SuppressWarnings("serial") // Type of field is not Serializable
public final Object value;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2009, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -43,5 +43,6 @@ public class InternalError extends Error {
this.args = args;
}
@SuppressWarnings("serial") // Array component type is not Serializable
public final Object[] args;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -93,6 +93,7 @@ public class JavapTask implements DisassemblerTool.DisassemblerTask, Messages {
}
final String key;
@SuppressWarnings("serial") // Array component type is not Serializable
final Object[] args;
boolean showUsage;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -63,6 +63,7 @@ class JdepsTask {
return this;
}
final String key;
@SuppressWarnings("serial") // Array component type is not Serializable
final Object[] args;
boolean showUsage;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -34,6 +34,7 @@ package com.sun.tools.jdeps;
class MultiReleaseException extends RuntimeException {
private static final long serialVersionUID = 4474870142461654108L;
private final String key;
@SuppressWarnings("serial") // Array component type is not Serializable
private final Object[] params;
/**

@ -0,0 +1,38 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.improper.SPF
// key: compiler.warn.OSF.array.SPF
// key: compiler.warn.SPF.null.init
// options: -Xlint:serial
import java.io.Serializable;
class ImproperSPF implements Serializable {
// Proper declaration of serialPersistentFields is:
// private static final ObjectStreamField[] serialPersistentFields = ...
public /*instance*/ Object serialPersistentFields = null;
private static final long serialVersionUID = 42;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,6 +22,9 @@
*/
// key: compiler.warn.improper.SVUID
// key: compiler.warn.constant.SVUID
// key: compiler.warn.long.SVUID
// options: -Xlint:serial
import java.io.Serializable;

@ -0,0 +1,43 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.ineffectual.serial.field.enum
// key: compiler.warn.ineffectual.serial.method.enum
// options: -Xlint:serial
import java.io.*;
enum IneffectualSerialEnum implements Serializable {
INSTANCE;
// The serialVersionUID field is ineffectual for enum classes.
private static final long serialVersionUID = 42;
// The readObject method is ineffectual for enum classes.
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
return;
}
}

@ -0,0 +1,55 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.ineffectual.serial.method.externalizable
// key: compiler.warn.ineffectual.serial.field.externalizable
// key: compiler.warn.externalizable.missing.public.no.arg.ctor
// options: -Xlint:serial
import java.io.*;
class IneffectualSerialExtern implements Externalizable {
private static final long serialVersionUID = 42;
// Must have a public no-arg constructor
public IneffectualSerialExtern(int foo) {}
public void writeExternal(ObjectOutput out) throws IOException {
return;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
return;
}
// ineffectual
private static final ObjectStreamField[] serialPersistentFields = {};
// The readObject method is ineffectual for Externalizable classes.
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
return;
}
}

@ -0,0 +1,42 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.ineffectual.serial.field.record
// key: compiler.warn.ineffectual.serial.method.record
// options: -Xlint:serial
import java.io.*;
record IneffectualSerialRecord(int foo) implements Serializable {
// A serialPersistentFields is ineffectual for enum classes.
private static final ObjectStreamField[] serialPersistentFields = {};
// The readObject method is ineffectual for record classes.
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
return;
}
}

@ -0,0 +1,42 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.non.private.method.weaker.access
// key: compiler.warn.default.ineffective
// key: compiler.warn.ineffectual.serial.field.interface
// options: -Xlint:serial
import java.io.*;
interface SerialInterfaceMethodsAndFields extends Serializable {
public static final ObjectStreamField[] serialPersistentFields = {};
public void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException;
public void readObjectNoData() throws ObjectStreamException;
public void writeObject(ObjectOutputStream stream) throws IOException;
default public Object readResolve() throws ObjectStreamException {
return null;
}
}

@ -0,0 +1,56 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.serializable.missing.access.no.arg.ctor
// key: compiler.warn.non.serializable.instance.field
// key: compiler.warn.non.serializable.instance.field.array
// options: -Xlint:serial
import java.io.*;
class SerialMissingNoArgCtor {
public SerialMissingNoArgCtor(int foo) {
}
// Not accessible to SerialSubclass
private SerialMissingNoArgCtor() {}
// SerialSubclass does not have access to a non-arg ctor in the
// first non-serializable superclass in its superclass chain.
static class SerialSubclass extends SerialMissingNoArgCtor
implements Serializable {
private static final long serialVersionUID = 42;
// non-serializable non-transient instance field
private Object datum = null;
// base component type of array is non-serializable
private Object[] data = null;
public SerialSubclass() {
super(1);
}
}
}

@ -0,0 +1,71 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.serial.method.not.private
// key: compiler.warn.serial.method.static
// key: compiler.warn.serial.method.unexpected.return.type
// key: compiler.warn.serial.concrete.instance.method
// key: compiler.warn.serial.method.one.arg
// key: compiler.warn.serial.method.parameter.type
// key: compiler.warn.serial.method.no.args
// key: compiler.warn.serial.method.unexpected.exception
// options: -Xlint:serial
import java.io.*;
abstract class SerialNonPrivateMethod implements Serializable {
private static final long serialVersionUID = 42;
private static class CustomObjectOutputStream extends ObjectOutputStream {
public CustomObjectOutputStream() throws IOException,
SecurityException {}
}
// Should be private and have a single argument of type
// ObjectOutputStream
void writeObject(CustomObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
}
// Should be private non-static and have one argument
private static void readObject(ObjectInputStream stream, int retries)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
}
// Should return void
private int readObjectNoData() throws ObjectStreamException {
return 42;
}
// Should be concrete instance method
public abstract Object writeReplace() throws ObjectStreamException;
// Should have no arguments and throw ObjectStreamException
/*package*/ Object readResolve(int foo)
throws ReflectiveOperationException { // Checked exception
return null;
}
}

@ -0,0 +1,37 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=CtorAccess.out -XDrawDiagnostics -Xlint:serial CtorAccess.java
*/
import java.io.*;
class CtorAccess {
public CtorAccess(int i) {}
// Cannot by accessed by SerialSubclass
private CtorAccess(){}
static class SerialSubclass
extends CtorAccess
implements Serializable {
private static final long serialVersionUID = 42;
SerialSubclass() {
super(42);
}
}
// *not* static
class MemberSuper {
// Implicit this$0 argument
public MemberSuper() {}
}
class SerialMemberSub
extends MemberSuper
implements Serializable {
SerialMemberSub(){super();}
private static final long serialVersionUID = 42;
}
}

@ -0,0 +1,3 @@
CtorAccess.java:15:12: compiler.warn.serializable.missing.access.no.arg.ctor: CtorAccess
CtorAccess.java:30:5: compiler.warn.serializable.missing.access.no.arg.ctor: CtorAccess.MemberSuper
2 warnings

@ -0,0 +1,33 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=DeepNestingSuppression.out -XDrawDiagnostics -Xlint:serial DeepNestingSuppression.java
*/
import java.io.Serializable;
/*
* Verify suppressing serial warnings works through several levels of
* nested types.
*/
class DeepNestingSuppression {
@SuppressWarnings("serial")
static class SuppressedOuter {
static class Intermediate {
static class Inner implements Serializable {
// warning for int rather than long svuid
private static final int serialVersionUID = 42;
}
}
}
static class Outer {
static class Intermediate {
static class Inner implements Serializable {
// warning for int rather than long svuid
private static final int serialVersionUID = 42;
}
}
}
}

@ -0,0 +1,2 @@
DeepNestingSuppression.java:29:42: compiler.warn.long.SVUID: DeepNestingSuppression.Outer.Intermediate.Inner
1 warning

@ -0,0 +1,38 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=EnumSerial.out -XDrawDiagnostics -Xlint:serial EnumSerial.java
*/
import java.io.*;
enum EnumSerial implements Serializable {
INSTANCE;
// Verify a warning is generated in an enum class for each of the
// distinguished serial fields and methods.
private static final long serialVersionUID = 42;
private static final ObjectStreamField[] serialPersistentFields = {};
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
}
private Object writeReplace() throws ObjectStreamException {
return null;
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
}
private void readObjectNoData() throws ObjectStreamException {
return;
}
private Object readResolve() throws ObjectStreamException {
return null;
}
}

@ -0,0 +1,8 @@
EnumSerial.java:9:1: compiler.warn.ineffectual.serial.field.enum: serialVersionUID
EnumSerial.java:9:1: compiler.warn.ineffectual.serial.field.enum: serialPersistentFields
EnumSerial.java:9:1: compiler.warn.ineffectual.serial.method.enum: writeObject
EnumSerial.java:9:1: compiler.warn.ineffectual.serial.method.enum: writeReplace
EnumSerial.java:9:1: compiler.warn.ineffectual.serial.method.enum: readObject
EnumSerial.java:9:1: compiler.warn.ineffectual.serial.method.enum: readObjectNoData
EnumSerial.java:9:1: compiler.warn.ineffectual.serial.method.enum: readResolve
7 warnings

@ -0,0 +1,51 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=Extern.out -XDrawDiagnostics -Xlint:serial Extern.java
*/
import java.io.*;
class Extern implements Externalizable {
private static final long serialVersionUID = 42;
// No-arg constructor on an Externalizable class must be public
protected Extern() {}
// ineffectual
private static final ObjectStreamField[] serialPersistentFields = {};
public void writeExternal(ObjectOutput out) throws IOException {
return;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
return;
}
// ineffectual
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
}
// ineffectual
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
}
// ineffectual
private void readObjectNoData() throws ObjectStreamException {
return;
}
// (possibly) effective
private Object writeReplace() throws ObjectStreamException {
return null;
}
// (possibly) effective
private Object readResolve() throws ObjectStreamException {
return null;
}
}

@ -0,0 +1,6 @@
Extern.java:9:1: compiler.warn.externalizable.missing.public.no.arg.ctor
Extern.java:9:1: compiler.warn.ineffectual.serial.field.externalizable
Extern.java:9:1: compiler.warn.ineffectual.serial.method.externalizable: readObject
Extern.java:9:1: compiler.warn.ineffectual.serial.method.externalizable: writeObject
Extern.java:9:1: compiler.warn.ineffectual.serial.method.externalizable: readObjectNoData
5 warnings

@ -0,0 +1,46 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=ImproperReturnTypes.out -XDrawDiagnostics -Xlint:serial ImproperReturnTypes.java
*/
import java.io.*;
class ImproperReturnTypes implements Serializable {
private static final long serialVersionUID = 42;
/*
* Serialization-related methods return either void or Object:
*
* private void writeObject(ObjectOutputStream stream) throws IOException
* ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException
*
* private void readObject(ObjectInputStream stream)
* throws IOException, ClassNotFoundException
* private void readObjectNoData() throws ObjectStreamException
* ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException
*/
private int writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
return 0;
}
private int writeReplace() throws ObjectStreamException {
return 1;
}
private int readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
return 2;
}
private int readObjectNoData() throws ObjectStreamException {
return 3;
}
private int readResolve() throws ObjectStreamException {
return 4;
}
}

@ -0,0 +1,6 @@
ImproperReturnTypes.java:24:17: compiler.warn.serial.method.unexpected.return.type: writeObject, int, void
ImproperReturnTypes.java:29:17: compiler.warn.serial.method.unexpected.return.type: writeReplace, int, java.lang.Object
ImproperReturnTypes.java:33:17: compiler.warn.serial.method.unexpected.return.type: readObject, int, void
ImproperReturnTypes.java:39:17: compiler.warn.serial.method.unexpected.return.type: readObjectNoData, int, void
ImproperReturnTypes.java:43:17: compiler.warn.serial.method.unexpected.return.type: readResolve, int, java.lang.Object
5 warnings

@ -0,0 +1,37 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=ImproperSerialPF.out -XDrawDiagnostics -Xlint:serial ImproperSerialPF.java
*/
import java.io.*;
class ImproperSerialPF implements Serializable {
// Proper declaration of serialPersistentFields is:
// private static final ObjectStreamField[] serialPersistentFields = ...
public /*instance*/ Object serialPersistentFields = Boolean.TRUE;
private static final long serialVersionUID = 42;
static class LiteralNullSPF implements Serializable {
private static final ObjectStreamField[] serialPersistentFields = null;
private static final long serialVersionUID = 42;
}
// Casting obscures the simple syntactic null-check
static class CastedNullSPF implements Serializable {
private static final ObjectStreamField[] serialPersistentFields =
(ObjectStreamField[])null;
private static final long serialVersionUID = 42;
}
// Conditional obscures the simple syntactic null-check too
static class ConditionalNullSPF implements Serializable {
private static final ObjectStreamField[] serialPersistentFields =
(true ? null : null);
private static final long serialVersionUID = 42;
}
}

@ -0,0 +1,4 @@
ImproperSerialPF.java:17:75: compiler.warn.SPF.null.init
ImproperSerialPF.java:12:32: compiler.warn.improper.SPF
ImproperSerialPF.java:12:32: compiler.warn.OSF.array.SPF
3 warnings

@ -0,0 +1,40 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=InstanceField.out -XDrawDiagnostics -Xlint:serial InstanceField.java
*/
import java.io.*;
class IntanceField implements Serializable {
private static final long serialVersionUID = 42;
// Non-transient instance fields in a serializable class w/o
// serialPersistentFields defined should get warnings if the type
// of the field cannot be serialized.
private Object foo;
private Object[] foos;
private Thread[][] ArrayOfArrayOfThreads;
// No warnings
private static Object bar;
private static Object[] bars;
private int baz;
private double[] quux;
static class NestedInstance implements Serializable {
private static final long serialVersionUID = 24;
// Should disable instance field warnings
private static final ObjectStreamField[] serialPersistentFields = {};
private Object foo;
}
}

@ -0,0 +1,4 @@
InstanceField.java:16:20: compiler.warn.non.serializable.instance.field
InstanceField.java:18:22: compiler.warn.non.serializable.instance.field.array: java.lang.Object
InstanceField.java:20:24: compiler.warn.non.serializable.instance.field.array: java.lang.Thread
3 warnings

@ -0,0 +1,14 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=InterfaceFields.out -XDrawDiagnostics -Xlint:serial InterfaceFields.java
*/
import java.io.*;
interface InterfaceFields extends Serializable {
public static final int serialVersionUID = 12345;
public static final ObjectStreamField[] serialPersistentFields = {};
}

@ -0,0 +1,3 @@
InterfaceFields.java:10:29: compiler.warn.long.SVUID: InterfaceFields
InterfaceFields.java:12:45: compiler.warn.ineffectual.serial.field.interface
2 warnings

@ -0,0 +1,43 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=InterfaceNonPrivateMethods.out -XDrawDiagnostics -Xlint:serial InterfaceNonPrivateMethods.java
*/
import java.io.*;
// Holder class
class InterfaceNonPrivateMethods {
interface NonPrivateMethods extends Serializable {
public void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException;
public void readObjectNoData() throws ObjectStreamException;
public void writeObject(ObjectOutputStream stream) throws IOException;
// Ineffective default methods; serialization only looks up
// superclass chain
public default Object writeReplace() throws ObjectStreamException {
return null;
}
public default Object readResolve() throws ObjectStreamException {
return null;
}
}
interface NonPrivateMethodsDefaults extends Serializable {
default public void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
return;
}
default public void readObjectNoData()
throws ObjectStreamException {
return;
}
default public void writeObject(ObjectOutputStream stream)
throws IOException {
return;
}
}
}

@ -0,0 +1,9 @@
InterfaceNonPrivateMethods.java:13:21: compiler.warn.non.private.method.weaker.access
InterfaceNonPrivateMethods.java:15:21: compiler.warn.non.private.method.weaker.access
InterfaceNonPrivateMethods.java:16:21: compiler.warn.non.private.method.weaker.access
InterfaceNonPrivateMethods.java:20:31: compiler.warn.default.ineffective
InterfaceNonPrivateMethods.java:23:31: compiler.warn.default.ineffective
InterfaceNonPrivateMethods.java:29:29: compiler.warn.non.private.method.weaker.access
InterfaceNonPrivateMethods.java:33:29: compiler.warn.non.private.method.weaker.access
InterfaceNonPrivateMethods.java:37:29: compiler.warn.non.private.method.weaker.access
8 warnings

@ -0,0 +1,44 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=RecordSerial.out -XDrawDiagnostics -Xlint:serial RecordSerial.java
*/
import java.io.*;
record RecordSerial(int foo) implements Serializable {
// Verify a warning is generated in a record class for each of the
// ineffectual serial fields and methods.
// partially effective
private static final long serialVersionUID = 42;
// ineffectual
private static final ObjectStreamField[] serialPersistentFields = {};
// ineffectual
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
}
// (possibly) effective
private Object writeReplace() throws ObjectStreamException {
return null;
}
// ineffectual
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
}
// ineffectual
private void readObjectNoData() throws ObjectStreamException {
return;
}
// (possibly) effective
private Object readResolve() throws ObjectStreamException {
return null;
}
}

@ -0,0 +1,5 @@
RecordSerial.java:9:1: compiler.warn.ineffectual.serial.field.record
RecordSerial.java:9:1: compiler.warn.ineffectual.serial.method.record: writeObject
RecordSerial.java:9:1: compiler.warn.ineffectual.serial.method.record: readObject
RecordSerial.java:9:1: compiler.warn.ineffectual.serial.method.record: readObjectNoData
4 warnings

@ -0,0 +1,40 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=SerialMethodArity.out -XDrawDiagnostics -Xlint:serial SerialMethodArity.java
*/
import java.io.*;
class SerialMethodMods implements Serializable {
private static final long serialVersionUID = 42;
private static class CustomObjectOutputStream extends ObjectOutputStream {
public CustomObjectOutputStream() throws IOException,
SecurityException {}
}
// Should have a single parameter of exact type ObjectOutputStream
private void writeObject(CustomObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
}
// Should have a single parameter of exact type ObjectInputStream
private void readObject(ObjectInputStream stream, int retries)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
}
// Should have no arguments
private void readObjectNoData(int retries) throws ObjectStreamException {}
// Should have no arguments
public Object writeReplace(int arg0, int arg1) throws ObjectStreamException {
return null;
}
// Should have no arguments
public Object readResolve(double foo, float bar) throws ObjectStreamException {
return null;
}
}

@ -0,0 +1,6 @@
SerialMethodArity.java:18:18: compiler.warn.serial.method.parameter.type: writeObject, java.io.ObjectOutputStream, SerialMethodMods.CustomObjectOutputStream
SerialMethodArity.java:23:18: compiler.warn.serial.method.one.arg: readObject, 2
SerialMethodArity.java:29:39: compiler.warn.serial.method.no.args: readObjectNoData
SerialMethodArity.java:32:36: compiler.warn.serial.method.no.args: writeReplace
SerialMethodArity.java:37:38: compiler.warn.serial.method.no.args: readResolve
5 warnings

@ -0,0 +1,33 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=SerialMethodMods.out -XDrawDiagnostics -Xlint:serial SerialMethodMods.java
*/
import java.io.*;
abstract class SerialMethodMods implements Serializable {
private static final long serialVersionUID = 42;
// Should be private
void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
}
// Should be private
public void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
}
// Should be concrete instance method
private static void readObjectNoData() throws ObjectStreamException {}
// Should be concrete instance method
public abstract Object writeReplace() throws ObjectStreamException;
// Should be concrete instance method
public static Object readResolve() throws ObjectStreamException {
return null;
}
}

@ -0,0 +1,6 @@
SerialMethodMods.java:13:10: compiler.warn.serial.method.not.private: writeObject
SerialMethodMods.java:18:17: compiler.warn.serial.method.not.private: readObject
SerialMethodMods.java:24:25: compiler.warn.serial.method.static: readObjectNoData
SerialMethodMods.java:27:28: compiler.warn.serial.concrete.instance.method: writeReplace
SerialMethodMods.java:30:26: compiler.warn.serial.concrete.instance.method: readResolve
5 warnings

@ -0,0 +1,218 @@
/*
* @test /nodynamiccopyright/
* @bug 8202056
* @compile/ref=SerialMethodThrows.out -XDrawDiagnostics -Xlint:serial SerialMethodThrows.java
*/
import java.io.*;
/*
* Container class for various serializable classes with different
* kinds of throws clauses. Canonical serialization method signatures:
*
* private void writeObject(ObjectOutputStream stream)
* throws IOException
*
* ANY-ACCESS-MODIFIER Object writeReplace()
* throws ObjectStreamException
*
* private void readObject(ObjectInputStream stream)
* throws IOException, ClassNotFoundException
*
* private void readObjectNoData()
* throws ObjectStreamException
*
* ANY-ACCESS-MODIFIER Object readResolve()
* throws ObjectStreamException
*/
class SerialMethodThrows {
// Being declared to throw no exceptions is fine and should not
// generate any warnings.
static class NoThrows implements Serializable {
private static final long serialVersionUID = 42;
private void writeObject(ObjectOutputStream stream) {
try {
stream.defaultWriteObject();
} catch (IOException e) {
;
}
}
private Object writeReplace() {
return null;
}
private void readObject(ObjectInputStream stream) {
try {
stream.defaultReadObject();
} catch (IOException | ClassNotFoundException e) {
;
}
}
private void readObjectNoData() {}
private Object readResolve() {
return null;
}
}
// Being declared to throw the canonical exceptions is fine and
// should not generate any warnings.
static class ErrorThrows implements Serializable {
private static final long serialVersionUID = 42;
private void writeObject(ObjectOutputStream stream)
throws Error {
try {
stream.defaultWriteObject();
} catch (IOException e) {
;
}
}
private Object writeReplace()
throws Error {
return null;
}
private void readObject(ObjectInputStream stream)
throws Error {
try {
stream.defaultReadObject();
} catch (IOException | ClassNotFoundException e) {
;
}
}
private void readObjectNoData()
throws Error {}
private Object readResolve()
throws Error {
return null;
}
}
// Being declared to throw the canonical exceptions is fine and
// should not generate any warnings.
static class ExactThrows implements Serializable {
private static final long serialVersionUID = 42;
private void writeObject(ObjectOutputStream stream)
throws IOException {
stream.defaultWriteObject();
}
private Object writeReplace()
throws ObjectStreamException {
return null;
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
}
private void readObjectNoData()
throws ObjectStreamException {}
private Object readResolve()
throws ObjectStreamException {
return null;
}
}
// Being declared to throw subclasses of the canonical exceptions
// is fine and should not generate any warnings.
static class SubclassThrows implements Serializable {
private static final long serialVersionUID = 42;
private void writeObject(ObjectOutputStream stream)
throws CustomIOException {
try {
stream.defaultWriteObject();
} catch (IOException e) {
;
}
}
private Object writeReplace()
throws CustomObjectStreamException {
return null;
}
private void readObject(ObjectInputStream stream)
throws CustomIOException, CustomClassNotFoundException {
try {
stream.defaultReadObject();
} catch (IOException | ClassNotFoundException e) {
;
}
}
private void readObjectNoData()
throws CustomObjectStreamException {}
private Object readResolve()
throws CustomObjectStreamException {
return null;
}
}
private static class CustomIOException extends IOException{
private static final long serialVersionUID = 1;
}
private static class CustomObjectStreamException extends ObjectStreamException {
private static final long serialVersionUID = 2;
}
private static class CustomClassNotFoundException extends ClassNotFoundException {
private static final long serialVersionUID = 3;
}
// Use to trigger warnings
private static class CustomException extends Exception {
private static final long serialVersionUID = 3;
}
// Being declared to throw subclasses of the canonical exceptions
// is fine and should not generate any warnings.
static class CustomThrows implements Serializable {
private static final long serialVersionUID = 42;
private void writeObject(ObjectOutputStream stream)
throws CustomException {
try {
stream.defaultWriteObject();
} catch (IOException e) {
;
}
}
private Object writeReplace()
throws CustomException {
return null;
}
private void readObject(ObjectInputStream stream)
throws CustomException {
try {
stream.defaultReadObject();
} catch (IOException | ClassNotFoundException e) {
;
}
}
private void readObjectNoData()
throws CustomException {}
private Object readResolve()
throws CustomException {
return null;
}
}
}

@ -0,0 +1,6 @@
SerialMethodThrows.java:187:22: compiler.warn.serial.method.unexpected.exception: writeObject, SerialMethodThrows.CustomException
SerialMethodThrows.java:196:24: compiler.warn.serial.method.unexpected.exception: writeReplace, SerialMethodThrows.CustomException
SerialMethodThrows.java:201:22: compiler.warn.serial.method.unexpected.exception: readObject, SerialMethodThrows.CustomException
SerialMethodThrows.java:210:22: compiler.warn.serial.method.unexpected.exception: readObjectNoData, SerialMethodThrows.CustomException
SerialMethodThrows.java:213:24: compiler.warn.serial.method.unexpected.exception: readResolve, SerialMethodThrows.CustomException
5 warnings