Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add handling of RequiresIdentity in createAnnotation

Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,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
}

Expand Down
2 changes: 1 addition & 1 deletion src/java.base/share/classes/java/lang/ref/Cleaner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
* @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
Expand Down Expand Up @@ -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<? super T> q) {
public PhantomReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}

Expand Down
2 changes: 1 addition & 1 deletion src/java.base/share/classes/java/lang/ref/Reference.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
* @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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
* @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) {
Expand Down
6 changes: 3 additions & 3 deletions src/java.base/share/classes/java/lang/ref/SoftReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
* @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
Expand All @@ -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;
}
Expand All @@ -96,7 +96,7 @@ public SoftReference(T referent) {
* 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;
}
Expand Down
6 changes: 3 additions & 3 deletions src/java.base/share/classes/java/lang/ref/WeakReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@
* @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
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
public WeakReference(@jdk.internal.RequiresIdentity T referent) {
super(referent);
}

Expand All @@ -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<? super T> q) {
public WeakReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}

Expand Down
4 changes: 2 additions & 2 deletions src/java.base/share/classes/java/util/WeakHashMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
* @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> {

Expand Down Expand Up @@ -457,7 +457,7 @@ Entry<K,V> 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<K,V>[] tab = getTable();
Expand Down
51 changes: 51 additions & 0 deletions src/java.base/share/classes/jdk/internal/RequiresIdentity.java
Original file line number Diff line number Diff line change
@@ -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 {
}
1 change: 0 additions & 1 deletion src/java.base/share/classes/jdk/internal/ValueBased.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,3 @@
@Target(value={TYPE})
public @interface ValueBased {
}

Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,11 @@ public static EnumSet<Flag> asFlagSet(long flags) {
*/
public static final long NON_SEALED = 1L<<63; // ClassSymbols

/**
* Flag to indicate that the type should not be value based
*/
public static final long REQUIRES_IDENTITY = 1<<20; // TypeSymbols

/**
* Describe modifier flags as they might appear in source code, i.e.,
* separated by spaces and in the order suggested by JLS 8.1.1.
Expand Down Expand Up @@ -560,7 +565,9 @@ public enum Flag {
public String toString() {
return "non-sealed";
}
};
},
REQUIRES_IDENTITY(Flags.REQUIRES_IDENTITY)
;

Flag(long flag) {
this.value = flag;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ private void initializeRootIfNeeded() {
values.add(LintCategory.PREVIEW);
}
values.add(LintCategory.SYNCHRONIZATION);
values.add(LintCategory.IDENTITY);
values.add(LintCategory.INCUBATING);
}

Expand Down Expand Up @@ -368,6 +369,11 @@ public enum LintCategory {
*/
SYNCHRONIZATION("synchronization"),

/**
* Warn about uses of @ValueBased classes where an identity class is expected.
*/
IDENTITY("identity"),

/**
* Warn about issues relating to use of text blocks
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
public final Type ioType;
Expand Down Expand Up @@ -611,6 +613,8 @@ public <R, P> R accept(ElementVisitor<R, P> 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");
ioType = enterClass("java.io.IO");
Expand Down
64 changes: 64 additions & 0 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
Original file line number Diff line number Diff line change
Expand Up @@ -5331,6 +5331,70 @@ public Type constantType(LoadableConstant c) {
}
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="requiresIdentityVisitor">
public boolean isValueBased(Type t) {
return t != null && t.tsym != null && (t.tsym.flags() & VALUE_BASED) != 0;
}

public boolean needsRequiresIdentityWarning(Type t) {
RequiresIdentityVisitor requiresIdentityVisitor = new RequiresIdentityVisitor();
requiresIdentityVisitor.visit(t);
return requiresIdentityVisitor.requiresWarning;
}

// where
class RequiresIdentityVisitor extends Types.UnaryVisitor<Void> {
boolean requiresWarning = false;

@Override
public Void visitType(Type t, Void _unused) {
return null;
}

@Override
public Void visitWildcardType(WildcardType t, Void _unused) {
return visit(t.type);
}

@Override
public Void visitTypeVar(TypeVar t, Void aVoid) {
return null;
}

@Override
public Void visitArrayType(ArrayType t, Void _unused) {
return visit(t.elemtype);
}

@Override
public Void visitClassType(ClassType t, Void _unused) {
if (t != null && t.tsym != null) {
SymbolMetadata sm = t.tsym.getMetadata();
if (sm != null) {
for (Attribute.TypeCompound ta: sm.getTypeAttributes()) {
if (ta.type.tsym == syms.requiresIdentityType.tsym) {
TypeAnnotationPosition tap = ta.position;
if (tap.type == TargetType.CLASS_TYPE_PARAMETER) {
int index = tap.parameter_index;
Type tparam = t.typarams_field.get(index);
if (isValueBased(tparam)) {
requiresWarning = true;
return null;
}
}
}
}
}
}
visit(t.getEnclosingType());
for (Type targ : t.getTypeArguments()) {
visit(targ);
}
return null;
}
}
// </editor-fold>

public void newRound() {
descCache._map.clear();
isDerivedRawCache.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,12 @@ private <T extends Attribute.Compound> void annotateNow(Symbol toAnnotate,
&& types.isSameType(c.type, syms.restrictedType)) {
toAnnotate.flags_field |= Flags.RESTRICTED;
}

if (!c.type.isErroneous()
&& (toAnnotate.kind == TYP || toAnnotate.kind == VAR)
&& types.isSameType(c.type, syms.requiresIdentityType)) {
toAnnotate.flags_field |= Flags.REQUIRES_IDENTITY;
}
}

List<T> buf = List.nil();
Expand Down
Loading