< prev index next >

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java

Print this page
*** 26,11 ***
  package com.sun.tools.javac.comp;
  
  import java.util.*;
  import java.util.function.BiConsumer;
  import java.util.function.BiPredicate;
- import java.util.function.Consumer;
  import java.util.function.Predicate;
  import java.util.function.Supplier;
  import java.util.function.ToIntBiFunction;
  import java.util.stream.Collectors;
  import java.util.stream.StreamSupport;
--- 26,10 ---

*** 77,15 ***
  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.
   *
   *  <p><b>This is NOT part of any supported API.
--- 76,12 ---

*** 182,10 ***
--- 178,12 ---
          deferredLintHandler = DeferredLintHandler.instance(context);
  
          allowModules = Feature.MODULES.allowedInSource(source);
          allowRecords = Feature.RECORDS.allowedInSource(source);
          allowSealed = Feature.SEALED_CLASSES.allowedInSource(source);
+         allowValueClasses = (!preview.isPreview(Feature.VALUE_CLASSES) || preview.isEnabled()) &&
+                 Feature.VALUE_CLASSES.allowedInSource(source);
      }
  
      /** Character for synthetic names
       */
      char syntheticNameChar;

*** 225,10 ***
--- 223,14 ---
  
      /** Are sealed classes allowed
       */
      private final boolean allowSealed;
  
+     /** Are value classes allowed
+      */
+     private final boolean allowValueClasses;
+ 
  /* *************************************************************************
   * Errors and Warnings
   **************************************************************************/
  
      Lint setLint(Lint newLint) {

*** 758,10 ***
--- 760,35 ---
              return (t.hasTag(TYPEVAR))
                                      ? diags.fragment(Fragments.TypeParameter(t))
                                      : t;
          }
  
