diff --git a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java index 1ebd22dc6ab48..08d5cb85ef24d 100644 --- a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java +++ b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java @@ -304,13 +304,18 @@ public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFil "Ljdk/internal/ValueBased;"; private static final String VALUE_BASED_ANNOTATION_INTERNAL = "Ljdk/internal/ValueBased+Annotation;"; + private static final String REQUIRES_IDENTITY_ANNOTATION = + "Ljdk/internal/RequiresIdentity;"; + private static final String REQUIRES_IDENTITY_ANNOTATION_INTERNAL = + "Ljdk/internal/RequiresIdentity+Annotation;"; public static final Set HARDCODED_ANNOTATIONS = new HashSet<>( List.of("Ljdk/Profile+Annotation;", "Lsun/Proprietary+Annotation;", PREVIEW_FEATURE_ANNOTATION_OLD, PREVIEW_FEATURE_ANNOTATION_NEW, VALUE_BASED_ANNOTATION, - RESTRICTED_ANNOTATION)); + RESTRICTED_ANNOTATION, + REQUIRES_IDENTITY_ANNOTATION)); private void stripNonExistentAnnotations(LoadDescriptions data) { Set allClasses = data.classes.name2Class.keySet(); @@ -1021,6 +1026,12 @@ private Annotation createAnnotation(AnnotationDescription desc) { annotationType = VALUE_BASED_ANNOTATION_INTERNAL; } + if (REQUIRES_IDENTITY_ANNOTATION.equals(annotationType)) { + //the non-public RequiresIdentity annotation will not be available in ct.sym, + //replace with purely synthetic javac-internal annotation: + annotationType = REQUIRES_IDENTITY_ANNOTATION_INTERNAL; + } + if (RESTRICTED_ANNOTATION.equals(annotationType)) { //the non-public Restricted annotation will not be available in ct.sym, //replace with purely synthetic javac-internal annotation: @@ -2202,6 +2213,7 @@ private boolean readAttribute(FeatureDescription feature, Attribute attr) { chd.permittedSubclasses = a.permittedSubclasses().stream().map(ClassEntry::asInternalName).collect(Collectors.toList()); } case ModuleMainClassAttribute a -> ((ModuleHeaderDescription) feature).moduleMainClass = a.mainClass().asInternalName(); + case RuntimeVisibleTypeAnnotationsAttribute a -> {/* do nothing for now */} default -> throw new IllegalArgumentException("Unhandled attribute: " + attr.attributeName()); // Do nothing } diff --git a/src/java.base/share/classes/java/lang/ref/Cleaner.java b/src/java.base/share/classes/java/lang/ref/Cleaner.java index c5a163831c893..20a964be21da9 100644 --- a/src/java.base/share/classes/java/lang/ref/Cleaner.java +++ b/src/java.base/share/classes/java/lang/ref/Cleaner.java @@ -219,7 +219,7 @@ public static Cleaner create(ThreadFactory threadFactory) { * @param action a {@code Runnable} to invoke when the object becomes phantom reachable * @return a {@code Cleanable} instance */ - public Cleanable register(Object obj, Runnable action) { + public Cleanable register(@jdk.internal.RequiresIdentity Object obj, Runnable action) { Objects.requireNonNull(obj, "obj"); Objects.requireNonNull(action, "action"); return new CleanerImpl.PhantomCleanableRef(obj, this, action); diff --git a/src/java.base/share/classes/java/lang/ref/PhantomReference.java b/src/java.base/share/classes/java/lang/ref/PhantomReference.java index 3f01144afe618..a6e144dc7b6e2 100644 --- a/src/java.base/share/classes/java/lang/ref/PhantomReference.java +++ b/src/java.base/share/classes/java/lang/ref/PhantomReference.java @@ -51,7 +51,7 @@ * @since 1.2 */ -public non-sealed class PhantomReference extends Reference { +public non-sealed class PhantomReference<@jdk.internal.RequiresIdentity T> extends Reference { /** * Returns this reference object's referent. Because the referent of a @@ -101,7 +101,7 @@ void clearImpl() { * @param q the queue with which the reference is to be registered, * or {@code null} if registration is not required */ - public PhantomReference(T referent, ReferenceQueue q) { + public PhantomReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue q) { super(referent, q); } diff --git a/src/java.base/share/classes/java/lang/ref/Reference.java b/src/java.base/share/classes/java/lang/ref/Reference.java index e3e0258c3c949..c83f197380a31 100644 --- a/src/java.base/share/classes/java/lang/ref/Reference.java +++ b/src/java.base/share/classes/java/lang/ref/Reference.java @@ -44,7 +44,7 @@ * @sealedGraph */ -public abstract sealed class Reference +public abstract sealed class Reference<@jdk.internal.RequiresIdentity T> permits PhantomReference, SoftReference, WeakReference, FinalReference { /* The state of a Reference object is characterized by two attributes. It diff --git a/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java b/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java index 9f3fad760ba13..d3879d4a8fcc3 100644 --- a/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java +++ b/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java @@ -46,7 +46,7 @@ * @since 1.2 */ -public class ReferenceQueue { +public class ReferenceQueue<@jdk.internal.RequiresIdentity T> { private static class Null extends ReferenceQueue { @Override boolean enqueue(Reference r) { diff --git a/src/java.base/share/classes/java/lang/ref/SoftReference.java b/src/java.base/share/classes/java/lang/ref/SoftReference.java index 925f894fdabaa..0bc372eb105d3 100644 --- a/src/java.base/share/classes/java/lang/ref/SoftReference.java +++ b/src/java.base/share/classes/java/lang/ref/SoftReference.java @@ -62,7 +62,7 @@ * @since 1.2 */ -public non-sealed class SoftReference extends Reference { +public non-sealed class SoftReference<@jdk.internal.RequiresIdentity T> extends Reference { /** * Timestamp clock, updated by the garbage collector @@ -82,7 +82,7 @@ public non-sealed class SoftReference extends Reference { * * @param referent object the new soft reference will refer to */ - public SoftReference(T referent) { + public SoftReference(@jdk.internal.RequiresIdentity T referent) { super(referent); this.timestamp = clock; } @@ -96,7 +96,7 @@ public SoftReference(T referent) { * or {@code null} if registration is not required * */ - public SoftReference(T referent, ReferenceQueue q) { + public SoftReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue q) { super(referent, q); this.timestamp = clock; } diff --git a/src/java.base/share/classes/java/lang/ref/WeakReference.java b/src/java.base/share/classes/java/lang/ref/WeakReference.java index 1a733bb32b97d..4b4f834485e99 100644 --- a/src/java.base/share/classes/java/lang/ref/WeakReference.java +++ b/src/java.base/share/classes/java/lang/ref/WeakReference.java @@ -46,7 +46,7 @@ * @since 1.2 */ -public non-sealed class WeakReference extends Reference { +public non-sealed class WeakReference<@jdk.internal.RequiresIdentity T> extends Reference { /** * Creates a new weak reference that refers to the given object. The new @@ -54,7 +54,7 @@ public non-sealed class WeakReference extends Reference { * * @param referent object the new weak reference will refer to */ - public WeakReference(T referent) { + public WeakReference(@jdk.internal.RequiresIdentity T referent) { super(referent); } @@ -66,7 +66,7 @@ public WeakReference(T referent) { * @param q the queue with which the reference is to be registered, * or {@code null} if registration is not required */ - public WeakReference(T referent, ReferenceQueue q) { + public WeakReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue q) { super(referent, q); } diff --git a/src/java.base/share/classes/java/util/WeakHashMap.java b/src/java.base/share/classes/java/util/WeakHashMap.java index 276e8731d847d..7f2960eb2ad39 100644 --- a/src/java.base/share/classes/java/util/WeakHashMap.java +++ b/src/java.base/share/classes/java/util/WeakHashMap.java @@ -132,7 +132,7 @@ * @see java.util.HashMap * @see java.lang.ref.WeakReference */ -public class WeakHashMap +public class WeakHashMap<@jdk.internal.RequiresIdentity K,V> extends AbstractMap implements Map { @@ -457,7 +457,7 @@ Entry getEntry(Object key) { * (A {@code null} return can also indicate that the map * previously associated {@code null} with {@code key}.) */ - public V put(K key, V value) { + public V put(@jdk.internal.RequiresIdentity K key, V value) { Object k = maskNull(key); int h = hash(k); Entry[] tab = getTable(); diff --git a/src/java.base/share/classes/jdk/internal/RequiresIdentity.java b/src/java.base/share/classes/jdk/internal/RequiresIdentity.java new file mode 100644 index 0000000000000..5b54debd095c2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/RequiresIdentity.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_PARAMETER; + +/** + * Indicates that the annotated parameter or type parameter is not expected to be a + * Value Based class. + * Using a parameter or type parameter of a value-based classes + * should produce warnings about behavior that is inconsistent with identity based semantics. + * + * Note this internal annotation is handled specially by the javac compiler. + * To work properly with {@code --release older-release}, it requires special + * handling in {@code make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java} + * and {@code src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java}. + * + * @since 25 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value={PARAMETER, TYPE_PARAMETER}) +public @interface RequiresIdentity { +} diff --git a/src/java.base/share/classes/jdk/internal/ValueBased.java b/src/java.base/share/classes/jdk/internal/ValueBased.java index d27ab73c01f68..160ffce874a1b 100644 --- a/src/java.base/share/classes/jdk/internal/ValueBased.java +++ b/src/java.base/share/classes/jdk/internal/ValueBased.java @@ -46,4 +46,3 @@ @Target(value={TYPE}) public @interface ValueBased { } - diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java index fb1b782d87e39..ede2511f35c48 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java @@ -25,11 +25,15 @@ package com.sun.tools.javac.code; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; import java.util.stream.Stream; import com.sun.tools.javac.main.Option; @@ -119,7 +123,7 @@ public Lint suppress(LintCategory... lc) { private EnumSet values; private EnumSet suppressedValues; - private static final Map map = new ConcurrentHashMap<>(20); + private static final Map map = new LinkedHashMap<>(40); @SuppressWarnings("this-escape") protected Lint(Context context) { @@ -149,10 +153,10 @@ private void initializeRootIfNeeded() { return; // Initialize enabled categories based on "-Xlint" flags - if (options.isSet(Option.XLINT) || options.isSet(Option.XLINT_CUSTOM, "all")) { + if (options.isSet(Option.XLINT) || options.isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_ALL)) { // If -Xlint or -Xlint:all is given, enable all categories by default values = EnumSet.allOf(LintCategory.class); - } else if (options.isSet(Option.XLINT_CUSTOM, "none")) { + } else if (options.isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_NONE)) { // if -Xlint:none is given, disable all categories by default values = LintCategory.newEmptySet(); } else { @@ -173,15 +177,15 @@ private void initializeRootIfNeeded() { if (!options.isSet(Option.PREVIEW)) { values.add(LintCategory.PREVIEW); } - values.add(LintCategory.SYNCHRONIZATION); + values.add(LintCategory.IDENTITY); values.add(LintCategory.INCUBATING); } // Look for specific overrides for (LintCategory lc : LintCategory.values()) { - if (options.isSet(Option.XLINT_CUSTOM, lc.option)) { + if (options.isExplicitlyEnabled(Option.XLINT, lc)) { values.add(lc); - } else if (options.isSet(Option.XLINT_CUSTOM, "-" + lc.option)) { + } else if (options.isExplicitlyDisabled(Option.XLINT, lc)) { values.remove(lc); } } @@ -261,6 +265,11 @@ public enum LintCategory { */ FINALLY("finally"), + /** + * Warn about uses of @ValueBased classes where an identity class is expected. + */ + IDENTITY("identity", true, "synchronization"), + /** * Warn about use of incubating modules. * @@ -363,11 +372,6 @@ public enum LintCategory { */ STRICTFP("strictfp"), - /** - * Warn about synchronization attempts on instances of @ValueBased classes. - */ - SYNCHRONIZATION("synchronization"), - /** * Warn about issues relating to use of text blocks * @@ -410,10 +414,14 @@ public enum LintCategory { this(option, true); } - LintCategory(String option, boolean annotationSuppression) { + LintCategory(String option, boolean annotationSuppression, String... aliases) { this.option = option; this.annotationSuppression = annotationSuppression; - map.put(option, this); + ArrayList optionList = new ArrayList<>(1 + aliases.length); + optionList.add(option); + Collections.addAll(optionList, aliases); + this.optionList = Collections.unmodifiableList(optionList); + this.optionList.forEach(ident -> map.put(ident, this)); } /** @@ -426,13 +434,23 @@ public static Optional get(String option) { return Optional.ofNullable(map.get(option)); } + /** + * Get all lint category option strings and aliases. + */ + public static Set options() { + return Collections.unmodifiableSet(map.keySet()); + } + public static EnumSet newEmptySet() { return EnumSet.noneOf(LintCategory.class); } - /** Get the string representing this category in @SuppressAnnotations and -Xlint options. */ + /** Get the "canonical" string representing this category in @SuppressAnnotations and -Xlint options. */ public final String option; + /** Get a list containing "option" followed by zero or more aliases. */ + public final List optionList; + /** Does this category support being suppressed by the {@code @SuppressWarnings} annotation? */ public final boolean annotationSuppression; } @@ -496,20 +514,6 @@ public EnumSet suppressionsFrom(Symbol symbol) { return suppressions; } - /** - * Retrieve the recognized lint categories suppressed by the given @SuppressWarnings annotation. - * - * @param annotation @SuppressWarnings annotation, or null - * @return set of lint categories, possibly empty but never null - */ - private EnumSet suppressionsFrom(JCAnnotation annotation) { - initializeSymbolsIfNeeded(); - if (annotation == null) - return LintCategory.newEmptySet(); - Assert.check(annotation.attribute.type.tsym == syms.suppressWarningsType.tsym); - return suppressionsFrom(Stream.of(annotation).map(anno -> anno.attribute)); - } - // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions private EnumSet suppressionsFrom(Stream attributes) { initializeSymbolsIfNeeded(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java index 74c740656f4a3..2079532eb2c2e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java @@ -228,6 +228,8 @@ public static Symtab instance(Context context) { public final Type constantBootstrapsType; public final Type valueBasedType; public final Type valueBasedInternalType; + public final Type requiresIdentityType; + public final Type requiresIdentityInternalType; public final Type classDescType; public final Type enumDescType; @@ -610,6 +612,8 @@ public R accept(ElementVisitor v, P p) { constantBootstrapsType = enterClass("java.lang.invoke.ConstantBootstraps"); valueBasedType = enterClass("jdk.internal.ValueBased"); valueBasedInternalType = enterSyntheticAnnotation("jdk.internal.ValueBased+Annotation"); + requiresIdentityType = enterClass("jdk.internal.RequiresIdentity"); + requiresIdentityInternalType = enterSyntheticAnnotation("jdk.internal.RequiresIdentity+Annotation"); classDescType = enterClass("java.lang.constant.ClassDesc"); enumDescType = enterClass("java.lang.Enum$EnumDesc"); // For serialization lint checking diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java index bd2ea7048311b..fa0220b1e1fdd 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java @@ -665,6 +665,10 @@ public boolean isFinal() { return (tsym.flags() & FINAL) != 0; } + public boolean isValueBased() { + return tsym != null && (tsym.flags_field & VALUE_BASED) != 0; + } + /** * Does this type contain occurrences of type t? */ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 8f955fbb392fb..b4504502b6fa4 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -1047,8 +1047,10 @@ public void visitMethodDef(JCMethodDecl tree) { chk.validate(tree.typarams, localEnv); // Check that result type is well-formed. - if (tree.restype != null && !tree.restype.type.hasTag(VOID)) + if (tree.restype != null && !tree.restype.type.hasTag(VOID)) { chk.validate(tree.restype, localEnv); + } + chk.checkRequiresIdentity(tree, env.info.lint); // Check that receiver type is well-formed. if (tree.recvparam != null) { @@ -1328,6 +1330,7 @@ public void visitVarDef(JCVariableDecl tree) { log.error(tree, Errors.IllegalRecordComponentName(v)); } } + chk.checkRequiresIdentity(tree, env.info.lint); } finally { chk.setLint(prevLint); @@ -1949,17 +1952,12 @@ private Symbol enumConstant(JCTree tree, Type enumType) { public void visitSynchronized(JCSynchronized tree) { chk.checkRefType(tree.pos(), attribExpr(tree.lock, env)); - if (isValueBased(tree.lock.type)) { + if (tree.lock.type != null && tree.lock.type.isValueBased()) { env.info.lint.logIfEnabled(tree.pos(), LintWarnings.AttemptToSynchronizeOnInstanceOfValueBasedClass); } attribStat(tree.body, env); result = null; } - // where - private boolean isValueBased(Type t) { - return t != null && t.tsym != null && (t.tsym.flags() & VALUE_BASED) != 0; - } - public void visitTry(JCTry tree) { // Create a new local environment with a local @@ -2672,6 +2670,7 @@ public void visitApply(JCMethodInvocation tree) { Type capturedRes = resultInfo.checkContext.inferenceContext().cachedCapture(tree, restype, true); result = check(tree, capturedRes, KindSelector.VAL, resultInfo); } + chk.checkRequiresIdentity(tree, env.info.lint); chk.validate(tree.typeargs, localEnv); } //where @@ -2914,6 +2913,8 @@ else if (!skipNonDiamondPath) { } } + chk.checkRequiresIdentity(tree, env.info.lint); + if (cdef != null) { visitAnonymousClassDefinition(tree, clazz, clazztype, cdef, localEnv, argtypes, typeargtypes, pkind); return; @@ -3793,6 +3794,7 @@ public void visitReference(final JCMemberReference that) { if (!isSpeculativeRound) { checkAccessibleTypes(that, localEnv, resultInfo.checkContext.inferenceContext(), desc, currentTarget); } + chk.checkRequiresIdentity(that, localEnv.info.lint); result = check(that, currentTarget, KindSelector.VAL, resultInfo); } catch (Types.FunctionDescriptorLookupError ex) { JCDiagnostic cause = ex.getDiagnostic(); @@ -4096,6 +4098,7 @@ public void visitBinary(JCBinary tree) { public void visitTypeCast(final JCTypeCast tree) { Type clazztype = attribType(tree.clazz, env); chk.validate(tree.clazz, env, false); + chk.checkRequiresIdentity(tree, env.info.lint); //a fresh environment is required for 292 inference to work properly --- //see Infer.instantiatePolymorphicSignatureInstance() Env localEnv = env.dup(tree); @@ -4237,6 +4240,7 @@ public void visitBindingPattern(JCBindingPattern tree) { } else { matchBindings = new MatchBindings(List.of(v), List.nil()); } + chk.checkRequiresIdentity(tree, env.info.lint); } @Override @@ -5590,6 +5594,8 @@ private void attribClassBody(Env env, ClassSymbol c) { chk.validate(tree.implementing, env); } + chk.checkRequiresIdentity(tree, env.info.lint); + c.markAbstractIfNeeded(types); // If this is a non-abstract class, check that it has no abstract diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 1faa77e88fde9..05730e3f257a7 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -28,6 +28,7 @@ import java.util.*; import java.util.function.BiConsumer; import java.util.function.BiPredicate; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.ToIntBiFunction; @@ -46,6 +47,7 @@ import com.sun.tools.javac.code.Source.Feature; import com.sun.tools.javac.comp.Annotate.AnnotationTypeMetadata; import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.resources.CompilerProperties; import com.sun.tools.javac.resources.CompilerProperties.Errors; import com.sun.tools.javac.resources.CompilerProperties.Fragments; import com.sun.tools.javac.resources.CompilerProperties.Warnings; @@ -5667,4 +5669,180 @@ private Void runUnderLint(E symbol, JCClassDecl p, BiConsume } + void checkRequiresIdentity(JCTree tree, Lint lint) { + switch (tree) { + case JCClassDecl classDecl -> { + Type st = types.supertype(classDecl.sym.type); + if (st != null && + // no need to recheck j.l.Object, shortcut, + st.tsym != syms.objectType.tsym && + // this one could be null, no explicit extends + classDecl.extending != null) { + checkIfIdentityIsExpected(classDecl.extending.pos(), st, lint); + } + for (JCExpression intrface: classDecl.implementing) { + checkIfIdentityIsExpected(intrface.pos(), intrface.type, lint); + } + for (JCTypeParameter tp : classDecl.typarams) { + checkIfIdentityIsExpected(tp.pos(), tp.type, lint); + } + } + case JCVariableDecl variableDecl -> { + if (variableDecl.vartype != null && + (variableDecl.sym.flags_field & RECORD) == 0 || + (variableDecl.sym.flags_field & ~(Flags.PARAMETER | RECORD | GENERATED_MEMBER)) != 0) { + /* we don't want to warn twice so if this variable is a compiler generated parameter of + * a canonical record constructor, we don't want to issue a warning as we will warn the + * corresponding compiler generated private record field anyways + */ + checkIfIdentityIsExpected(variableDecl.vartype.pos(), variableDecl.vartype.type, lint); + } + } + case JCTypeCast typeCast -> checkIfIdentityIsExpected(typeCast.clazz.pos(), typeCast.clazz.type, lint); + case JCBindingPattern bindingPattern -> { + if (bindingPattern.var.vartype != null) { + checkIfIdentityIsExpected(bindingPattern.var.vartype.pos(), bindingPattern.var.vartype.type, lint); + } + } + case JCMethodDecl methodDecl -> { + for (JCTypeParameter tp : methodDecl.typarams) { + checkIfIdentityIsExpected(tp.pos(), tp.type, lint); + } + if (methodDecl.restype != null && !methodDecl.restype.type.hasTag(VOID)) { + checkIfIdentityIsExpected(methodDecl.restype.pos(), methodDecl.restype.type, lint); + } + } + case JCMemberReference mref -> { + checkIfIdentityIsExpected(mref.expr.pos(), mref.target, lint); + checkIfTypeParamsRequiresIdentity(mref.sym.getMetadata(), mref.typeargs, lint); + } + case JCPolyExpression poly + when (poly instanceof JCNewClass || poly instanceof JCMethodInvocation) -> { + if (poly instanceof JCNewClass newClass) { + checkIfIdentityIsExpected(newClass.clazz.pos(), newClass.clazz.type, lint); + } + List argExps = poly instanceof JCNewClass ? + ((JCNewClass)poly).args : + ((JCMethodInvocation)poly).args; + Symbol msym = TreeInfo.symbolFor(poly); + if (msym != null) { + if (!argExps.isEmpty() && msym instanceof MethodSymbol ms && ms.params != null) { + VarSymbol lastParam = ms.params.head; + for (VarSymbol param: ms.params) { + if (param.attribute(syms.requiresIdentityType.tsym) != null && argExps.head.type.isValueBased()) { + lint.logIfEnabled(argExps.head.pos(), LintWarnings.AttemptToUseValueBasedWhereIdentityExpected); + } + lastParam = param; + argExps = argExps.tail; + } + while (argExps != null && !argExps.isEmpty() && lastParam != null) { + if (lastParam.attribute(syms.requiresIdentityType.tsym) != null && argExps.head.type.isValueBased()) { + lint.logIfEnabled(argExps.head.pos(), LintWarnings.AttemptToUseValueBasedWhereIdentityExpected); + } + argExps = argExps.tail; + } + } + checkIfTypeParamsRequiresIdentity( + msym.getMetadata(), + poly instanceof JCNewClass ? + ((JCNewClass)poly).typeargs : + ((JCMethodInvocation)poly).typeargs, + lint); + } + } + default -> throw new AssertionError("unexpected tree " + tree); + } + } + + /** Check if a type required an identity class + */ + private boolean checkIfIdentityIsExpected(DiagnosticPosition pos, Type t, Lint lint) { + if (t != null && + lint != null && + lint.isEnabled(LintCategory.IDENTITY)) { + RequiresIdentityVisitor requiresIdentityVisitor = new RequiresIdentityVisitor(); + // we need to avoid recursion due to self referencing type vars or captures, this is why we need a set + requiresIdentityVisitor.visit(t, new HashSet<>()); + if (requiresIdentityVisitor.requiresWarning) { + lint.logIfEnabled(pos, LintWarnings.AttemptToUseValueBasedWhereIdentityExpected); + return true; + } + } + return false; + } + + // where + private class RequiresIdentityVisitor extends Types.SimpleVisitor> { + boolean requiresWarning = false; + + @Override + public Void visitType(Type t, Set seen) { + return null; + } + + @Override + public Void visitWildcardType(WildcardType t, Set seen) { + return visit(t.type, seen); + } + + @Override + public Void visitTypeVar(TypeVar t, Set seen) { + if (seen.add(t)) { + visit(t.getUpperBound(), seen); + } + return null; + } + + @Override + public Void visitCapturedType(CapturedType t, Set seen) { + if (seen.add(t)) { + visit(t.getUpperBound(), seen); + visit(t.getLowerBound(), seen); + } + return null; + } + + @Override + public Void visitArrayType(ArrayType t, Set seen) { + return visit(t.elemtype, seen); + } + + @Override + public Void visitClassType(ClassType t, Set seen) { + if (t != null && t.tsym != null) { + SymbolMetadata sm = t.tsym.getMetadata(); + if (sm != null && !t.getTypeArguments().isEmpty()) { + if (sm.getTypeAttributes().stream() + .filter(ta -> ta.type.tsym == syms.requiresIdentityType.tsym && + t.getTypeArguments().get(ta.position.parameter_index) != null && + t.getTypeArguments().get(ta.position.parameter_index).isValueBased()).findAny().isPresent()) { + requiresWarning = true; + return null; + } + } + } + visit(t.getEnclosingType(), seen); + for (Type targ : t.getTypeArguments()) { + visit(targ, seen); + } + return null; + } + } // RequiresIdentityVisitor + + private void checkIfTypeParamsRequiresIdentity(SymbolMetadata sm, + List typeParamTrees, + Lint lint) { + if (typeParamTrees != null && !typeParamTrees.isEmpty()) { + for (JCExpression targ : typeParamTrees) { + checkIfIdentityIsExpected(targ.pos(), targ.type, lint); + } + if (sm != null) + sm.getTypeAttributes().stream() + .filter(ta -> (ta.type.tsym == syms.requiresIdentityType.tsym) && + typeParamTrees.get(ta.position.parameter_index).type != null && + typeParamTrees.get(ta.position.parameter_index).type.isValueBased()) + .forEach(ta -> lint.logIfEnabled(typeParamTrees.get(ta.position.parameter_index).pos(), + CompilerProperties.LintWarnings.AttemptToUseValueBasedWhereIdentityExpected)); + } + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java index 596c5704cbef3..a159793fe327f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java @@ -205,7 +205,7 @@ protected Modules(Context context) { allowAccessIntoSystem = options.isUnset(Option.RELEASE); - lintOptions = options.isUnset(Option.XLINT_CUSTOM, "-" + LintCategory.OPTIONS.option); + lintOptions = !options.isExplicitlyDisabled(Option.XLINT, LintCategory.OPTIONS); multiModuleMode = fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH); ClassWriter classWriter = ClassWriter.instance(context); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Arguments.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Arguments.java index 14b42bc01c5d3..9e3a978c3bc9a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Arguments.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Arguments.java @@ -503,8 +503,7 @@ public boolean validate() { } } else { // single-module or legacy mode - boolean lintPaths = options.isUnset(Option.XLINT_CUSTOM, - "-" + LintCategory.PATH.option); + boolean lintPaths = !options.isExplicitlyDisabled(Option.XLINT, LintCategory.PATH); if (lintPaths) { Path outDirParent = outDir.getParent(); if (outDirParent != null && Files.exists(outDirParent.resolve("module-info.class"))) { @@ -577,7 +576,7 @@ public boolean validate() { reportDiag(Errors.SourcepathModulesourcepathConflict); } - boolean lintOptions = options.isUnset(Option.XLINT_CUSTOM, "-" + LintCategory.OPTIONS.option); + boolean lintOptions = !options.isExplicitlyDisabled(Option.XLINT, LintCategory.OPTIONS); if (lintOptions && source.compareTo(Source.DEFAULT) < 0 && !options.isSet(Option.RELEASE)) { if (fm instanceof BaseFileManager baseFileManager) { if (source.compareTo(Source.JDK8) <= 0) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java index e0fdd30c2427f..f4122cebb64af 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java @@ -46,6 +46,7 @@ import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.lang.model.SourceVersion; @@ -491,18 +492,15 @@ public void process(OptionHelper helper, String option) throws InvalidValueExcep log.printRawLines(WriterKind.STDOUT, log.localize(PrefixKind.JAVAC, "opt.help.lint.header")); log.printRawLines(WriterKind.STDOUT, String.format(LINT_KEY_FORMAT, - "all", + LINT_CUSTOM_ALL, log.localize(PrefixKind.JAVAC, "opt.Xlint.all"))); - for (LintCategory lc : LintCategory.values()) { - log.printRawLines(WriterKind.STDOUT, - String.format(LINT_KEY_FORMAT, - lc.option, - log.localize(PrefixKind.JAVAC, - "opt.Xlint.desc." + lc.option))); - } + LintCategory.options().forEach(ident -> log.printRawLines(WriterKind.STDOUT, + String.format(LINT_KEY_FORMAT, + ident, + log.localize(PrefixKind.JAVAC, "opt.Xlint.desc." + ident)))); log.printRawLines(WriterKind.STDOUT, String.format(LINT_KEY_FORMAT, - "none", + LINT_CUSTOM_NONE, log.localize(PrefixKind.JAVAC, "opt.Xlint.none"))); super.process(helper, option); } @@ -835,6 +833,16 @@ private Option[] getSupportedRuntimeOptions() { } }; + /** + * Special lint category key meaning "all lint categories". + */ + public static final String LINT_CUSTOM_ALL = "all"; + + /** + * Special lint category key meaning "no lint categories". + */ + public static final String LINT_CUSTOM_NONE = "none"; + /** * This exception is thrown when an invalid value is given for an option. * The detail string gives a detailed, localized message, suitable for use @@ -1081,6 +1089,17 @@ public OptionKind getKind() { return kind; } + /** + * If this option is named {@code FOO}, obtain the option named {@code FOO_CUSTOM}. + * + * @param option regular option + * @return corresponding custom option + * @throws IllegalArgumentException if no such option exists + */ + public Option getCustom() { + return Option.valueOf(name() + "_CUSTOM"); + } + public boolean isInBasicOptionGroup() { return group == BASIC; } @@ -1364,12 +1383,11 @@ public static PkgInfo get(Options options) { private static Set getXLintChoices() { Set choices = new LinkedHashSet<>(); - choices.add("all"); - for (Lint.LintCategory c : Lint.LintCategory.values()) { - choices.add(c.option); - choices.add("-" + c.option); - } - choices.add("none"); + choices.add(LINT_CUSTOM_ALL); + Lint.LintCategory.options().stream() + .flatMap(ident -> Stream.of(ident, "-" + ident)) + .forEach(choices::add); + choices.add(LINT_CUSTOM_NONE); return choices; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index 46d0438f27dfe..2fa158565cc2d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -4248,10 +4248,14 @@ compiler.err.incorrect.number.of.nested.patterns=\ compiler.warn.declared.using.preview=\ {0} {1} is declared using a preview feature, which may be removed in a future release. -# lint: synchronization +# lint: identity compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class=\ attempt to synchronize on an instance of a value-based class +# lint: identity +compiler.warn.attempt.to.use.value.based.where.identity.expected=\ + use of a value-based class with an operation that expects reliable identity + # 0: type compiler.err.enclosing.class.type.non.denotable=\ enclosing class type: {0}\n\ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties index 1a8be506e7f4d..d74f5f14d15ef 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties @@ -292,7 +292,13 @@ javac.opt.Xlint.desc.restricted=\ Warn about use of restricted methods. javac.opt.Xlint.desc.synchronization=\ - Warn about synchronization attempts on instances of value-based classes. + Warn about synchronization attempts on instances of value-based classes.\n\ +\ This key is a deprecated alias for ''identity'', which has the same uses and\n\ +\ effects. Users are encouraged to use the ''identity'' category for all future\n\ +\ and existing uses of ''synchronization''. + +javac.opt.Xlint.desc.identity=\ + Warn about uses of value-based classes where an identity class is expected. javac.opt.Xdoclint=\ Enable recommended checks for problems in javadoc comments diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java index e7a07a57a8f7f..63f5b0ca75abb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java @@ -29,6 +29,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import com.sun.tools.javac.code.Lint.LintCategory; import com.sun.tools.javac.main.Option; import static com.sun.tools.javac.main.Option.*; @@ -170,6 +171,58 @@ public boolean isUnset(Option option, String value) { return !isSet(option, value); } + /** + * Check whether the given lint category is explicitly enabled or disabled. + * + *

+ * If the category is neither enabled nor disabled, return the given default value. + * + * @param option the plain (non-custom) option + * @param lc the {@link LintCategory} in question + * @param defaultValue presumed default value + * @return true if {@code lc} would be included + */ + public boolean isSet(Option option, LintCategory lc, boolean defaultValue) { + Option customOption = option.getCustom(); + if (lc.optionList.stream().anyMatch(alias -> isSet(customOption, alias))) { + return true; + } + if (lc.optionList.stream().anyMatch(alias -> isSet(customOption, "-" + alias))) { + return false; + } + if (isSet(option) || isSet(customOption, Option.LINT_CUSTOM_ALL)) { + return true; + } + if (isSet(customOption, Option.LINT_CUSTOM_NONE)) { + return false; + } + return defaultValue; + } + + /** + * Determine if a specific {@link LintCategory} was explicitly enabled via a custom option flag + * of the form {@code -Flag:all} or {@code -Flag:key}. + * + * @param option the option + * @param lc the {@link LintCategory} in question + * @return true if {@code lc} has been explicitly enabled + */ + public boolean isExplicitlyEnabled(Option option, LintCategory lc) { + return isSet(option, lc, false); + } + + /** + * Determine if a specific {@link LintCategory} was explicitly disabled via a custom option flag + * of the form {@code -Flag:none} or {@code -Flag:-key}. + * + * @param option the option + * @param lc the {@link LintCategory} in question + * @return true if {@code lc} has been explicitly disabled + */ + public boolean isExplicitlyDisabled(Option option, LintCategory lc) { + return !isSet(option, lc, true); + } + public void put(String name, String value) { values.put(name, value); initialized = true; diff --git a/src/jdk.compiler/share/classes/module-info.java b/src/jdk.compiler/share/classes/module-info.java index 60a9ae0e476b3..33cff9379f2fa 100644 --- a/src/jdk.compiler/share/classes/module-info.java +++ b/src/jdk.compiler/share/classes/module-info.java @@ -164,6 +164,7 @@ * {@code fallthrough} falling through from one case of a {@code switch} statement to * the next * {@code finally} {@code finally} clauses that do not terminate normally + * {@code identity} use of a value-based class where an identity class is expected * {@code incubating} use of incubating modules * {@code lossy-conversions} possible lossy conversions in compound assignment * {@code missing-explicit-ctor} missing explicit constructors in public and protected classes @@ -186,7 +187,11 @@ * and interfaces * {@code static} accessing a static member using an instance * {@code strictfp} unnecessary use of the {@code strictfp} modifier - * {@code synchronization} synchronization attempts on instances of value-based classes + * {@code synchronization} synchronization attempts on instances of value-based classes; + * this key is a deprecated alias for {@code identity}, which has + * the same uses and effects. Users are encouraged to use the + * {@code identity} category for all future and existing uses of + * {@code synchronization} * {@code text-blocks} inconsistent white space characters in text block indentation * {@code this-escape} superclass constructor leaking {@code this} before subclass initialized * {@code try} issues relating to use of {@code try} blocks diff --git a/src/jdk.compiler/share/man/javac.md b/src/jdk.compiler/share/man/javac.md index f951ea0def3aa..997023487b0ae 100644 --- a/src/jdk.compiler/share/man/javac.md +++ b/src/jdk.compiler/share/man/javac.md @@ -596,6 +596,9 @@ file system locations may be directories, JAR files or JMOD files. - `finally`: Warns about `finally` clauses that do not terminate normally. + - `identity`: Warns about use of a value-based class where an identity + class is expected + - `incubating`: Warns about the use of incubating modules. - `lossy-conversions`: Warns about possible lossy conversions @@ -646,7 +649,9 @@ file system locations may be directories, JAR files or JMOD files. - `strictfp`: Warns about unnecessary use of the `strictfp` modifier. - `synchronization`: Warns about synchronization attempts on instances - of value-based classes. + of value-based classes. This key is a deprecated alias for `identity`, + which has the same uses and effects. Users are encouraged to use the + `identity` category for all future and existing uses of `synchronization`. - `text-blocks`: Warns about inconsistent white space characters in text block indentation. diff --git a/test/langtools/tools/javac/diags/CheckResourceKeys.java b/test/langtools/tools/javac/diags/CheckResourceKeys.java index 9f6f37dc23d0b..1675d99275e4b 100644 --- a/test/langtools/tools/javac/diags/CheckResourceKeys.java +++ b/test/langtools/tools/javac/diags/CheckResourceKeys.java @@ -32,6 +32,7 @@ import java.io.*; import java.util.*; import java.util.regex.*; +import java.util.stream.Stream; import javax.tools.*; import java.lang.classfile.*; import java.lang.classfile.constantpool.*; @@ -175,14 +176,7 @@ void findDeadKeys(Set codeStrings, Set resourceKeys) { //check lint description keys: if (s.startsWith("opt.Xlint.desc.")) { String option = s.substring(15); - boolean found = false; - - for (LintCategory lc : LintCategory.values()) { - if (option.equals(lc.option)) - found = true; - } - - if (found) + if (LintCategory.options().contains(option)) continue; } diff --git a/test/langtools/tools/javac/diags/examples/AttemptToSynchronizeOnInstanceOfVbc.java b/test/langtools/tools/javac/diags/examples/AttemptToSynchronizeOnInstanceOfVbc.java index 9d63a2af0a430..e677c4b3b8a07 100644 --- a/test/langtools/tools/javac/diags/examples/AttemptToSynchronizeOnInstanceOfVbc.java +++ b/test/langtools/tools/javac/diags/examples/AttemptToSynchronizeOnInstanceOfVbc.java @@ -22,7 +22,7 @@ */ // key: compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class -// options: -Xlint:synchronization +// options: -Xlint:identity class AttemptToSynchronizeOnInstanceOfVbc { void foo(Integer i) { diff --git a/test/langtools/tools/javac/diags/examples/RequiresIdentity.java b/test/langtools/tools/javac/diags/examples/RequiresIdentity.java new file mode 100644 index 0000000000000..005de46ab7c82 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/RequiresIdentity.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025, 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.attempt.to.use.value.based.where.identity.expected +// options: -Xlint:identity --add-exports java.base/jdk.internal=ALL-UNNAMED + +class RequiresIdentity<@jdk.internal.RequiresIdentity T> { + RequiresIdentity field; // should warn +} diff --git a/test/langtools/tools/javac/lint/ExternalAbuseOfVbc.java b/test/langtools/tools/javac/lint/ExternalAbuseOfVbc.java index 53de855fb0105..d68932774a276 100644 --- a/test/langtools/tools/javac/lint/ExternalAbuseOfVbc.java +++ b/test/langtools/tools/javac/lint/ExternalAbuseOfVbc.java @@ -5,8 +5,10 @@ * @compile/fail/ref=ExternalAbuseOfVbc.out -XDrawDiagnostics -Werror -Xlint ExternalAbuseOfVbc.java * @compile/fail/ref=ExternalAbuseOfVbc.out -XDrawDiagnostics -Werror -Xlint:all ExternalAbuseOfVbc.java * @compile/fail/ref=ExternalAbuseOfVbc.out -XDrawDiagnostics -Werror -Xlint:synchronization ExternalAbuseOfVbc.java + * @compile/fail/ref=ExternalAbuseOfVbc.out -XDrawDiagnostics -Werror -Xlint:identity ExternalAbuseOfVbc.java * @compile/fail/ref=ExternalAbuseOfVbc.out --release 16 -XDrawDiagnostics -Werror -Xlint:synchronization ExternalAbuseOfVbc.java - * @compile/ref=LintModeOffAbuseOfVbc.out -XDrawDiagnostics -Werror -Xlint:-synchronization ExternalAbuseOfVbc.java + * @compile/fail/ref=ExternalAbuseOfVbc.out --release 16 -XDrawDiagnostics -Werror -Xlint:identity ExternalAbuseOfVbc.java + * @compile/ref=LintModeOffAbuseOfVbc.out -XDrawDiagnostics -Werror -Xlint:-synchronization,-identity ExternalAbuseOfVbc.java */ public final class ExternalAbuseOfVbc { diff --git a/test/langtools/tools/javac/lint/ExternalAbuseOfVbc.out b/test/langtools/tools/javac/lint/ExternalAbuseOfVbc.out index b042a2c213979..d03e33e06d50c 100644 --- a/test/langtools/tools/javac/lint/ExternalAbuseOfVbc.out +++ b/test/langtools/tools/javac/lint/ExternalAbuseOfVbc.out @@ -1,4 +1,4 @@ -ExternalAbuseOfVbc.java:19:13: compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class +ExternalAbuseOfVbc.java:21:13: compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class - compiler.err.warnings.and.werror 1 error 1 warning diff --git a/test/langtools/tools/javac/lint/RequiresIdentityHelper.java b/test/langtools/tools/javac/lint/RequiresIdentityHelper.java new file mode 100644 index 0000000000000..abc50d6d446ba --- /dev/null +++ b/test/langtools/tools/javac/lint/RequiresIdentityHelper.java @@ -0,0 +1,21 @@ +/* /nodynamiccopyright/ */ + +package java.lang; + +public class RequiresIdentityHelper<@jdk.internal.RequiresIdentity T> { + public RequiresIdentityHelper() {} + public <@jdk.internal.RequiresIdentity TT> RequiresIdentityHelper(@jdk.internal.RequiresIdentity Object o) {} + + class RequiresIdentity2 { + public RequiresIdentity2() {} + public void foo(@jdk.internal.RequiresIdentity Object o) {} + public void bar(@jdk.internal.RequiresIdentity Object... o) {} + public void gg(@jdk.internal.RequiresIdentity TT ri) {} + } + + interface RequiresIdentityInt<@jdk.internal.RequiresIdentity T> {} + + interface MyIntFunction<@jdk.internal.RequiresIdentity R> { + R apply(int value); + } +} diff --git a/test/langtools/tools/javac/lint/RequiresIdentityTest.java b/test/langtools/tools/javac/lint/RequiresIdentityTest.java new file mode 100644 index 0000000000000..ddc25c8d31153 --- /dev/null +++ b/test/langtools/tools/javac/lint/RequiresIdentityTest.java @@ -0,0 +1,90 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8354556 + * @summary Expand value-based class warnings to java.lang.ref API + * @compile --patch-module java.base=${test.src} RequiresIdentityHelper.java + * @compile/fail/ref=RequiresIdentityTest.out --patch-module java.base=${test.src} -Werror -XDrawDiagnostics -Xlint:identity RequiresIdentityTest.java + * @compile/fail/ref=RequiresIdentityTest.out --patch-module java.base=${test.src} -Werror -XDrawDiagnostics -Xlint:synchronization RequiresIdentityTest.java + * @compile/ref=RequiresIdentityTest2.out --patch-module java.base=${test.src} -Werror -XDrawDiagnostics -Xlint:-identity RequiresIdentityTest.java + * @compile/ref=RequiresIdentityTest2.out --patch-module java.base=${test.src} -Werror -XDrawDiagnostics -Xlint:-synchronization RequiresIdentityTest.java + * @compile/fail/ref=RequiresIdentityTest.out --patch-module java.base=${test.src} -Werror -XDrawDiagnostics -Xlint:identity RequiresIdentityHelper.java RequiresIdentityTest.java + * @compile/fail/ref=RequiresIdentityTest.out --patch-module java.base=${test.src} -Werror -XDrawDiagnostics -Xlint:synchronization RequiresIdentityHelper.java RequiresIdentityTest.java + * @compile/ref=RequiresIdentityTest2.out --patch-module java.base=${test.src} -Werror -XDrawDiagnostics -Xlint:-identity RequiresIdentityHelper.java RequiresIdentityTest.java + * @compile/ref=RequiresIdentityTest2.out --patch-module java.base=${test.src} -Werror -XDrawDiagnostics -Xlint:-synchronization RequiresIdentityHelper.java RequiresIdentityTest.java + */ + +package java.lang; + +@SuppressWarnings("deprecation") +public class RequiresIdentityTest extends RequiresIdentityHelper // should warn + implements RequiresIdentityHelper.RequiresIdentityInt { // should warn + class Box {} + + RequiresIdentityHelper field; // should warn + RequiresIdentityHelper[] field2; // should warn + Box> field3; // should warn + Box> field4; // should warn + RequiresIdentityHelper field5 = new RequiresIdentityHelper(); // two warnings here + + public RequiresIdentityTest() {} + public RequiresIdentityTest(Integer i) { + super(i); // should warn + } + + void test(RequiresIdentity2 ri, Integer i) { // warn on the first argument due to its enclosing type: RequiresIdentityHelper + RequiresIdentityHelper localVar; // should warn + RequiresIdentityHelper[] localVar2; // should warn + // there should be warnings for the invocations below + ri.foo(i); + ri.bar(i, // warn here + i); // and here too + ri.gg(i); + } + + interface I extends RequiresIdentityHelper.RequiresIdentityInt {} // should warn + + void m(Object o) { + RequiresIdentityHelper ri = (RequiresIdentityHelper) o; // should warn + } + + RequiresIdentityHelper test() { // warn + return null; + } + + // two warns here one for the type parameter and one for the result type + > T test2() { return null; } + + class SomeClass> {} // warn + + record R(RequiresIdentityHelper c) {} // warn + record RR(R r) {} + + void m1(RequiresIdentityInt ri) { // warn here + if (ri instanceof RequiresIdentityInt rii) {} // and here + } + + void m2(RR rr) { + if (rr instanceof RR(R(RequiresIdentityHelper rii))) {} + } + + void m3() {} + void m4() { + this.>m3(); + } + + MyIntFunction field6 = Integer::new; // two warnings here + + class Run { + public <@jdk.internal.RequiresIdentity K> void run() {} + } + void m5(Runnable r) {} + void m6() { + m5(new Run()::run); + } + + void m7(Integer i, Object o) { + RequiresIdentityHelper var1 = new RequiresIdentityHelper(i); + RequiresIdentityHelper var2 = new RequiresIdentityHelper(o); + RequiresIdentityHelper var3 = new RequiresIdentityHelper(o); + } +} diff --git a/test/langtools/tools/javac/lint/RequiresIdentityTest.out b/test/langtools/tools/javac/lint/RequiresIdentityTest.out new file mode 100644 index 0000000000000..ddcd4c91354bf --- /dev/null +++ b/test/langtools/tools/javac/lint/RequiresIdentityTest.out @@ -0,0 +1,39 @@ +RequiresIdentityTest.java:19:65: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:20:88: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:23:27: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:24:36: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:25:8: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:26:8: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:27:72: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:27:27: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:31:15: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:34:32: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:35:31: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:36:40: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:38:16: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:39:16: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:40:16: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:41:15: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:44:67: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:47:63: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:50:27: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:55:6: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:55:49: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:57:21: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:59:36: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:62:32: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:63:46: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:67:54: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:72:37: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:75:37: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:75:18: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:82:32: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:86:90: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:87:52: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:88:82: compiler.warn.attempt.to.use.value.based.where.identity.expected +RequiresIdentityTest.java:88:31: compiler.warn.attempt.to.use.value.based.where.identity.expected +- compiler.err.warnings.and.werror +- compiler.note.unchecked.filename: RequiresIdentityTest.java +- compiler.note.unchecked.recompile +1 error +34 warnings diff --git a/test/langtools/tools/javac/lint/RequiresIdentityTest2.out b/test/langtools/tools/javac/lint/RequiresIdentityTest2.out new file mode 100644 index 0000000000000..55a7ccdeb8427 --- /dev/null +++ b/test/langtools/tools/javac/lint/RequiresIdentityTest2.out @@ -0,0 +1,2 @@ +- compiler.note.unchecked.filename: RequiresIdentityTest.java +- compiler.note.unchecked.recompile