dimanche 5 juin 2022

Is the built-in LambdaMetafactory parameter type checking correct?

For example, when I execute the following:

public static int addOne(Number base) {
  return base.intValue() + 1;
}

public static interface AddOneLambda {
  public int addOne(Integer base);
}

public static void main(String[] a) throws Throwable {
  Method lambdaMethod = AddOneLambda.class.getMethod("addOne", Integer.class);
  Class<?>[] lambdaParameters = Stream.of(lambdaMethod.getParameters()).map(p -> p.getType()).toArray(Class[]::new);

  Method sourceMethod = Main.class.getMethod("addOne", Number.class);
  Class<?>[] sourceParameters = Stream.of(sourceMethod.getParameters()).map(p -> p.getType()).toArray(Class[]::new);

  MethodHandles.Lookup lookup = MethodHandles.lookup();
  CallSite site = LambdaMetafactory.metafactory(lookup, //
      lambdaMethod.getName(), //
      MethodType.methodType(lambdaMethod.getDeclaringClass()), //
      MethodType.methodType(lambdaMethod.getReturnType(), lambdaParameters), //
      lookup.unreflect(sourceMethod), //
      MethodType.methodType(sourceMethod.getReturnType(), sourceParameters));

  AddOneLambda addOneLambda = (AddOneLambda) site.getTarget().invoke();
  System.out.println("1 + 1 = " + addOneLambda.addOne(1));
}

I receive the following exception from metafactory:

LambdaConversionException: Type mismatch for dynamic parameter 0: class java.lang.Number is not a subtype of class java.lang.Integer

I don't understand this. Passing an Integer to the AddOneLambda should always be fine, because the underlying addOne method can accept Integers as part of it's Number signature - so I believe this configuration should be "safe".

On the other hand, when I execute the above with this change:

public static int addOne(Integer base) {
  return base.intValue() + 1;
}

public interface AddOneLambda {
  public int addOne(Number base);
}

The metafactory allows now this without exception, but it doesn't seem right. I can pass any kind of Number to AddOneLambda, even though the underlying method can only handle Integers - so I believe this configuration to be "unsafe". Indeed, if I now call addOneLambda.addOne(1.5) I receive an exception for inability to cast Double to Integer.

Why then is my initial code not allowed, while the change which ultimately allows for invalid types to be passed ok? Is it something to do with the values I'm passing to metafactory, is metafactory incorrectly checking the types, or does this prevent some other kind of situation I haven't considered? If relevant, I'm using JDK 17.0.3.





Aucun commentaire:

Enregistrer un commentaire