+     void checkConstraintsOfValueClass(JCClassDecl tree, ClassSymbol c) {
+         DiagnosticPosition pos = tree.pos();
+         for (Type st : types.closure(c.type)) {
+             if (st == null || st.tsym == null || st.tsym.kind == ERR)
+                 continue;
+             if  (st.tsym == syms.objectType.tsym || st.tsym == syms.recordType.tsym || st.isInterface())
+                 continue;
+             if (!st.tsym.isAbstract()) {
+                 if (c != st.tsym) {
+                     log.error(pos, Errors.ConcreteSupertypeForValueClass(c, st));
+                 }
+                 continue;
+             }
+             // dealing with an abstract value or value super class below.
+             for (Symbol s : st.tsym.members().getSymbols(NON_RECURSIVE)) {
+                 if (s.kind == MTH) {
+                     if ((s.flags() & (SYNCHRONIZED | STATIC)) == SYNCHRONIZED) {
+                         log.error(pos, Errors.SuperClassMethodCannotBeSynchronized(s, c, st));
+                     }
+                     break;
+                 }
+             }
+         }
+     }
+ 
      /** Check that type is a valid qualifier for a constructor reference expression
       */
      Type checkConstructorRefType(DiagnosticPosition pos, Type t) {
          t = checkClassOrArrayType(pos, t);
          if (t.hasTag(CLASS)) {

*** 815,10 ***
--- 842,36 ---
              return typeTagError(pos,
                                  diags.fragment(Fragments.TypeReqRef),
                                  t);
      }
  
+     /** Check that type is an identity type, i.e. not a value type.
+      *  When not discernible statically, give it the benefit of doubt
+      *  and defer to runtime.
+      *
+      *  @param pos           Position to be used for error reporting.
+      *  @param t             The type to be checked.
+      */
+     boolean checkIdentityType(DiagnosticPosition pos, Type t) {
+         if (t.hasTag(TYPEVAR)) {
+             t = types.skipTypeVars(t, false);
+         }
+         if (t.isIntersection()) {
+             IntersectionClassType ict = (IntersectionClassType)t;
+             boolean result = true;
+             for (Type component : ict.getExplicitComponents()) {
+                 result &= checkIdentityType(pos, component);
+             }
+             return result;
+         }
+         if (t.isPrimitive() || (t.isValueClass() && !t.tsym.isAbstract())) {
+             typeTagError(pos, diags.fragment(Fragments.TypeReqIdentity), t);
+             return false;
+         }
+         return true;
+     }
+ 
      /** Check that each type is a reference type, i.e. a class, interface or array type
       *  or a type variable.
       *  @param trees         Original trees, used for error reporting.
       *  @param types         The types to be checked.
       */

*** 1205,12 ***
                  mask = ReceiverParamFlags;
              else if (sym.owner.kind != TYP)
                  mask = LocalVarFlags;
              else if ((sym.owner.flags_field & INTERFACE) != 0)
                  mask = implicit = InterfaceVarFlags;
!             else
!                 mask = VarFlags;
              break;
          case MTH:
              if (sym.name == names.init) {
                  if ((sym.owner.flags_field & ENUM) != 0) {
                      // enum constructors cannot be declared public or
--- 1258,17 ---
                  mask = ReceiverParamFlags;
              else if (sym.owner.kind != TYP)
                  mask = LocalVarFlags;
              else if ((sym.owner.flags_field & INTERFACE) != 0)
                  mask = implicit = InterfaceVarFlags;
!             else {
!                 boolean isInstanceFieldOfValueClass = sym.owner.type.isValueClass() && (flags & STATIC) == 0;
+                 mask = !isInstanceFieldOfValueClass ? VarFlags : ValueFieldFlags;
+                 if (isInstanceFieldOfValueClass) {
+                     implicit |= FINAL | STRICT;
+                 }
+             }
              break;
          case MTH:
              if (sym.name == names.init) {
                  if ((sym.owner.flags_field & ENUM) != 0) {
                      // enum constructors cannot be declared public or

*** 1232,13 ***
                      }
                  } else {
                      mask = implicit = InterfaceMethodFlags;
                  }
              } else if ((sym.owner.flags_field & RECORD) != 0) {
!                 mask = RecordMethodFlags;
              } else {
!                 mask = MethodFlags;
              }
              if ((flags & STRICTFP) != 0) {
                  warnOnExplicitStrictfp(pos);
              }
              // Imply STRICTFP if owner has STRICTFP set.
--- 1290,16 ---
                      }
                  } else {
                      mask = implicit = InterfaceMethodFlags;
                  }
              } else if ((sym.owner.flags_field & RECORD) != 0) {
!                 mask = ((sym.owner.flags_field & VALUE_CLASS) != 0 && (flags & Flags.STATIC) == 0) ?
+                         RecordMethodFlags & ~SYNCHRONIZED : RecordMethodFlags;
              } else {
!                 // value objects do not have an associated monitor/lock
+                 mask = ((sym.owner.flags_field & VALUE_CLASS) != 0 && (flags & Flags.STATIC) == 0) ?
+                         MethodFlags & ~SYNCHRONIZED : MethodFlags;
              }
              if ((flags & STRICTFP) != 0) {
                  warnOnExplicitStrictfp(pos);
              }
              // Imply STRICTFP if owner has STRICTFP set.

