Skip to content
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

Improve Invoker exception messages #3133

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

Azquelt
Copy link
Contributor

@Azquelt Azquelt commented Mar 5, 2025

Some of the exception messages you can get from calling an Invoker with the wrong arguments are a bit obtuse and I wanted to look at providing a bit more information.

To do this, I'm doing extra checks before calling the method to ensure that:

  • Enough arguments have been passed in
  • Non-looked-up arguments have the correct type
  • Non-looked-up primitive arguments are not null
  • Non-looked-up instances have the correct type
  • The instance is not null if the method is non-static

Most of these checks have to be done before the argument array is spread to the actual arguments, since the MethodHandles implementation will throw an exception there if things are the wrong types.

If there are transformers, type checking is done against the input type of the transformer.

The null check for the instance is done after the transformers run because an instance transformer can convert a null to a real instance.

The null check for primitive arguments, however, has to be done before the argument array is spread, since the spread operation will fail if the arguments cannot be converted to the primitive type successfully. I think this is safe because we separately check that the types for an argument transformer match the method parameters, and if that's a primitive type then the argument transformer isn't able to return null there.

Performance

I'm doing all of these checks before method invocation. Running an invoke call to InvokableBean.ping 10 million times took 5.2 seconds before these changes and 5.7 seconds afterwards, so there is a performance impact there which we may want to look into further.

One other thought I had was that it might be possible to catch NullPointerException and ClassCastException and only do these checks in the case where one of those exceptions was thrown. If we find that there's a problem with the instance or arguments at that point, we can replace the exception thrown by the MethodHandles implementation. If all our checks pass we'd rethrow the original exception.

Examples

Not enough arguments:

Before:

java.lang.IllegalArgumentException: array is not of length 1
	at java.base/java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:167)
	at java.base/java.lang.invoke.MethodHandleImpl.checkSpreadArgument(MethodHandleImpl.java:590)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

After:

org.jboss.weld.exceptions.IllegalArgumentException: WELD-002017: Cannot invoke public java.lang.String io.openliberty.cdi41.internal.fat.invokers.app.InvokedBean.basicMethod(int) because 1 arguments were expected but only 0 were provided
	at org.jboss.weld.invokable.InvokerValidationUtils.argCountAtLeast(InvokerValidationUtils.java:67)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

Arguments array is null when arguments are required

Before:

java.lang.NullPointerException: null array reference
	at java.base/java.lang.invoke.MethodHandleImpl.checkSpreadArgument(MethodHandleImpl.java:581)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

After:

org.jboss.weld.exceptions.IllegalArgumentException: WELD-002017: Cannot invoke public java.lang.String io.openliberty.cdi41.internal.fat.invokers.app.InvokedBean.basicMethod(int) because 1 arguments were expected but only 0 were provided
	at org.jboss.weld.invokable.InvokerValidationUtils.argCountAtLeast(InvokerValidationUtils.java:67)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

^I wasn't sure whether this one should still be a NullPointerException or whether we should treat it the same as when the user doesn't pass enough arguments.

Primitive argument is null:

Before:

java.lang.NullPointerException: Cannot invoke "java.lang.Number.intValue()" because the return value of "sun.invoke.util.ValueConversions.primitiveConversion(sun.invoke.util.Wrapper, java.lang.Object, boolean)" is null
	at java.base/sun.invoke.util.ValueConversions.unboxInteger(ValueConversions.java:81)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

After:

java.lang.NullPointerException: WELD-002019: Cannot invoke public java.lang.String io.openliberty.cdi41.internal.fat.invokers.app.InvokedBean.basicMethod(int) because parameter 1 is a primitive type but the argument is null
	at org.jboss.weld.invokable.InvokerValidationUtils.argumentsHaveCorrectType(InvokerValidationUtils.java:99)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

Primitive argument has the wrong type:

Before:

java.lang.ClassCastException: java.lang.Object incompatible with java.lang.Number
	at java.base/sun.invoke.util.ValueConversions.primitiveConversion(ValueConversions.java:247)
	at java.base/sun.invoke.util.ValueConversions.unboxInteger(ValueConversions.java:81)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

After:

java.lang.ClassCastException: WELD-002018: Cannot invoke public java.lang.String io.openliberty.cdi41.internal.fat.invokers.app.InvokedBean.basicMethod(int) because argument 1 has type class java.lang.Object which cannot be cast to int
	at org.jboss.weld.invokable.InvokerValidationUtils.argumentsHaveCorrectType(InvokerValidationUtils.java:102)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

Instance has the wrong type:

Before:

java.lang.ClassCastException: Cannot cast java.lang.Object to io.openliberty.cdi41.internal.fat.invokers.app.InvokedBean
	at java.base/java.lang.Class.cast(Class.java:3264)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

After:

java.lang.ClassCastException: WELD-002016: Cannot invoke public java.lang.String io.openliberty.cdi41.internal.fat.invokers.app.InvokedBean.basicMethod(int) because the instance passed to the Invoker has type class java.lang.Object which cannot be cast to class io.openliberty.cdi41.internal.fat.invokers.app.InvokedBean
	at org.jboss.weld.invokable.InvokerValidationUtils.instanceHasType(InvokerValidationUtils.java:35)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

Null instance for a non-static method:

Before:

java.lang.NullPointerException: Cannot invoke "java.lang.invoke.MethodHandle.invoke(java.lang.Object, java.lang.Object[])"
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

After:

java.lang.NullPointerException: WELD-002015: Cannot invoke public java.lang.String io.openliberty.cdi41.internal.fat.invokers.app.InvokedBean.basicMethod(int) because the instance passed to the Invoker was null
	at org.jboss.weld.invokable.InvokerValidationUtils.instanceNotNull(InvokerValidationUtils.java:50)
	at org.jboss.weld.invokable.InvokerImpl.invoke(InvokerImpl.java:18)

Azquelt added 2 commits March 5, 2025 11:46
Do additional type checking before the invocation so that we can give a
better exception message if the invocation will fail with a
ClassCastException or NullPointerException.
@Azquelt Azquelt requested a review from manovotn as a code owner March 5, 2025 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant