< prev index next > src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
Print this page
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;
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.
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;
/** Are sealed classes allowed
*/
private final boolean allowSealed;
+ /** Are value classes allowed
+ */
+ private final boolean allowValueClasses;
+
/* *************************************************************************
* Errors and Warnings
**************************************************************************/
Lint setLint(Lint newLint) {
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)) {
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.
*/
mask = ReceiverParamFlags;
else if (sym.owner.kind != TYP)
mask = LocalVarFlags;
else if ((sym.owner.flags_field & INTERFACE) != 0)
mask = implicit = InterfaceVarFlags;
- else
- mask = VarFlags;
+ 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
}
} else {
mask = implicit = InterfaceMethodFlags;
}
} else if ((sym.owner.flags_field & RECORD) != 0) {
- mask = RecordMethodFlags;
+ mask = ((sym.owner.flags_field & VALUE_CLASS) != 0 && (flags & Flags.STATIC) == 0) ?
+ RecordMethodFlags & ~SYNCHRONIZED : RecordMethodFlags;
} else {
- mask = MethodFlags;
+ // 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.
(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;
+ 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 ||
// 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
- mask &= ~(ABSTRACT | FINAL | SEALED | NON_SEALED);
+ // 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;
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;
}
else {
log.error(pos,
Errors.ModNotAllowedHere(asFlagSet(illegal)));
}
- }
- else if ((sym.kind == TYP ||
+ } 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))
&&
checkDisjoint(pos, flags,
PRIVATE,
PUBLIC | PROTECTED)
&&
- checkDisjoint(pos, flags,
+ // 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,
&& checkDisjoint(pos, flags,
SEALED,
FINAL | NON_SEALED)
&& checkDisjoint(pos, flags,
SEALED,
- ANNOTATION)) {
+ ANNOTATION)
+ && checkDisjoint(pos, flags,
+ VALUE_CLASS,
+ ANNOTATION)
+ && checkDisjoint(pos, flags,
+ VALUE_CLASS,
+ INTERFACE) ) {
// skip
}
return flags & (mask | ~ExtendedStandardFlags) | implicit;
}
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()) {
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).
*
}
/**
* Check structure of serialization declarations.
*/
- public void checkSerialStructure(JCClassDecl tree, ClassSymbol c) {
- (new SerialTypeVisitor()).visit(c, tree);
+ 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
* 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() {
+ 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",
.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 -> {
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 "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
// readObject and writeObject methods and is generally
// innocuous.
// private void writeObject(ObjectOutputStream stream) throws IOException
checkPrivateNonStaticMethod(tree, method);
- checkReturnType(tree, e, method, syms.voidType);
+ isExpectedReturnType(tree, method, syms.voidType, true);
checkOneArg(tree, e, method, syms.objectOutputStreamType);
- checkExceptions(tree, e, method, syms.ioExceptionType);
+ hasExpectedExceptions(tree, method, true, syms.ioExceptionType);
checkExternalizable(tree, e, method);
}
- private void checkWriteReplace(JCClassDecl tree, Element e, MethodSymbol 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
- checkConcreteInstanceMethod(tree, e, method);
- checkReturnType(tree, e, method, syms.objectType);
- checkNoArgs(tree, e, method);
- checkExceptions(tree, e, method, syms.objectStreamExceptionType);
+ 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);
- checkReturnType(tree, e, method, syms.voidType);
+ isExpectedReturnType(tree, method, syms.voidType, true);
checkOneArg(tree, e, method, syms.objectInputStreamType);
- checkExceptions(tree, e, method, syms.ioExceptionType, syms.classNotFoundExceptionType);
+ 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);
- checkReturnType(tree, e, method, syms.voidType);
- checkNoArgs(tree, e, method);
- checkExceptions(tree, e, method, syms.objectStreamExceptionType);
+ 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
- checkConcreteInstanceMethod(tree, e, method);
- checkReturnType(tree,e, method, syms.objectType);
- checkNoArgs(tree, e, method);
- checkExceptions(tree, e, method, syms.objectStreamExceptionType);
+ 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);
}
case METHOD -> {
var method = (MethodSymbol)enclosed;
switch(name) {
- case "writeReplace" -> checkWriteReplace(tree, e, method);
+ 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);
}}});
}
return null;
}
- void checkConcreteInstanceMethod(JCClassDecl tree,
- Element enclosing,
- MethodSymbol method) {
+ 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()));
+ TreeInfo.diagnosticPositionFor(method, tree),
+ Warnings.SerialConcreteInstanceMethod(method.getSimpleName()));
+ }
+ return false;
}
+ return true;
}
- private void checkReturnType(JCClassDecl tree,
- Element enclosing,
- MethodSymbol method,
- Type expectedReturnType) {
+ 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)) {
- log.warning(LintCategory.SERIAL,
+ if (warn) {
+ log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialMethodUnexpectedReturnType(method.getSimpleName(),
- rtype, expectedReturnType));
+ rtype, expectedReturnType));
+ }
+ return false;
}
+ return true;
}
private void checkOneArg(JCClassDecl tree,
Element enclosing,
MethodSymbol method,
return (parameters.size() == 1) &&
types.isSameType(parameters.get(0).asType(), expectedType);
}
- private void checkNoArgs(JCClassDecl tree, Element enclosing, MethodSymbol method) {
+ boolean hasNoArgs(JCClassDecl tree, MethodSymbol method, boolean warn) {
var parameters = method.getParameters();
if (!parameters.isEmpty()) {
- log.warning(LintCategory.SERIAL,
+ 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())) {
Warnings.IneffectualSerialMethodExternalizable(method.getSimpleName()));
}
return;
}
- private void checkExceptions(JCClassDecl tree,
- Element enclosing,
- MethodSymbol method,
- Type... declaredExceptions) {
+ 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
declared = true;
continue;
}
}
if (!declared) {
- log.warning(LintCategory.SERIAL,
+ if (warn) {
+ log.warning(LintCategory.SERIAL,
TreeInfo.diagnosticPositionFor(method, tree),
Warnings.SerialMethodUnexpectedException(method.getSimpleName(),
- thrownType));
+ thrownType));
+ }
+ return false;
}
}
}
- return;
+ return true;
}
private <E extends Element> Void runUnderLint(E symbol, JCClassDecl p, BiConsumer<E, JCClassDecl> task) {
Lint prevLint = lint;
try {
< prev index next >