*** 1251,11 ***
                      (sym.isDirectlyOrIndirectlyLocal() && (flags & ANNOTATION) != 0)) {
                  boolean implicitlyStatic = !sym.isAnonymous() &&
                          ((flags & RECORD) != 0 || (flags & ENUM) != 0 || (flags & INTERFACE) != 0);
                  boolean staticOrImplicitlyStatic = (flags & STATIC) != 0 || implicitlyStatic;
                  // local statics are allowed only if records are allowed too
!                 mask = staticOrImplicitlyStatic && allowRecords && (flags & ANNOTATION) == 0 ? StaticLocalFlags : LocalClassFlags;
                  implicit = implicitlyStatic ? STATIC : implicit;
              } else if (sym.owner.kind == TYP) {
                  // statics in inner classes are allowed only if records are allowed too
                  mask = ((flags & STATIC) != 0) && allowRecords && (flags & ANNOTATION) == 0 ? ExtendedMemberStaticClassFlags : ExtendedMemberClassFlags;
                  if (sym.owner.owner.kind == PCK ||
--- 1312,11 ---
                      (sym.isDirectlyOrIndirectlyLocal() && (flags & ANNOTATION) != 0)) {
                  boolean implicitlyStatic = !sym.isAnonymous() &&
                          ((flags & RECORD) != 0 || (flags & ENUM) != 0 || (flags & INTERFACE) != 0);
                  boolean staticOrImplicitlyStatic = (flags & STATIC) != 0 || implicitlyStatic;
                  // local statics are allowed only if records are allowed too
!                 mask = staticOrImplicitlyStatic && allowRecords && (flags & ANNOTATION) == 0 ? ExtendedStaticLocalClassFlags : ExtendedLocalClassFlags;
                  implicit = implicitlyStatic ? STATIC : implicit;
              } else if (sym.owner.kind == TYP) {
                  // statics in inner classes are allowed only if records are allowed too
                  mask = ((flags & STATIC) != 0) && allowRecords && (flags & ANNOTATION) == 0 ? ExtendedMemberStaticClassFlags : ExtendedMemberClassFlags;
                  if (sym.owner.owner.kind == PCK ||

*** 1267,16 ***
                  // Nested interfaces and enums are always STATIC (Spec ???)
                  if ((flags & (INTERFACE | ENUM | RECORD)) != 0 ) implicit = STATIC;
              } else {
                  mask = ExtendedClassFlags;
              }
              // Interfaces are always ABSTRACT
              if ((flags & INTERFACE) != 0) implicit |= ABSTRACT;
  
              if ((flags & ENUM) != 0) {
!                 // enums can't be declared abstract, final, sealed or non-sealed
!                 mask &= ~(ABSTRACT | FINAL | SEALED | NON_SEALED);
                  implicit |= implicitEnumFinalFlag(tree);
              }
              if ((flags & RECORD) != 0) {
                  // records can't be declared abstract
                  mask &= ~ABSTRACT;
--- 1328,24 ---
                  // Nested interfaces and enums are always STATIC (Spec ???)
                  if ((flags & (INTERFACE | ENUM | RECORD)) != 0 ) implicit = STATIC;
              } else {
                  mask = ExtendedClassFlags;
              }
+             if ((flags & (VALUE_CLASS | SEALED | ABSTRACT)) == (VALUE_CLASS | SEALED) ||
+                 (flags & (VALUE_CLASS | NON_SEALED | ABSTRACT)) == (VALUE_CLASS | NON_SEALED)) {
+                 log.error(pos, Errors.NonAbstractValueClassCantBeSealedOrNonSealed);
+             }
              // Interfaces are always ABSTRACT
              if ((flags & INTERFACE) != 0) implicit |= ABSTRACT;
  
+             if ((flags & (INTERFACE | VALUE_CLASS)) == 0) {
+                 implicit |= IDENTITY_TYPE;
+             }
+ 
              if ((flags & ENUM) != 0) {
!                 // enums can't be declared abstract, final, sealed or non-sealed or value
!                 mask &= ~(ABSTRACT | FINAL | SEALED | NON_SEALED | VALUE_CLASS);
                  implicit |= implicitEnumFinalFlag(tree);
              }
              if ((flags & RECORD) != 0) {
                  // records can't be declared abstract
                  mask &= ~ABSTRACT;

*** 1285,10 ***
--- 1354,15 ---
              if ((flags & STRICTFP) != 0) {
                  warnOnExplicitStrictfp(pos);
              }
              // Imply STRICTFP if owner has STRICTFP set.
              implicit |= sym.owner.flags_field & STRICTFP;
+ 
+             // concrete value classes are implicitly final
+             if ((flags & (ABSTRACT | INTERFACE | VALUE_CLASS)) == VALUE_CLASS) {
+                 implicit |= FINAL;
+             }
              break;
          default:
              throw new AssertionError();
          }
          long illegal = flags & ExtendedStandardFlags & ~mask;

*** 1299,12 ***
              }
              else {
                  log.error(pos,
                          Errors.ModNotAllowedHere(asFlagSet(illegal)));
              }
!         }
-         else if ((sym.kind == TYP ||
                    // ISSUE: Disallowing abstract&private is no longer appropriate
                    // in the presence of inner classes. Should it be deleted here?
                    checkDisjoint(pos, flags,
                                  ABSTRACT,
                                  PRIVATE | STATIC | DEFAULT))
--- 1373,11 ---
              }
              else {
                  log.error(pos,
                          Errors.ModNotAllowedHere(asFlagSet(illegal)));
              }
