From 339bc421a9f4dcc56896c5180b70494cadac50d9 Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Tue, 17 Dec 2024 09:58:40 -0500 Subject: [PATCH 1/2] 8346307: [lworld] Document WeakHashMap and other apis that may throw IdentityException Add APINote and javadoc for IdentityException where it will be useful to know. Simplified WeakHashMap javadoc updates for IdentityException. Added note to System.identityHashCode to include value objects. --- .../classes/java/lang/IdentityException.java | 3 +- .../share/classes/java/lang/System.java | 17 +++- .../classes/java/util/IdentityHashMap.java | 6 +- .../share/classes/java/util/WeakHashMap.java | 28 ++++++- test/jdk/java/util/Collection/MOAT.java | 5 +- test/jdk/java/util/WeakHashMapValues.java | 80 +++++++++++++++++++ 6 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 test/jdk/java/util/WeakHashMapValues.java diff --git a/src/java.base/share/classes/java/lang/IdentityException.java b/src/java.base/share/classes/java/lang/IdentityException.java index c6a5ca0563d..848b964ed2a 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 @@ *

* Identity objects are required for synchronization and locking. * Value-based - * 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 for any type of {@link java.lang.ref.Reference}. * * @since Valhalla */ diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 59dc488d645..c180176192e 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -673,6 +673,21 @@ public static native void arraycopy(Object src, int srcPos, * hashCode(). * The hash code for the null reference is zero. * + *

+ *
+ * The "identity hash code" of a {@linkplain Class#isValue() value object} + * is computed by combining the hash codes of the value object's fields recursively. + *
+ *
+ * @apiNote + *
+ *
+ * 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. + *
+ *
+ * * @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/util/IdentityHashMap.java b/src/java.base/share/classes/java/util/IdentityHashMap.java index 5c8dc463b31..f248af50dcb 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, 2022, 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,7 +35,9 @@ /** * This class implements the {@code Map} interface with a hash table, using - * reference-equality in place of object-equality when comparing keys (and + * reference-equality for {@linkplain Class#isIdentity() identity objects} and + * substutitability for {@linkplain Class#isValue value objects} + * 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 diff --git a/src/java.base/share/classes/java/util/WeakHashMap.java b/src/java.base/share/classes/java/util/WeakHashMap.java index fbad7ced0f3..11f2139e6dc 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, 2022, 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,11 +25,13 @@ package java.util; -import java.lang.ref.WeakReference; +import java.lang.ref.Reference; 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; +import java.util.function.Function; /** @@ -122,6 +124,22 @@ * * Java Collections Framework. * + * @apiNote + *
+ *
+ * Keys that are {@linkplain Class#isValue() value objects} do not have identity + * and can not be used as keys in a {@code WeakHashMap}. {@linkplain 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}. + *
+ *
+ * * @param the type of keys maintained by this map * @param the type of mapped values * @@ -290,6 +308,9 @@ static Object unmaskNull(Object key) { * default uses Object.equals. */ private boolean matchesKey(Entry e, Object key) { + // only identity objects can be compared to a reference + if (!Objects.hasIdentity(key)) + return false; // check if the given entry refers to the given key without // keeping a strong reference to the entry's referent if (e.refersTo(key)) return true; @@ -456,9 +477,11 @@ Entry 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 the {@code key} is a value object */ public V put(K key, V value) { Object k = maskNull(key); + Objects.hasIdentity(k); int h = hash(k); Entry[] tab = getTable(); int i = indexFor(h, tab.length); @@ -548,6 +571,7 @@ private void transfer(Entry[] src, Entry[] 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 m) { int numKeysToBeAdded = m.size(); diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index 0e5eadc9799..cee1d373aa6 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, 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,11 +26,12 @@ * @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 + * 4802647 7123424 8024709 8193128 8346307 * @summary Run many tests on many Collection and Map implementations * @author Martin Buchholz * @modules java.base/java.util:open * @run main MOAT + * @run main/othervm --enable-preview MOAT * @key randomness */ diff --git a/test/jdk/java/util/WeakHashMapValues.java b/test/jdk/java/util/WeakHashMapValues.java new file mode 100644 index 00000000000..61b69fa6b53 --- /dev/null +++ b/test/jdk/java/util/WeakHashMapValues.java @@ -0,0 +1,80 @@ +/* + * 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; + +/* + * @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 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 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 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 hmap = new HashMap<>(); + hmap.put(key, "6"); + assertThrows(IdentityException.class, () -> whm.putAll(hmap)); + + } +} + +value class Foo { + int x; + Foo(int x) { + this.x = x; + } +} From 68dd3fb675d0f87da8de2bc7525d5bcc2a87dac1 Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Tue, 17 Dec 2024 09:58:40 -0500 Subject: [PATCH 2/2] 8346307: [lworld] Document WeakHashMap and other apis that may throw IdentityException Add APINote and javadoc for IdentityException where it will be useful to know. Updated Objects.hasIdentity to return true for non-null reference to identity class; else false Added Objects.isValueObject to return true for non-null reference to value class; else false Updated tests for value objects. --- .../share/classes/java/lang/Class.java | 30 +++--- .../share/classes/java/lang/System.java | 17 +++- .../invoke/InnerClassLambdaMetafactory.java | 4 +- .../classes/java/util/IdentityHashMap.java | 6 +- .../share/classes/java/util/Objects.java | 36 +++++-- .../share/classes/java/util/WeakHashMap.java | 27 +++++- .../java/lang/Class/GenericStringTest.java | 14 +-- test/jdk/java/util/Collection/MOAT.java | 5 +- test/jdk/java/util/WeakHashMapValues.java | 93 +++++++++++++++++++ .../valhalla/valuetypes/ObjectMethods.java | 66 +++++++++---- 10 files changed, 239 insertions(+), 59 deletions(-) create mode 100644 test/jdk/java/util/WeakHashMapValues.java diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index e305a8a6c40..bd49227d4b0 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -683,36 +683,34 @@ private static Class forName(Module module, String name, Class caller) { } /** - * {@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}, + * 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} * then 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}, + * then 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/System.java b/src/java.base/share/classes/java/lang/System.java index 59dc488d645..c180176192e 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -673,6 +673,21 @@ public static native void arraycopy(Object src, int srcPos, * hashCode(). * The hash code for the null reference is zero. * + *
+ *
+ * The "identity hash code" of a {@linkplain Class#isValue() value object} + * is computed by combining the hash codes of the value object's fields recursively. + *
+ *
+ * @apiNote + *
+ *
+ * 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. + *
+ *
+ * * @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 c37dbebdef4..b0623680542 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 5c8dc463b31..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, 2022, 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 fbad7ced0f3..e45609617ee 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, 2022, 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,11 +25,13 @@ package java.util; -import java.lang.ref.WeakReference; +import java.lang.ref.Reference; 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; +import java.util.function.Function; /** @@ -122,6 +124,22 @@ * * Java Collections Framework. * + * @apiNote + *
+ *
+ * Objects that are {@linkplain Class#isValue() value objects} do not have identity + * and can not be used as keys in a {@code WeakHashMap}. {@linkplain 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}. + *
+ *
+ * * @param the type of keys maintained by this map * @param the type of mapped values * @@ -288,6 +306,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 e, Object key) { // check if the given entry refers to the given key without @@ -456,9 +476,11 @@ Entry 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[] tab = getTable(); int i = indexFor(h, tab.length); @@ -548,6 +570,7 @@ private void transfer(Entry[] src, Entry[] 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 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..9b0f744944d 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 @@ -57,7 +57,7 @@ public static void main(String... args) throws ReflectiveOperationException { new PlatformTestCase(java.lang.Enum.class, "public abstract class java.lang.Enum>"), new PlatformTestCase(java.util.Map.class, - "public abstract interface java.util.Map"), + "public abstract value interface java.util.Map"), new PlatformTestCase(java.util.EnumMap.class, "public class java.util.EnumMap,V>"), new PlatformTestCase(java.util.EventListenerProxy.class, @@ -141,10 +141,10 @@ private static int checkToGenericString(Class clazz, String expected) { String value(); } -@ExpectedGenericString("abstract interface AnInterface") +@ExpectedGenericString("abstract value interface AnInterface") strictfp interface AnInterface {} -@ExpectedGenericString("abstract interface LocalMap") +@ExpectedGenericString("abstract value interface LocalMap") interface LocalMap {} @ExpectedGenericString("final enum AnEnum") @@ -202,7 +202,7 @@ non-sealed class GreatGrandChildACCB extends GrandChildACC {} } // Test cases for sealed/non-sealed _interface_ hierarchy. -@ExpectedGenericString("abstract sealed interface SealedRootIntf") +@ExpectedGenericString("abstract sealed value interface SealedRootIntf") sealed interface SealedRootIntf permits SealedRootIntf.ChildA, @@ -241,13 +241,13 @@ non-sealed class GreatGrandChildACCB extends GrandChildACC {} } } - @ExpectedGenericString("public abstract static sealed interface SealedRootIntf$IntfA") + @ExpectedGenericString("public abstract static sealed value interface SealedRootIntf$IntfA") sealed interface IntfA extends SealedRootIntf { @ExpectedGenericString("public static non-sealed class SealedRootIntf$IntfA$IntfAImpl") non-sealed class IntfAImpl implements IntfA {} } - @ExpectedGenericString("public abstract static non-sealed interface SealedRootIntf$IntfB") + @ExpectedGenericString("public abstract static non-sealed value interface SealedRootIntf$IntfB") non-sealed interface IntfB extends SealedRootIntf { // Check that non-sealing can be allowed with a second superinterface being sealed. @ExpectedGenericString("public static non-sealed class SealedRootIntf$IntfB$IntfAImpl") diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index 0e5eadc9799..cee1d373aa6 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, 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,11 +26,12 @@ * @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 + * 4802647 7123424 8024709 8193128 8346307 * @summary Run many tests on many Collection and Map implementations * @author Martin Buchholz * @modules java.base/java.util:open * @run main MOAT + * @run main/othervm --enable-preview MOAT * @key randomness */ 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 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 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 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 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 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 9e3fbb901a9..7ad13b76944 100644 --- a/test/jdk/valhalla/valuetypes/ObjectMethods.java +++ b/test/jdk/valhalla/valuetypes/ObjectMethods.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -29,9 +29,13 @@ * @run junit/othervm -Dvalue.bsm.salt=1 ObjectMethods * @run junit/othervm -Dvalue.bsm.salt=1 -XX:InlineFieldMaxFlatSize=0 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.ImplicitlyConstructible; @@ -112,12 +116,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 identitiesData() { + Function 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), @@ -128,27 +133,54 @@ static Stream identitiesData() { ); } + // Classes to test + static Stream 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 equalsTests() {