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

8269121: Type inference bug with method references #5406

Closed
@@ -281,17 +281,17 @@ void validateMetafactoryArgs() throws LambdaConversionException {
for (int i=capturedStart; i<capturedArity; i++) {
Class<?> implParamType = implMethodType.parameterType(i);
Class<?> capturedParamType = factoryType.parameterType(i);
if (!capturedParamType.equals(implParamType)) {
if (!canConvert(capturedParamType, implParamType)) {
throw new LambdaConversionException(
String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s",
String.format("Type mismatch in captured lambda parameter %d: %s is not convertible to %s",
i, capturedParamType, implParamType));
}
}
// Check for adaptation match on non-receiver SAM arguments
for (int i=samStart; i<implArity; i++) {
Class<?> implParamType = implMethodType.parameterType(i);
Class<?> dynamicParamType = dynamicMethodType.parameterType(i - capturedArity);
if (!isAdaptableTo(dynamicParamType, implParamType, true)) {
if (!canConvert(dynamicParamType, implParamType)) {
throw new LambdaConversionException(
String.format("Type mismatch for lambda argument %d: %s is not convertible to %s",
i, dynamicParamType, implParamType));
@@ -301,7 +301,7 @@ void validateMetafactoryArgs() throws LambdaConversionException {
// Adaptation match: return type
Class<?> expectedType = dynamicMethodType.returnType();
Class<?> actualReturnType = implMethodType.returnType();
if (!isAdaptableToAsReturn(actualReturnType, expectedType)) {
if (!canConvert(actualReturnType, expectedType)) {
throw new LambdaConversionException(
String.format("Type mismatch for lambda return: %s is not convertible to %s",
actualReturnType, expectedType));
@@ -319,7 +319,7 @@ private void checkDescriptor(MethodType descriptor) throws LambdaConversionExcep
for (int i = 0; i < dynamicMethodType.parameterCount(); i++) {
Class<?> dynamicParamType = dynamicMethodType.parameterType(i);
Class<?> descriptorParamType = descriptor.parameterType(i);
if (!descriptorParamType.isAssignableFrom(dynamicParamType)) {
if (!canConvert(dynamicParamType, descriptorParamType)) {
String msg = String.format("Type mismatch for dynamic parameter %d: %s is not a subtype of %s",
i, dynamicParamType, descriptorParamType);
throw new LambdaConversionException(msg);
@@ -328,13 +328,65 @@ private void checkDescriptor(MethodType descriptor) throws LambdaConversionExcep

Class<?> dynamicReturnType = dynamicMethodType.returnType();
Class<?> descriptorReturnType = descriptor.returnType();
if (!isAdaptableToAsReturnStrict(dynamicReturnType, descriptorReturnType)) {
if (!canConvert(dynamicReturnType, descriptorReturnType)) {
String msg = String.format("Type mismatch for lambda expected return: %s is not convertible to %s",
dynamicReturnType, descriptorReturnType);
throw new LambdaConversionException(msg);
}
}

private static boolean canConvert(Class<?> src, Class<?> dst) {
// short-circuit a few cases:
if (src == dst || src == Object.class || dst == Object.class) return true;
// the remainder of this logic is documented in MethodHandle.asType
if (src.isPrimitive()) {
// can force void to an explicit null, a la reflect.Method.invoke
// can also force void to a primitive zero, by analogy
if (src == void.class) return true; //or !dst.isPrimitive()?
Wrapper sw = Wrapper.forPrimitiveType(src);
if (dst.isPrimitive()) {
// P->P must widen
return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw);
} else {
// P->R must box and widen
return dst.isAssignableFrom(sw.wrapperType());
}
} else if (dst.isPrimitive()) {
// any value can be dropped
if (dst == void.class) return true;
Wrapper dw = Wrapper.forPrimitiveType(dst);
// R->P must be able to unbox (from a dynamically chosen type) and widen
// For example:
// Byte/Number/Comparable/Object -> dw:Byte -> byte.
// Character/Comparable/Object -> dw:Character -> char
// Boolean/Comparable/Object -> dw:Boolean -> boolean
// This means that dw must be cast-compatible with src.
if (src.isAssignableFrom(dw.wrapperType())) {
return true;
}
// The above does not work if the source reference is strongly typed
// to a wrapper whose primitive must be widened. For example:
// Byte -> unbox:byte -> short/int/long/float/double
// Character -> unbox:char -> int/long/float/double
if (Wrapper.isWrapperType(src) &&
dw.isConvertibleFrom(Wrapper.forWrapperType(src))) {
// can unbox from src and then widen to dst
return true;
}
// We have already covered cases which arise due to runtime unboxing
// of a reference type which covers several wrapper types:
// Object -> cast:Integer -> unbox:int -> long/float/double
// Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double
// An marginal case is Number -> dw:Character -> char, which would be OK if there were a
// subclass of Number which wraps a value that can convert to char.
// Since there is none, we don't need an extra check here to cover char or boolean.
return false;
} else {
// R->R always works, since null is always valid dynamically
return true;
}
}

/**
* Check type adaptability for parameter types.
* @param fromType Type to convert from
@@ -23,10 +23,16 @@

/*
* @test
* @bug 8035776 8173587
* @bug 8035776 8173587 8269121
* @summary metafactory should fail if instantiatedMethodType does not match sam/bridge descriptors
* @modules java.base/sun.invoke.util
*/

import sun.invoke.util.Wrapper;

import java.lang.invoke.*;
import java.lang.reflect.Modifier;

import java.util.*;

public class MetafactoryDescriptorTest {
@@ -37,8 +43,6 @@ static MethodType mt(Class<?> ret, Class<?>... params) {
return MethodType.methodType(ret, params);
}

public interface I {}

public static class C {
public static void m_void(String arg) {}
public static boolean m_boolean(String arg) { return true; }
@@ -52,6 +56,10 @@ public static void m_void(String arg) {}
public static String m_String(String arg) { return ""; }
public static Integer m_Integer(String arg) { return 23; }
public static Object m_Object(String arg) { return new Object(); }
public static I m_I(String arg) { return new I() {}; }
public static J m_J(String arg) { return new J() {}; }
public static CC m_CC(String arg) { return new CC(); }
public static FF m_FF(String arg) { return new FF(); }

public static String n_boolean(boolean arg) { return ""; }
public static String n_char(char arg) { return ""; }
@@ -64,6 +72,10 @@ public static void m_void(String arg) {}
public static String n_String(String arg) { return ""; }
public static String n_Integer(Integer arg) { return ""; }
public static String n_Object(Object arg) { return ""; }
public static String n_I(I arg) { return ""; }
public static String n_J(J arg) { return ""; }
public static String n_CC(CC arg) { return ""; }
public static String n_FF(FF arg) { return ""; }

public static MethodHandle getM(Class<?> c) {
try {
@@ -89,21 +101,25 @@ public static MethodHandle getN(Class<?> c) {
public static void main(String... args) {
Class<?>[] t = { void.class, boolean.class, char.class,
byte.class, short.class, int.class, long.class, float.class, double.class,
String.class, Integer.class, Object.class };
String.class, Integer.class, Object.class,
I.class, J.class, CC.class, FF.class};

for (int i = 0; i < t.length; i++) {
MethodHandle m = C.getM(t[i]);
MethodHandle n = C.getN(t[i]); // null for void.class
for (int j = 0; j < t.length; j++) {
boolean correctRet = t[j].isAssignableFrom(t[i]) || conversions.contains(t[i], t[j]);
//if (i == j) continue;
boolean correctRet = canConvert(t[i], t[j]) || conversions.contains(t[i], t[j]);
test(correctRet, m, mt(t[i], String.class), mt(t[j], String.class));
testBridge(correctRet, m, mt(t[i], String.class), mt(t[i], String.class),
mt(t[j], Object.class));
testBridge(correctRet, m, mt(t[i], String.class), mt(t[i], String.class),
mt(t[i], CharSequence.class), mt(t[j], Object.class));

if (t[i] != void.class && t[j] != void.class) {
boolean correctParam = t[j].isAssignableFrom(t[i]);
//boolean correctParam = t[j].isAssignableFrom(t[i]) || sideCastExists(t[i], t[j]);
boolean correctParam = canConvert(t[i], t[j]);
System.out.println("testing correctParam = " + correctParam + " t[i] = " + t[i] + " t[j] = " + t[j]);
test(correctParam, n, mt(String.class, t[i]), mt(String.class, t[j]));
testBridge(correctParam, n, mt(String.class, t[i]), mt(String.class, t[i]),
mt(Object.class, t[j]));
@@ -126,6 +142,10 @@ static void testBridge(boolean correct, MethodHandle mh, MethodType instMT, Meth

static void tryMetafactory(boolean correct, MethodHandle mh, MethodType instMT, MethodType samMT) {
try {
System.err.println("invoking metafactory:" +
" impl=" + mh +
", inst=" + instMT +
", sam=" + samMT);
LambdaMetafactory.metafactory(lookup, "run", mt(I.class),
samMT, mh, instMT);
if (!correct) {
@@ -266,4 +286,63 @@ public boolean contains(Class<?> from, Class<?> to) {
conversions.put(Boolean.class, boolean.class);
}

private static boolean canConvert(Class<?> src, Class<?> dst) {
// short-circuit a few cases:
if (src == dst || src == Object.class || dst == Object.class) return true;
// the remainder of this logic is documented in MethodHandle.asType
if (src.isPrimitive()) {
// can force void to an explicit null, a la reflect.Method.invoke
// can also force void to a primitive zero, by analogy
if (src == void.class) return true; //or !dst.isPrimitive()?
Wrapper sw = Wrapper.forPrimitiveType(src);
if (dst.isPrimitive()) {
// P->P must widen
return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw);
} else {
// P->R must box and widen
return dst.isAssignableFrom(sw.wrapperType());
}
} else if (dst.isPrimitive()) {
// any value can be dropped
if (dst == void.class) return true;
Wrapper dw = Wrapper.forPrimitiveType(dst);
// R->P must be able to unbox (from a dynamically chosen type) and widen
// For example:
// Byte/Number/Comparable/Object -> dw:Byte -> byte.
// Character/Comparable/Object -> dw:Character -> char
// Boolean/Comparable/Object -> dw:Boolean -> boolean
// This means that dw must be cast-compatible with src.
if (src.isAssignableFrom(dw.wrapperType())) {
return true;
}
// The above does not work if the source reference is strongly typed
// to a wrapper whose primitive must be widened. For example:
// Byte -> unbox:byte -> short/int/long/float/double
// Character -> unbox:char -> int/long/float/double
if (Wrapper.isWrapperType(src) &&
dw.isConvertibleFrom(Wrapper.forWrapperType(src))) {
// can unbox from src and then widen to dst
return true;
}
// We have already covered cases which arise due to runtime unboxing
// of a reference type which covers several wrapper types:
// Object -> cast:Integer -> unbox:int -> long/float/double
// Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double
// An marginal case is Number -> dw:Character -> char, which would be OK if there were a
// subclass of Number which wraps a value that can convert to char.
// Since there is none, we don't need an extra check here to cover char or boolean.
return false;
} else {
// R->R always works, since null is always valid dynamically
return true;
}
}

public interface I {}

public interface J {}

public static class CC {}

public static final class FF {}
}
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2021, 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.
*/

/**
* @test
* @bug 8269121
* @summary Type inference bug with method references
*/

public class MethodReferenceIntersection5 {
interface StringLiteral {}

interface Variable {}

class MyFact {
static Object make (StringLiteral v) { return null; }
}

interface OneVariableQuery<VarType extends Variable> {
Object query(VarType var1);
}

static class Interpreter {
<VarType extends Variable> Object query(OneVariableQuery<VarType> query) { return null; }
}

public static void main(String[] args) {
new Interpreter().query(MyFact::make);
}
}