!         } else if ((sym.kind == TYP ||
                    // ISSUE: Disallowing abstract&private is no longer appropriate
                    // in the presence of inner classes. Should it be deleted here?
                    checkDisjoint(pos, flags,
                                  ABSTRACT,
                                  PRIVATE | STATIC | DEFAULT))

*** 1323,11 ***
                   &&
                   checkDisjoint(pos, flags,
                                 PRIVATE,
                                 PUBLIC | PROTECTED)
                   &&
!                  checkDisjoint(pos, flags,
                                 FINAL,
                                 VOLATILE)
                   &&
                   (sym.kind == TYP ||
                    checkDisjoint(pos, flags,
--- 1396,12 ---
                   &&
                   checkDisjoint(pos, flags,
                                 PRIVATE,
                                 PUBLIC | PROTECTED)
                   &&
!                  // we are using `implicit` here as instance fields of value classes are implicitly final
+                  checkDisjoint(pos, flags | implicit,
                                 FINAL,
                                 VOLATILE)
                   &&
                   (sym.kind == TYP ||
                    checkDisjoint(pos, flags,

*** 1339,11 ***
                   && checkDisjoint(pos, flags,
                                  SEALED,
                             FINAL | NON_SEALED)
                   && checkDisjoint(pos, flags,
                                  SEALED,
!                                 ANNOTATION)) {
              // skip
          }
          return flags & (mask | ~ExtendedStandardFlags) | implicit;
      }
  
--- 1413,17 ---
                   && checkDisjoint(pos, flags,
                                  SEALED,
                             FINAL | NON_SEALED)
                   && checkDisjoint(pos, flags,
                                  SEALED,
!                                 ANNOTATION)
+                 && checkDisjoint(pos, flags,
+                                 VALUE_CLASS,
+                                 ANNOTATION)
+                 && checkDisjoint(pos, flags,
+                                 VALUE_CLASS,
+                                 INTERFACE) ) {
              // skip
          }
          return flags & (mask | ~ExtendedStandardFlags) | implicit;
      }
  

*** 2156,10 ***
--- 2236,15 ---
              if (m.overrides(syms.enumFinalFinalize, origin, types, false)) {
                  log.error(tree.pos(), Errors.EnumNoFinalize);
                  return;
              }
          }
+         if (allowValueClasses && origin.isValueClass() && names.finalize.equals(m.name)) {
+             if (m.overrides(syms.objectFinalize, origin, types, false)) {
+                 log.warning(tree.pos(), Warnings.ValueFinalize);
+             }
+         }
          if (allowRecords && origin.isRecord()) {
              // let's find out if this is a user defined accessor in which case the @Override annotation is acceptable
              Optional<? extends RecordComponent> recordComponent = origin.getRecordComponents().stream()
                      .filter(rc -> rc.accessor == tree.sym && (rc.accessor.flags_field & GENERATED_MEMBER) == 0).findFirst();
              if (recordComponent.isPresent()) {

*** 2584,10 ***
--- 2669,22 ---
              for (List<Type> m = supertypes; m != l; m = m.tail)
                  if (!checkCompatibleAbstracts(pos, l.head, m.head, c))
                      return;
          }
          checkCompatibleConcretes(pos, c);
+ 
+         Type identitySuper = null;
+         for (Type t : types.closure(c)) {
+             if (t != c) {
+                 if (t.isIdentityClass() && (t.tsym.flags() & VALUE_BASED) == 0)
+                     identitySuper = t;
+                 if (c.isValueClass() && identitySuper != null && identitySuper.tsym != syms.objectType.tsym) { // Object is special
+                     log.error(pos, Errors.ValueTypeHasIdentitySuperType(c, identitySuper));
+                     break;
+                 }
+             }
+         }
      }
  
      /** Check that all non-override equivalent methods accessible from 'site'
       *  are mutually compatible (JLS 8.4.8/9.4.1).
       *

*** 4896,12 ***
      }
  
      /**
       * 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
--- 4993,12 ---
      }
  
      /**
       * Check structure of serialization declarations.
       */
