diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index 6af5841403d..913e58aed72 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -333,18 +333,20 @@ public String toGenericString() { if (isAnnotation()) { sb.append('@'); } - if (isValue()) { - sb.append("value "); - } if (isInterface()) { // Note: all annotation interfaces are interfaces sb.append("interface"); } else { if (isEnum()) sb.append("enum"); - else if (isRecord()) - sb.append("record"); - else - sb.append("class"); + else { + if (isValue()) { + sb.append("value "); + } + if (isRecord()) + sb.append("record"); + else + sb.append("class"); + } } sb.append(' '); sb.append(getName()); @@ -611,36 +613,34 @@ public static Class<?> forName(Module module, String name) { } /** - * {@return {@code true} if this {@code Class} object represents an identity - * class or interface; otherwise {@code false}} + * {@return {@code true} if this {@code Class} object represents an identity class; + * otherwise {@code false}} * - * If this {@code Class} object represents an array type, then this method - * returns {@code true}. - * If this {@code Class} object represents a primitive type, or {@code void}, - * then this method returns {@code false}. + * If this {@code Class} object represents an array type then this method returns {@code true}. + * If this {@code Class} object represents an interface, a primitive type, or {@code void} + * this method returns {@code false}. * + * @see AccessFlag#IDENTITY * @since Valhalla */ @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true) public native boolean isIdentity(); /** - * {@return {@code true} if this {@code Class} object represents a value - * class; otherwise {@code false}} + * {@return {@code true} if this {@code Class} object represents a value class; + * otherwise {@code false}} + * All classes that are not {@linkplain #isIdentity identity classes} are value classes. * - * If this {@code Class} object represents an array type, an interface, - * a primitive type, or {@code void}, then this method returns {@code false}. + * If this {@code Class} object represents an array type then this method returns {@code false}. + * If this {@code Class} object represents an interface, a primitive type, or {@code void} + * this method returns {@code true}. * + * @see AccessFlag#IDENTITY * @since Valhalla */ @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true) public boolean isValue() { - if (!PreviewFeatures.isEnabled()) { - return false; - } - if (isPrimitive() || isArray() || isInterface()) - return false; - return ((getModifiers() & Modifier.IDENTITY) == 0); + return PreviewFeatures.isEnabled() ? !isIdentity() : false; } /** diff --git a/src/java.base/share/classes/java/lang/IdentityException.java b/src/java.base/share/classes/java/lang/IdentityException.java index c6a5ca0563d..e4b0fb026c1 100644 --- a/src/java.base/share/classes/java/lang/IdentityException.java +++ b/src/java.base/share/classes/java/lang/IdentityException.java @@ -30,7 +30,8 @@ * <p> * Identity objects are required for synchronization and locking. * <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">Value-based</a> - * objects do not have identity and cannot be used for synchronization or locking. + * objects do not have identity and cannot be used for synchronization, locking, + * or any type of {@link java.lang.ref.Reference}. * * @since Valhalla */ diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index e02893cd948..6fbaae32388 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -31,10 +31,17 @@ * Class {@code Object} is the root of the class hierarchy. * Every class has {@code Object} as a superclass. All objects, * including arrays, implement the methods of this class. - * <p> - * Subclasses of {@code java.lang.Object} can be either an {@linkplain Class#isIdentity identity class} - * or a {@linkplain Class#isValue value class}. - * See {@jls The Java Language Specification 8.1.1.5 Value Classes}. + * + * <div class="preview-block"> + * <div class="preview-comment"> + * When preview features are enabled, subclasses of {@code java.lang.Object} can be either + * an {@linkplain Class#isIdentity identity class} or a {@linkplain Class#isValue value class}. + * See {@jls The Java Language Specification 8.1.1.5 Value Classes}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + * </div> + * </div> * * @see java.lang.Class * @since 1.0 diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index b2de7d6993f..d7afa5344f2 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -471,6 +471,21 @@ public static native void arraycopy(Object src, int srcPos, * hashCode(). * The hash code for the null reference is zero. * + * <div class="preview-block"> + * <div class="preview-comment"> + * The "identity hash code" of a {@linkplain Class#isValue() value object} + * is computed by combining the identity hash codes of the value object's fields recursively. + * </div> + * </div> + * @apiNote + * <div class="preview-block"> + * <div class="preview-comment"> + * Note that, like ==, this hash code exposes information about a value object's + * private fields that might otherwise be hidden by an identity object. + * Developers should be cautious about storing sensitive secrets in value object fields. + * </div> + * </div> + * * @param x object for which the hashCode is to be calculated * @return the hashCode * @since 1.1 diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index a7d6217988e..282aa257aaf 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -596,7 +596,7 @@ LoadableDescriptorsAttributeBuilder add(MethodType... mtypes) { } boolean requiresLoadableDescriptors(Class<?> cls) { - return cls.isValue() && cls.accessFlags().contains(AccessFlag.FINAL); + return cls.isValue() && cls.accessFlags().contains(AccessFlag.FINAL) && !cls.isPrimitive(); } boolean isEmpty() { diff --git a/src/java.base/share/classes/java/util/IdentityHashMap.java b/src/java.base/share/classes/java/util/IdentityHashMap.java index 48a6d7b28df..5a11bada32a 100644 --- a/src/java.base/share/classes/java/util/IdentityHashMap.java +++ b/src/java.base/share/classes/java/util/IdentityHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -35,8 +35,8 @@ /** * This class implements the {@code Map} interface with a hash table, using - * reference-equality in place of object-equality when comparing keys (and - * values). In other words, in an {@code IdentityHashMap}, two keys + * `==` in place of object-equality when comparing keys (and values). + * In other words, in an {@code IdentityHashMap}, two keys * {@code k1} and {@code k2} are considered equal if and only if * {@code (k1==k2)}. (In normal {@code Map} implementations (like * {@code HashMap}) two keys {@code k1} and {@code k2} are considered equal diff --git a/src/java.base/share/classes/java/util/Objects.java b/src/java.base/share/classes/java/util/Objects.java index 7085323ce1e..f732401819a 100644 --- a/src/java.base/share/classes/java/util/Objects.java +++ b/src/java.base/share/classes/java/util/Objects.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 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 @@ -28,7 +28,6 @@ import jdk.internal.javac.PreviewFeature; import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.misc.Unsafe; import java.util.function.Supplier; @@ -180,19 +179,38 @@ public static String toIdentityString(Object o) { } /** - * {@return {@code true} if the specified object reference is an identity object, - * otherwise {@code false}} + * {@return {@code true} if the object is a non-null reference + * to an {@linkplain Class#isIdentity() identity object}, otherwise {@code false}} * - * @param obj an object - * @throws NullPointerException if {@code obj} is {@code null} + * @apiNote + * If the parameter is {@code null}, there is no object + * and hence no class to check for identity; the return is {@code false}. + * To test for a {@linkplain Class#isValue() value object} use: + * {@snippet type="java" : + * if (obj != null && !Objects.hasIdentity(obj)) { + * // obj is a non-null value object + * } + * } + * @param obj an object or {@code null} * @since Valhalla */ @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) // @IntrinsicCandidate public static boolean hasIdentity(Object obj) { - requireNonNull(obj); - return obj.getClass().isIdentity() || // Before Valhalla all classes are identity classes - obj.getClass() == Object.class; + return (obj == null) ? false : obj.getClass().isIdentity(); + } + + /** + * {@return {@code true} if the object is a non-null reference + * to an {@linkplain Class#isValue() value object}, otherwise {@code false}} + * + * @param obj an object or {@code null} + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) +// @IntrinsicCandidate + public static boolean isValueObject(Object obj) { + return (obj == null) ? false : obj.getClass().isValue(); } /** diff --git a/src/java.base/share/classes/java/util/WeakHashMap.java b/src/java.base/share/classes/java/util/WeakHashMap.java index 276e8731d84..8418a7c7d42 100644 --- a/src/java.base/share/classes/java/util/WeakHashMap.java +++ b/src/java.base/share/classes/java/util/WeakHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -25,8 +25,8 @@ package java.util; -import java.lang.ref.WeakReference; import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -122,6 +122,22 @@ * <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework"> * Java Collections Framework</a>. * + * @apiNote + * <div class="preview-block"> + * <div class="preview-comment"> + * Objects that are {@linkplain Class#isValue() value objects} do not have identity + * and can not be used as keys in a {@code WeakHashMap}. {@linkplain java.lang.ref.Reference References} + * such as {@linkplain WeakReference WeakReference} used by {@code WeakhashMap} + * to hold the key cannot refer to a value object. + * Methods such as {@linkplain #get get} or {@linkplain #containsKey containsKey} + * will always return {@code null} or {@code false} respectively. + * The methods such as {@linkplain #put put}, {@linkplain #putAll putAll}, + * {@linkplain #compute(Object, BiFunction) compute}, and + * {@linkplain #computeIfAbsent(Object, Function) computeIfAbsent} or any method putting + * a value object, as a key, throw {@link IdentityException}. + * </div> + * </div> + * * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values * @@ -288,6 +304,8 @@ static Object unmaskNull(Object key) { /** * Checks for equality of non-null reference x and possibly-null y. By * default uses Object.equals. + * The key may be a value object, but it will never be equal to the referent + * so does not need a separate Objects.hasIdentity check. */ private boolean matchesKey(Entry<K,V> e, Object key) { // check if the given entry refers to the given key without @@ -456,9 +474,11 @@ Entry<K,V> getEntry(Object key) { * {@code null} if there was no mapping for {@code key}. * (A {@code null} return can also indicate that the map * previously associated {@code null} with {@code key}.) + * @throws IdentityException if {@code key} is a value object */ public V put(K key, V value) { Object k = maskNull(key); + Objects.requireIdentity(k); int h = hash(k); Entry<K,V>[] tab = getTable(); int i = indexFor(h, tab.length); @@ -548,6 +568,7 @@ private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) { * * @param m mappings to be stored in this map. * @throws NullPointerException if the specified map is null. + * @throws IdentityException if any of the {@code keys} is a value object */ public void putAll(Map<? extends K, ? extends V> m) { int numKeysToBeAdded = m.size(); diff --git a/test/jdk/java/lang/Class/GenericStringTest.java b/test/jdk/java/lang/Class/GenericStringTest.java index 73a3ffed5ad..d0f2cd2c13d 100644 --- a/test/jdk/java/lang/Class/GenericStringTest.java +++ b/test/jdk/java/lang/Class/GenericStringTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index 1a4e5503f63..d98559fa819 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -26,7 +26,7 @@ * @bug 6207984 6272521 6192552 6269713 6197726 6260652 5073546 4137464 * 4155650 4216399 4294891 6282555 6318622 6355327 6383475 6420753 * 6431845 4802633 6570566 6570575 6570631 6570924 6691185 6691215 - * 4802647 7123424 8024709 8193128 8327858 + * 4802647 7123424 8024709 8193128 8327858 8346307 * @summary Run many tests on many Collection and Map implementations * @author Martin Buchholz * @modules java.base/java.util:open diff --git a/test/jdk/java/util/WeakHashMapValues.java b/test/jdk/java/util/WeakHashMapValues.java new file mode 100644 index 00000000000..774f3ade96b --- /dev/null +++ b/test/jdk/java/util/WeakHashMapValues.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024, 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. + */ + +import java.util.HashMap; +import java.util.WeakHashMap; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* + * @test + * @summary Check WeakHashMap throws IdentityException when Value Objects are put + * @enablePreview + * @run junit WeakHashMapValues + */ +public class WeakHashMapValues { + + /* + * Check that any kind of put with a value class as a key throws IdentityException + */ + @Test + void checkThrowsIdentityException() { + WeakHashMap<Object, Object> whm = new WeakHashMap<>(); + Object key = new Foo(1); + assertThrows(IdentityException.class, () -> whm.put(key, "1")); + assertThrows(IdentityException.class, () -> whm.putIfAbsent(key, "2")); + assertThrows(IdentityException.class, () -> whm.compute(key, (_, _) -> "3")); + assertThrows(IdentityException.class, () -> whm.computeIfAbsent(key, (_) -> "4")); + + HashMap<Object, String> hmap = new HashMap<>(); + hmap.put(key, "6"); + assertThrows(IdentityException.class, () -> whm.putAll(hmap)); + } + + /* + * Check that any kind of put with Integer as a value class as a key throws IdentityException + */ + @Test + void checkIntegerThrowsIdentityException() { + WeakHashMap<Object, Object> whm = new WeakHashMap<>(); + Object key = 1; + assertThrows(IdentityException.class, () -> whm.put(key, "1")); + assertThrows(IdentityException.class, () -> whm.putIfAbsent(key, "2")); + assertThrows(IdentityException.class, () -> whm.compute(key, (_, _) -> "3")); + assertThrows(IdentityException.class, () -> whm.computeIfAbsent(key, (_) -> "4")); + + HashMap<Object, String> hmap = new HashMap<>(); + hmap.put(key, "6"); + assertThrows(IdentityException.class, () -> whm.putAll(hmap)); + + } + + /** + * Check that queries with a value object return false or null. + */ + @Test + void checkValueObjectGet() { + WeakHashMap<Object, Object> whm = new WeakHashMap<>(); + Object key = "X"; + Object v = new Foo(1); + assertEquals(whm.get(v), null, "Get of value object should return null"); + assertEquals(whm.containsKey(v), false, "containsKey should return false"); + } +} + +value class Foo { + int x; + Foo(int x) { + this.x = x; + } +} diff --git a/test/jdk/valhalla/valuetypes/ObjectMethods.java b/test/jdk/valhalla/valuetypes/ObjectMethods.java index 90cc9c5a949..45a746bbf1e 100644 --- a/test/jdk/valhalla/valuetypes/ObjectMethods.java +++ b/test/jdk/valhalla/valuetypes/ObjectMethods.java @@ -29,9 +29,13 @@ * @run junit/othervm -Dvalue.bsm.salt=1 -XX:-UseAtomicValueFlattening ObjectMethods * @run junit/othervm -Dvalue.bsm.salt=1 -XX:-UseFieldFlattening ObjectMethods */ +import java.util.Optional; import java.util.List; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Stream; +import java.lang.reflect.AccessFlag; +import java.lang.reflect.Modifier; import jdk.internal.value.ValueClass; import jdk.internal.vm.annotation.NullRestricted; @@ -108,12 +112,13 @@ static value record ValueRecord(int i, String name) {} static final Ref R2 = new Ref(P2, null); static final Value V = new Value(P1, L1, R1, "value"); + // Instances to test, classes of each instance are tested too static Stream<Arguments> identitiesData() { + Function<String, String> lambda1 = (a) -> "xyz"; return Stream.of( - Arguments.of(new Object(), true, false), + Arguments.of(lambda1, true, false), // a lambda (Identity for now) + Arguments.of(new Object(), true, false), // java.lang.Object Arguments.of("String", true, false), - Arguments.of(String.class, true, false), - Arguments.of(Object.class, true, false), Arguments.of(L1, false, true), Arguments.of(V, false, true), Arguments.of(new ValueRecord(1, "B"), false, true), @@ -124,27 +129,54 @@ static Stream<Arguments> identitiesData() { ); } + // Classes to test + static Stream<Arguments> classesData() { + return Stream.of( + Arguments.of(int.class, false, true), // Fabricated primitive classes + Arguments.of(long.class, false, true), + Arguments.of(short.class, false, true), + Arguments.of(byte.class, false, true), + Arguments.of(float.class, false, true), + Arguments.of(double.class, false, true), + Arguments.of(char.class, false, true), + Arguments.of(void.class, false, true), + Arguments.of(String.class, true, false), + Arguments.of(Object.class, true, false), + Arguments.of(Function.class, false, true), // Interface + Arguments.of(Optional.class, false, true), // Concrete value classes... + Arguments.of(Character.class, false, true) + ); + } + @ParameterizedTest @MethodSource("identitiesData") public void identityTests(Object obj, boolean identityClass, boolean valueClass) { Class<?> clazz = obj.getClass(); + assertEquals(identityClass, Objects.hasIdentity(obj), "Objects.hasIdentity(" + obj + ")"); - if (clazz == Object.class) { - assertTrue(Objects.hasIdentity(obj), "Objects.hasIdentity()"); - } else { - assertEquals(identityClass, Objects.hasIdentity(obj), "Objects.hasIdentity()"); - } + // Run tests on the class + classTests(clazz, identityClass, valueClass); + } + + @ParameterizedTest + @MethodSource("classesData") + public void classTests(Class<?> clazz, boolean identityClass, boolean valueClass) { + assertEquals(identityClass, clazz.isIdentity(), "Class.isIdentity(): " + clazz); - assertEquals(identityClass, clazz.isIdentity(), "Class.isIdentity()"); + assertEquals(valueClass, clazz.isValue(), "Class.isValue(): " + clazz); - assertEquals(valueClass, clazz.isValue(), "Class.isValue()"); + assertEquals(clazz.accessFlags().contains(AccessFlag.IDENTITY), + identityClass, "AccessFlag.IDENTITY: " + clazz); - // JDK-8294866: Not yet implemented checks of AccessFlags for the array class -// assertEquals(clazz.accessFlags().contains(AccessFlag.IDENTITY), -// identityClass, "AccessFlag.IDENTITY"); -// -// assertEquals(clazz.accessFlags().contains(AccessFlag.VALUE), -// valueClass, "AccessFlag.VALUE"); + int modifiers = clazz.getModifiers(); + assertEquals(clazz.isIdentity(), (modifiers & Modifier.IDENTITY) != 0, "Class.getModifiers() & IDENTITY != 0"); + assertEquals(clazz.isValue(), (modifiers & Modifier.IDENTITY) == 0, "Class.getModifiers() & IDENTITY == 0"); + } + + @Test + public void identityTestNull() { + assertFalse(Objects.hasIdentity(null), "Objects.hasIdentity(null)"); + assertFalse(Objects.isValueObject(null), "Objects.isValueObject(null)"); } static Stream<Arguments> equalsTests() {