8281301: Map.of() calls with null argument should throw more contextual information.

Stuart Marks stuart.marks at oracle.com
Wed Mar 1 03:41:01 UTC 2023


Sorry, it appears that you have fallen into a thicket of issues. [*]

I'm usually fairly skeptical of these kinds of changes, as they often have more 
overhead than is apparent. Java requires all arguments to be /evaluated/ before the 
method is invoked. Evaluating a lambda includes loading and running the lambda 
metafactory, spinning bytecodes, and loading another class into the JVM. This is 
done even if the lambda is never /invoked/.

The unmodifiable collections are used a lot at startup time, so this means this 
extra work will be done on critical code paths even before main() is called. Also, 
most of this is run in interpretive mode, so we can't rely on the benefit of JIT 
compilation.

But there are other reasons to avoid using lambdas in critical startup code like 
this. Just for grins I applied your patch at ran it, and the result was this:


$ java -version
Error occurred during initialization of VM
java.lang.ExceptionInInitializerError
	at 
java.lang.invoke.MethodHandleNatives.findMethodHandleType(java.base/MethodHandleNatives.java:411)
	at java.util.ImmutableCollections$MapN.<init>(java.base/ImmutableCollections.java:1186)
	at java.util.Map.of(java.base/Map.java:1584)
	at jdk.internal.reflect.Reflection.<clinit>(java.base/Reflection.java:55)
	at java.security.AccessController.doPrivileged(java.base/AccessController.java:319)
	at java.lang.reflect.AccessibleObject.<clinit>(java.base/AccessibleObject.java:524)
Caused by: java.lang.NullPointerException
	at 
java.lang.invoke.MethodType$ConcurrentWeakInternSet.<init>(java.base/MethodType.java:1406)
	at java.lang.invoke.MethodType.<clinit>(java.base/MethodType.java:231)
	at 
java.lang.invoke.MethodHandleNatives.findMethodHandleType(java.base/MethodHandleNatives.java:411)
	at java.util.ImmutableCollections$MapN.<init>(java.base/ImmutableCollections.java:1186)
	at java.util.Map.of(java.base/Map.java:1584)
	at jdk.internal.reflect.Reflection.<clinit>(java.base/Reflection.java:55)
	at java.security.AccessController.doPrivileged(java.base/AccessController.java:319)
	at java.lang.reflect.AccessibleObject.<clinit>(java.base/AccessibleObject.java:524)


Essentially the JVM crashed even before it got to print out the version message. I 
don't know exactly what happened, but this kind of error often occurs if lambdas are 
used "too early" during initialization. The reason is that lambdas use a bunch of 
runtime infrastructure in java.lang.invoke that's written in Java. This Java code in 
turn uses a variety of collections. If collections code were to use lambdas that are 
evaluated at startup time, this would cause problems like initialization 
circularities, references to uninitialized data, or other inexplicable errors. 
That's what's happening here.

We could try to work around this by using explicit classes instead of lambdas for 
the messageSupplier, but this would still mean more classes loaded and initialized 
and bytecodes executed at startup time, which I'm fairly uncomfortable with given 
the reasons above.

s'marks

[*] Fortunately, it seems unlikely that you will be eaten by a grue. :-)


On 2/28/23 1:14 PM, Sage Vaillancourt wrote:
> Hi there. It's my first time posting to the development list, so
> please forgive any formatting errors.
> 
> My proposed change is pretty small. Basically, a pattern I see with
> some frequency is calling Objects.requireNonNull(object, "objectName")
> before (or within) Map.of(), because otherwise there's not much of a
> way to tell _which_ parameter caused the exception. Even just an index
> and key/value hint would make tracking down these types of errors
> easier without that bit of boilerplate.
> 
> I'd expect that any performance impact to be negligible, already being
> off the happy path, here.
> 
> Diff as I'm imagining it, but very open to suggestions:
> 
> diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java
> b/src/java.base/share/classes/java/util/ImmutableCollections.java
> index 3de7e1d5eae..814c1a9ec1b 100644
> --- a/src/java.base/share/classes/java/util/ImmutableCollections.java
> +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java
> @@ -1103,8 +1103,8 @@ class ImmutableCollections {
>           private final V v0;
> 
>           Map1(K k0, V v0) {
> -            this.k0 = Objects.requireNonNull(k0);
> -            this.v0 = Objects.requireNonNull(v0);
> +            this.k0 = Objects.requireNonNull(k0, () -> "Key in map
> entry was null");
> +            this.v0 = Objects.requireNonNull(v0, () -> "Value in map
> entry was null");
>           }
> 
>           @Override
> @@ -1183,9 +1183,9 @@ class ImmutableCollections {
> 
>               for (int i = 0; i < input.length; i += 2) {
>                   @SuppressWarnings("unchecked")
> -                    K k = Objects.requireNonNull((K)input[i]);
> +                    K k = Objects.requireNonNull((K)input[i], () ->
> "Key for map entry " + (i / 2) + " was null");
>                   @SuppressWarnings("unchecked")
> -                    V v = Objects.requireNonNull((V)input[i+1]);
> +                    V v = Objects.requireNonNull((V)input[i+1], () ->
> "Value for map entry " + (i / 2) + " was null");
>                   int idx = probe(k);
>                   if (idx >= 0) {
>                       throw new IllegalArgumentException("duplicate key: " + k);
> 


More information about the core-libs-dev mailing list