!     public void checkSerialStructure(Env<AttrContext> env, JCClassDecl tree, ClassSymbol c) {
!         (new SerialTypeVisitor(env)).visit(c, tree);
      }
  
      /**
       * This visitor will warn if a serialization-related field or
       * method is declared in a suspicious or incorrect way. In

*** 4928,12 ***
       * 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",
--- 5025,14 ---
       * 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> {
!         Env<AttrContext> env;
+         SerialTypeVisitor(Env<AttrContext> env) {
              this.lint = Check.this.lint;
+             this.env = env;
          }
  
          private static final Set<String> serialMethodNames =
              Set.of("writeObject", "writeReplace",
                     "readObject",  "readObjectNoData",

*** 4989,10 ***
--- 5088,11 ---
                       .iterator()
                       .hasNext();
  
              // Check declarations of serialization-related methods and
              // fields
+             final boolean[] hasWriteReplace = {false};
              for(Symbol el : c.getEnclosedElements()) {
                  runUnderLint(el, p, (enclosed, tree) -> {
                      String name = null;
                      switch(enclosed.getKind()) {
                      case FIELD -> {

*** 5063,29 ***
                          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
--- 5163,64 ---
                          var method = (MethodSymbol)enclosed;
                          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);
                              default ->  throw new AssertionError();
                              }
                          }
                      }
                      }
                  });
              }
!             if (!hasWriteReplace[0] &&
+                     (c.isValueClass() || hasAbstractValueSuperClass(c, Set.of(syms.numberType.tsym))) &&
+                     !c.isAbstract() && !c.isRecord() &&
+                     types.unboxedType(c.type) == Type.noType) {
+                 // we need to check if the class is inheriting an appropriate writeReplace method
+                 MethodSymbol ms = null;
+                 Log.DiagnosticHandler discardHandler = new Log.DiscardDiagnosticHandler(log);
+                 try {
+                     ms = rs.resolveInternalMethod(env.tree, env, c.type, names.writeReplace, List.nil(), List.nil());
+                 } catch (FatalError fe) {
+                     // ignore no method was found
+                 } finally {
+                     log.popDiagnosticHandler(discardHandler);
+                 }
+                 if (ms == null || !hasAppropriateWriteReplace(p, ms, false)) {
+                     log.warning(LintCategory.SERIAL, p,
+                             c.isValueClass() ? Warnings.SerializableValueClassWithoutWriteReplace1 :
+                                     Warnings.SerializableValueClassWithoutWriteReplace2);
+                 }
+             }
              return null;
          }
  
          boolean canBeSerialized(Type type) {
              return type.isPrimitive() || rs.isSerializable(type);
          }
  
+         private boolean hasAbstractValueSuperClass(Symbol c, Set<Symbol> excluding) {
+             while (c.getKind() == ElementKind.CLASS) {
+                 Type sup = ((ClassSymbol)c).getSuperclass();
+                 if (!sup.hasTag(CLASS) || sup.isErroneous() ||
+                         sup.tsym == syms.objectType.tsym) {
+                     return false;
+                 }
+                 // if it is a value super class it has to be abstract
+                 if (sup.isValueClass() && !excluding.contains(sup.tsym)) {
+                     return true;
+                 }
+                 c = sup.tsym;
+             }
+             return false;
+         }
+ 
          /**
           * Check that Externalizable class needs a public no-arg
           * constructor.
           *
           * Check that a Serializable class has access to the no-arg

*** 5205,61 ***
              // 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);
          }
  
          private void checkWriteExternalRecord(JCClassDecl tree, Element e, MethodSymbol method, boolean isExtern) {
              //public void writeExternal(ObjectOutput) throws IOException
              checkExternMethodRecord(tree, e, method, syms.objectOutputType, isExtern);
--- 5340,61 ---
              // 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);
          }
  
!         private boolean hasAppropriateWriteReplace(JCClassDecl tree, MethodSymbol method, boolean warn) {
              // ANY-ACCESS-MODIFIER Object writeReplace() throws
              // ObjectStreamException
  
              // 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) &&
!                     hasExpectedExceptions(tree, method, warn, 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);
!             isExpectedReturnType(tree, method, syms.voidType, true);
              checkOneArg(tree, e, 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 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);
          }
  
          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
!             isConcreteInstanceMethod(tree, method, true);
!             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) {
              //public void writeExternal(ObjectOutput) throws IOException
              checkExternMethodRecord(tree, e, method, syms.objectOutputType, isExtern);

*** 5505,11 ***
                      }
  
                      case METHOD -> {
                          var method = (MethodSymbol)enclosed;
                          switch(name) {
!                         case "writeReplace" -> checkWriteReplace(tree, e, method);
                          case "readResolve"  -> checkReadResolve(tree, e, method);
  
                          case "writeExternal" -> checkWriteExternalRecord(tree, e, method, isExtern);
                          case "readExternal"  -> checkReadExternalRecord(tree, e, method, isExtern);
  
--- 5640,11 ---
                      }
  
                      case METHOD -> {
                          var method = (MethodSymbol)enclosed;
                          switch(name) {
!                         case "writeReplace" -> hasAppropriateWriteReplace(tree, method, true);
                          case "readResolve"  -> checkReadResolve(tree, e, method);
  
                          case "writeExternal" -> checkWriteExternalRecord(tree, e, method, isExtern);
                          case "readExternal"  -> checkReadExternalRecord(tree, e, method, isExtern);
  

*** 5523,36 ***
                      }}});
              }
              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,
--- 5658,44 ---
                      }}});
              }
              return null;
          }
  
!         boolean isConcreteInstanceMethod(JCClassDecl tree,
!                                          MethodSymbol method,
!                                          boolean warn) {
              if ((method.flags() & (STATIC | ABSTRACT)) != 0) {
+                 if (warn) {
                      log.warning(LintCategory.SERIAL,
!                             TreeInfo.diagnosticPositionFor(method, tree),
!                             Warnings.SerialConcreteInstanceMethod(method.getSimpleName()));
+                 }
+                 return false;
              }
+             return true;
          }
  
!         private boolean isExpectedReturnType(JCClassDecl tree,
!                                           MethodSymbol method,
!                                           Type expectedReturnType,
!                                           boolean warn) {
              // 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)) {
!                 if (warn) {
+                     log.warning(LintCategory.SERIAL,
                              TreeInfo.diagnosticPositionFor(method, tree),
                              Warnings.SerialMethodUnexpectedReturnType(method.getSimpleName(),
!                                     rtype, expectedReturnType));
+                 }
+                 return false;
              }
+             return true;
          }
  
          private void checkOneArg(JCClassDecl tree,
                                   Element enclosing,
                                   MethodSymbol method,

*** 5586,17 ***
              return (parameters.size() == 1) &&
                  types.isSameType(parameters.get(0).asType(), expectedType);
          }
  
  
!         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())) {
--- 5729,21 ---
              return (parameters.size() == 1) &&
                  types.isSameType(parameters.get(0).asType(), expectedType);
          }
  
  
!         boolean hasNoArgs(JCClassDecl tree, MethodSymbol method, boolean warn) {
              var parameters = method.getParameters();
              if (!parameters.isEmpty()) {
!                 if (warn) {
+                     log.warning(LintCategory.SERIAL,
                              TreeInfo.diagnosticPositionFor(parameters.get(0), tree),
                              Warnings.SerialMethodNoArgs(method.getSimpleName()));
+                 }
+                 return false;
              }
+             return true;
          }
  
          private void checkExternalizable(JCClassDecl tree, Element enclosing, MethodSymbol method) {
              // If the enclosing class is externalizable, warn for the method
              if (isExternalizable((Type)enclosing.asType())) {

*** 5605,14 ***
                              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
--- 5752,14 ---
                              Warnings.IneffectualSerialMethodExternalizable(method.getSimpleName()));
              }
              return;
          }
  
!         private boolean hasExpectedExceptions(JCClassDecl tree,
!                                               MethodSymbol method,
!                                               boolean warn,
!                                               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

*** 5627,18 ***
                              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 {
--- 5774,21 ---
                              declared = true;
                              continue;
                          }
                      }
                      if (!declared) {
!                         if (warn) {
+                             log.warning(LintCategory.SERIAL,
                                      TreeInfo.diagnosticPositionFor(method, tree),
                                      Warnings.SerialMethodUnexpectedException(method.getSimpleName(),
!                                             thrownType));
+                         }
+                         return false;
                      }
                  }
              }
!             return true;
          }
  
          private <E extends Element> Void runUnderLint(E symbol, JCClassDecl p, BiConsumer<E, JCClassDecl> task) {
              Lint prevLint = lint;
              try {
< prev index next >