8354556: Expand value-based class warnings to java.lang.ref API

Co-authored-by: Archie Cobbs <acobbs@openjdk.org>
Reviewed-by: jlahoda
This commit is contained in:
Vicente Romero 2025-05-19 22:47:13 +00:00
parent fbeea1daef
commit 637e9d16dd
32 changed files with 610 additions and 85 deletions

View File

@ -304,13 +304,18 @@ public class CreateSymbols {
"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<String> 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<String> allClasses = data.classes.name2Class.keySet();
@ -1021,6 +1026,12 @@ public class CreateSymbols {
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 @@ public class CreateSymbols {
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
}

View File

@ -219,7 +219,7 @@ public final class Cleaner {
* @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);

View File

@ -51,7 +51,7 @@ import jdk.internal.vm.annotation.IntrinsicCandidate;
* @since 1.2
*/
public non-sealed class PhantomReference<T> extends Reference<T> {
public non-sealed class PhantomReference<@jdk.internal.RequiresIdentity T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
@ -101,7 +101,7 @@ public non-sealed class PhantomReference<T> extends Reference<T> {
* @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<? super T> q) {
public PhantomReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}

View File

@ -44,7 +44,7 @@ import jdk.internal.ref.Cleaner;
* @sealedGraph
*/
public abstract sealed class Reference<T>
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

View File

@ -46,7 +46,7 @@ import jdk.internal.vm.ContinuationSupport;
* @since 1.2
*/
public class ReferenceQueue<T> {
public class ReferenceQueue<@jdk.internal.RequiresIdentity T> {
private static class Null extends ReferenceQueue<Object> {
@Override
boolean enqueue(Reference<?> r) {

View File

@ -62,7 +62,7 @@ package java.lang.ref;
* @since 1.2
*/
public non-sealed class SoftReference<T> extends Reference<T> {
public non-sealed class SoftReference<@jdk.internal.RequiresIdentity T> extends Reference<T> {
/**
* Timestamp clock, updated by the garbage collector
@ -82,7 +82,7 @@ public non-sealed class SoftReference<T> extends Reference<T> {
*
* @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 non-sealed class SoftReference<T> extends Reference<T> {
* or {@code null} if registration is not required
*
*/
public SoftReference(T referent, ReferenceQueue<? super T> q) {
public SoftReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}

View File

@ -46,7 +46,7 @@ package java.lang.ref;
* @since 1.2
*/
public non-sealed class WeakReference<T> extends Reference<T> {
public non-sealed class WeakReference<@jdk.internal.RequiresIdentity T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
@ -54,7 +54,7 @@ public non-sealed class WeakReference<T> extends Reference<T> {
*
* @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 non-sealed class WeakReference<T> extends Reference<T> {
* @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<? super T> q) {
public WeakReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}

View File

@ -132,7 +132,7 @@ import java.util.function.Consumer;
* @see java.util.HashMap
* @see java.lang.ref.WeakReference
*/
public class WeakHashMap<K,V>
public class WeakHashMap<@jdk.internal.RequiresIdentity K,V>
extends AbstractMap<K,V>
implements Map<K,V> {
@ -457,7 +457,7 @@ public class WeakHashMap<K,V>
* (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<K,V>[] tab = getTable();

View File

@ -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 <a href="../lang/doc-files/ValueBased.html">value-based classes</a>
* 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 {
}

View File

@ -46,4 +46,3 @@ import static java.lang.annotation.ElementType.TYPE;
@Target(value={TYPE})
public @interface ValueBased {
}

View File

@ -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 class Lint {
private EnumSet<LintCategory> values;
private EnumSet<LintCategory> suppressedValues;
private static final Map<String, LintCategory> map = new ConcurrentHashMap<>(20);
private static final Map<String, LintCategory> map = new LinkedHashMap<>(40);
@SuppressWarnings("this-escape")
protected Lint(Context context) {
@ -149,10 +153,10 @@ public class Lint {
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 @@ public class Lint {
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 class Lint {
*/
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 class Lint {
*/
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 class Lint {
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<String> 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 class Lint {
return Optional.ofNullable(map.get(option));
}
/**
* Get all lint category option strings and aliases.
*/
public static Set<String> options() {
return Collections.unmodifiableSet(map.keySet());
}
public static EnumSet<LintCategory> 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<String> optionList;
/** Does this category support being suppressed by the {@code @SuppressWarnings} annotation? */
public final boolean annotationSuppression;
}
@ -496,20 +514,6 @@ public class Lint {
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<LintCategory> 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<LintCategory> suppressionsFrom(Stream<Attribute.Compound> attributes) {
initializeSymbolsIfNeeded();

View File

@ -228,6 +228,8 @@ public class Symtab {
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 class Symtab {
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

View File

@ -665,6 +665,10 @@ public abstract class Type extends AnnoConstruct implements TypeMirror, PoolCons
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?
*/

View File

@ -1047,8 +1047,10 @@ public class Attr extends JCTree.Visitor {
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 class Attr extends JCTree.Visitor {
log.error(tree, Errors.IllegalRecordComponentName(v));
}
}
chk.checkRequiresIdentity(tree, env.info.lint);
}
finally {
chk.setLint(prevLint);
@ -1949,17 +1952,12 @@ public class Attr extends JCTree.Visitor {
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 class Attr extends JCTree.Visitor {
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 @@ public class Attr extends JCTree.Visitor {
}
}
chk.checkRequiresIdentity(tree, env.info.lint);
if (cdef != null) {
visitAnonymousClassDefinition(tree, clazz, clazztype, cdef, localEnv, argtypes, typeargtypes, pkind);
return;
@ -3793,6 +3794,7 @@ public class Attr extends JCTree.Visitor {
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 class Attr extends JCTree.Visitor {
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<AttrContext> localEnv = env.dup(tree);
@ -4237,6 +4240,7 @@ public class Attr extends JCTree.Visitor {
} else {
matchBindings = new MatchBindings(List.of(v), List.nil());
}
chk.checkRequiresIdentity(tree, env.info.lint);
}
@Override
@ -5590,6 +5594,8 @@ public class Attr extends JCTree.Visitor {
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

View File

@ -28,6 +28,7 @@ package com.sun.tools.javac.comp;
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.Directive.RequiresDirective;
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 @@ public class Check {
}
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<JCExpression> 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<Void, Set<Type>> {
boolean requiresWarning = false;
@Override
public Void visitType(Type t, Set<Type> seen) {
return null;
}
@Override
public Void visitWildcardType(WildcardType t, Set<Type> seen) {
return visit(t.type, seen);
}
@Override
public Void visitTypeVar(TypeVar t, Set<Type> seen) {
if (seen.add(t)) {
visit(t.getUpperBound(), seen);
}
return null;
}
@Override
public Void visitCapturedType(CapturedType t, Set<Type> seen) {
if (seen.add(t)) {
visit(t.getUpperBound(), seen);
visit(t.getLowerBound(), seen);
}
return null;
}
@Override
public Void visitArrayType(ArrayType t, Set<Type> seen) {
return visit(t.elemtype, seen);
}
@Override
public Void visitClassType(ClassType t, Set<Type> 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<JCExpression> 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));
}
}
}

View File

@ -205,7 +205,7 @@ public class Modules extends JCTree.Visitor {
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);

View File

@ -503,8 +503,7 @@ public class Arguments {
}
} 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 class Arguments {
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) {

View File

@ -46,6 +46,7 @@ import java.util.StringJoiner;
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 enum Option {
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 @@ public enum Option {
}
};
/**
* 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 enum Option {
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 enum Option {
private static Set<String> getXLintChoices() {
Set<String> 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;
}

View File

@ -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\

View File

@ -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

View File

@ -29,6 +29,7 @@ import java.util.*;
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 class Options {
return !isSet(option, value);
}
/**
* Check whether the given lint category is explicitly enabled or disabled.
*
* <p>
* 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;

View File

@ -164,6 +164,7 @@ import javax.tools.StandardLocation;
* <tr><th scope="row">{@code fallthrough} <td>falling through from one case of a {@code switch} statement to
* the next
* <tr><th scope="row">{@code finally} <td>{@code finally} clauses that do not terminate normally
* <tr><th scope="row">{@code identity} <td>use of a value-based class where an identity class is expected
* <tr><th scope="row">{@code incubating} <td>use of incubating modules
* <tr><th scope="row">{@code lossy-conversions} <td>possible lossy conversions in compound assignment
* <tr><th scope="row">{@code missing-explicit-ctor} <td>missing explicit constructors in public and protected classes
@ -186,7 +187,11 @@ import javax.tools.StandardLocation;
* and interfaces
* <tr><th scope="row">{@code static} <td>accessing a static member using an instance
* <tr><th scope="row">{@code strictfp} <td>unnecessary use of the {@code strictfp} modifier
* <tr><th scope="row">{@code synchronization} <td>synchronization attempts on instances of value-based classes
* <tr><th scope="row">{@code synchronization} <td>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}
* <tr><th scope="row">{@code text-blocks} <td>inconsistent white space characters in text block indentation
* <tr><th scope="row">{@code this-escape} <td>superclass constructor leaking {@code this} before subclass initialized
* <tr><th scope="row">{@code try} <td>issues relating to use of {@code try} blocks

View File

@ -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.

View File

@ -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 @@ public class CheckResourceKeys {
//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;
}

View File

@ -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) {

View File

@ -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<Integer> field; // should warn
}

View File

@ -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 {

View File

@ -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

View File

@ -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<TT> {
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);
}
}

View File

@ -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<Integer> // should warn
implements RequiresIdentityHelper.RequiresIdentityInt<Integer> { // should warn
class Box<T> {}
RequiresIdentityHelper<Integer> field; // should warn
RequiresIdentityHelper<Integer>[] field2; // should warn
Box<? extends RequiresIdentityHelper<Integer>> field3; // should warn
Box<? super RequiresIdentityHelper<Integer>> field4; // should warn
RequiresIdentityHelper<Integer> field5 = new RequiresIdentityHelper<Integer>(); // two warnings here
public RequiresIdentityTest() {}
public RequiresIdentityTest(Integer i) {
super(i); // should warn
}
void test(RequiresIdentity2<Object> ri, Integer i) { // warn on the first argument due to its enclosing type: RequiresIdentityHelper<Integer>
RequiresIdentityHelper<Integer> localVar; // should warn
RequiresIdentityHelper<Integer>[] 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<Integer> {} // should warn
void m(Object o) {
RequiresIdentityHelper<?> ri = (RequiresIdentityHelper<Integer>) o; // should warn
}
RequiresIdentityHelper<Integer> test() { // warn
return null;
}
// two warns here one for the type parameter and one for the result type
<T extends RequiresIdentityHelper<Integer>> T test2() { return null; }
class SomeClass<T extends RequiresIdentityHelper<Integer>> {} // warn
record R(RequiresIdentityHelper<Integer> c) {} // warn
record RR(R r) {}
void m1(RequiresIdentityInt<Integer> ri) { // warn here
if (ri instanceof RequiresIdentityInt<Integer> rii) {} // and here
}
void m2(RR rr) {
if (rr instanceof RR(R(RequiresIdentityHelper<Integer> rii))) {}
}
<T> void m3() {}
void m4() {
this.<RequiresIdentityHelper<Integer>>m3();
}
MyIntFunction<Integer> field6 = Integer::new; // two warnings here
class Run<T> {
public <@jdk.internal.RequiresIdentity K> void run() {}
}
void m5(Runnable r) {}
void m6() {
m5(new Run<Object>()::<Integer>run);
}
void m7(Integer i, Object o) {
RequiresIdentityHelper<Object> var1 = new <Object>RequiresIdentityHelper<Object>(i);
RequiresIdentityHelper<Object> var2 = new <Integer>RequiresIdentityHelper<Object>(o);
RequiresIdentityHelper<Integer> var3 = new <Object>RequiresIdentityHelper<Integer>(o);
}
}

View File

@ -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

View File

@ -0,0 +1,2 @@
- compiler.note.unchecked.filename: RequiresIdentityTest.java
- compiler.note.unchecked.recompile