Repository: raphw/byte-buddy
Issue: The type assignability check in JavaConstant.Dynamic.ofInvocation() is too strict

Hi; it's me again; sorry!

For background: https://stackoverflow.com/questions/64360249/is-type-assignability-too-strict-in-javaconstant-dynamic-ofinvocation

In very concrete terms, I think that this assignability check:

https://github.com/raphw/byte-buddy/blob/e4ce4af1bab578a7ac3497d07449e50628d050e7/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java#L1636-L1638

…is too strict given that it prevents a method from being invoked that uses varargs.  Specifically, `iterator.next()` will, in such a case, return something like `Glorp[]`, and (let's say) `Glorp` is not assignable to `Glorp[]`.

Suppose I have:
```
static final JavaConstant toJavaConstant(final Glorp[] glorps) {
  final JavaConstant[] javaConstants = new JavaConstant[glorps.length];
  for (int i = 0; i < javaConstants.length; i++) {
    javaConstants[i] = toJavaConstant(glorps[i]); // another version of this method that works on scalars
  }
  return JavaConstant.Dynamic.ofInvocation(SOME_METHOD_THAT_ACCEPTS_A_VARARGS_OF_THINGS, javaConstants);
}
```
…and `SOME_METHOD_THAT_ACCEPTS_A_VARARGS_OF_THINGS` is a `MethodDescription.InDefinedShape` that resolves to something like this:
```
public static final Glorp[] toGlorps(final Glorp... glorps) {
  return glorps;
}
```
Currently, ByteBuddy will not allow this because of that type assignability check.

If I comment out lines 1636 through 1638, i.e. if I disable the type assignability check, this construction works just fine (probably thanks to all the `MethodHandle` argument conversion machinery happening at constant resolution time).

I'll file a PR shortly.

Changed files:
byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantDynamicTest.java, byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java

diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java
index 51ad4443c8..b4b1b108ca 100644
--- a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java
@@ -1602,7 +1602,7 @@ public interface JavaConstant {
         public static Dynamic ofInvocation(MethodDescription.InDefinedShape methodDescription, List<?> constants) {
             if (!methodDescription.isConstructor() && methodDescription.getReturnType().represents(void.class)) {
                 throw new IllegalArgumentException("Bootstrap method is no constructor or non-void static factory: " + methodDescription);
-            } else if (methodDescription.getParameters().size() + (methodDescription.isStatic() || methodDescription.isConstructor() ? 0 : 1) != constants.size()) {
+            } else if (!methodDescription.isVarArgs() && methodDescription.getParameters().size() + (methodDescription.isStatic() || methodDescription.isConstructor() ? 0 : 1) != constants.size()) {
                 throw new IllegalArgumentException("Cannot assign " + constants + " to " + methodDescription);
             }
             List<Object> arguments = new ArrayList<Object>(constants.size());
@@ -1633,8 +1633,13 @@ public interface JavaConstant {
                         throw new IllegalArgumentException("Not a compile-time constant: " + constant);
                     }
                 }
-                if (!typeDescription.isAssignableTo(iterator.next())) {
-                    throw new IllegalArgumentException("Cannot assign " + constants + " to " + methodDescription);
+                if (iterator.hasNext()) {
+                    TypeDescription next = iterator.next();
+                    if (!typeDescription.isAssignableTo(next)) {
+                        if (!methodDescription.isVarArgs() || iterator.hasNext() || !next.isArray() || !typeDescription.isAssignableTo(next.getComponentType())) {
+                            throw new IllegalArgumentException("Cannot assign " + constants + " to " + methodDescription);
+                        }
+                    }
                 }
             }
             return new Dynamic(new ConstantDynamic("invoke",
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantDynamicTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantDynamicTest.java
index f95547b3e1..faaa07f74a 100644
--- a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantDynamicTest.java
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantDynamicTest.java
@@ -1,5 +1,7 @@
 package net.bytebuddy.utility;
 
+import java.lang.reflect.Method;
+
 import net.bytebuddy.ByteBuddy;
 import net.bytebuddy.description.type.TypeDescription;
 import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@@ -478,6 +480,24 @@ public class JavaConstantDynamicTest {
         assertThat(baz.getDeclaredMethod(FOO).invoke(foo), sameInstance(baz.getDeclaredMethod(FOO).invoke(foo)));
     }
 
+    @Test
+    @JavaVersionRule.Enforce(11)
+    public void testInvocationOfVarargsMethod() throws Exception {
+        final Integer[] sourceIntegers = new Integer[] { Integer.valueOf(1), Integer.valueOf(2) };
+        Class<? extends Foo> baz = new ByteBuddy()
+                .subclass(Foo.class)
+                .method(isDeclaredBy(Foo.class))
+            .intercept(FixedValue.value(toJavaConstant(sourceIntegers)))
+                .make()
+                .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+                .getLoaded();
+        assertThat(baz.getDeclaredFields().length, is(0));
+        assertThat(baz.getDeclaredMethods().length, is(1));
+        Foo foo = baz.getDeclaredConstructor().newInstance();
+        assertThat((Integer[])baz.getDeclaredMethod(FOO).invoke(foo), equalTo(sourceIntegers));
+        assertThat(baz.getDeclaredMethod(FOO).invoke(foo), sameInstance(baz.getDeclaredMethod(FOO).invoke(foo)));
+    }
+
     @Test
     @JavaVersionRule.Enforce(11)
     public void testStaticFieldVarHandle() throws Exception {
@@ -649,6 +669,7 @@ public class JavaConstantDynamicTest {
         public Object bar() {
             return null;
         }
+
     }
 
     private static Object methodHandle() throws Exception {
@@ -660,4 +681,24 @@ public class JavaConstantDynamicTest {
     private static Object methodType() throws Exception {
         return Class.forName("java.lang.invoke.MethodType").getMethod("methodType", Class.class).invoke(null, void.class);
     }
+
+    private static JavaConstant toJavaConstant(Integer[] integers) throws Exception {
+        // This is convoluted and exists only to test issue #954.
+        JavaConstant[] constants = new JavaConstant[integers.length];
+        for (int i = 0; i < integers.length; i++) {
+            constants[i] = toJavaConstant(integers[i]);
+        }
+        return JavaConstant.Dynamic.ofInvocation(JavaConstantDynamicTest.class.getDeclaredMethod("toIntegers", Integer[].class), constants);
+    }
+
+    private static JavaConstant toJavaConstant(Integer i) throws Exception {
+        // This is convoluted and exists only to test issue #954.
+        return JavaConstant.Dynamic.ofInvocation(Integer.class.getMethod("valueOf", String.class), i.toString());
+    }
+
+    public static Integer[] toIntegers(final Integer... varargs) {
+        // This is convoluted and exists only to test issue #954.
+        return varargs;
+    }
+
 }