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

8268358: [lworld] toString for primitive class should return ClassName@hash #438

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -266,11 +266,7 @@ public boolean equals(Object obj) {
* @return a string representation of the object.
*/
public String toString() {
if (getClass().isPrimitiveClass()) {
return SharedSecrets.getJavaLangInvokeAccess().inlineObjectToString(this);
} else {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

/**
@@ -1483,10 +1483,6 @@ public VarHandle collectCoordinates(VarHandle target, int pos, MethodHandle filt
public VarHandle insertCoordinates(VarHandle target, int pos, Object... values) {
return VarHandles.insertCoordinates(target, pos, values);
}
@Override
public String inlineObjectToString(Object o) {
return ValueBootstrapMethods.inlineObjectToString(o);
}
});
}

@@ -95,8 +95,6 @@ private static MethodHandle generateTarget(Lookup lookup, String name, MethodTyp
return inlineTypeHashCode(valType);
case "equals":
return substitutableInvoker(valType).asType(methodType);
case "toString":
return inlineTypeToString(valType);
default:
throw new IllegalArgumentException(name + " not valid method name");
}
@@ -216,40 +214,6 @@ static MethodHandle inlineTypeHashCode(Class<?> type) {
return countedLoop(iterations, init, body);
}

static MethodHandle inlineTypeToString(Class<?> type) {
assert type.isValueType();
MethodHandle[] getters = MethodHandleBuilder.getters(type);
int length = getters.length;
StringBuilder format = new StringBuilder();
Class<?>[] parameterTypes = new Class<?>[length];
// append the value class name
format.append("[").append(type.getName());
String separator = " ";
Lookup lookup = new MethodHandles.Lookup(type.asPrimaryType());
for (int i = 0; i < length; i++) {
MethodHandle getter = getters[i];
Class<?> ftype = fieldType(getter);
MethodHandleInfo fieldInfo = lookup.revealDirect(getter);
format.append(separator)
.append(fieldInfo.getName())
.append("=\1");
getters[i]= filterReturnValue(getter, MethodHandleBuilder.toString(ftype));
parameterTypes[i] = String.class;
}
format.append("]");
try {
MethodHandle target = StringConcatFactory.makeConcatWithConstants(lookup, "toString",
methodType(String.class, parameterTypes), format.toString()).dynamicInvoker();
// apply getters
target = filterArguments(target, 0, getters);
// duplicate "this" argument from o::toString for each getter invocation
target = permuteArguments(target, methodType(String.class, type), new int[length]);
return target;
} catch (StringConcatException e) {
throw newLinkageError(e);
}

}
// ------ utility methods ------
private static boolean eq(byte a, byte b) { return a == b; }
private static boolean eq(short a, short b) { return a == b; }
@@ -301,27 +265,6 @@ private static boolean isSamePrimitiveClass(Object a, Object b) {
return a.getClass().isPrimitiveClass() && a.getClass() == b.getClass();
}

private static String toString(Object o) {
return o != null ? o.toString() : "null";
}

private static MethodHandle toString(Class<?> type) {
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
if (componentType.isPrimitiveClass()) {
componentType = componentType.asValueType();
}
if (componentType.isPrimitive()) {
int index = Wrapper.forPrimitiveType(componentType).ordinal();
return ARRAYS_TO_STRING[index];
} else {
return ARRAYS_TO_STRING[Wrapper.OBJECT.ordinal()].asType(methodType(String.class, type));
}
} else {
return TO_STRING.asType(methodType(String.class, type));
}
}

private static int hashCombiner(int accumulator, int value) {
return accumulator * 31 + value;
}
@@ -338,15 +281,12 @@ private static int computeHashCode(MethodHandle[] hashers, int v, int i, Object
}

private static final MethodHandle[] EQUALS = initEquals();
private static final MethodHandle[] ARRAYS_TO_STRING = initArraysToString();
private static final MethodHandle[] HASHCODE = initHashCode();

static final MethodHandle IS_SAME_INLINE_CLASS =
findStatic("isSamePrimitiveClass", methodType(boolean.class, Object.class, Object.class));
static final MethodHandle IS_NULL =
findStatic("isNull", methodType(boolean.class, Object.class, Object.class));
static final MethodHandle TO_STRING =
findStatic("toString", methodType(String.class, Object.class));

static final MethodHandle FALSE = constant(boolean.class, false);
static final MethodHandle TRUE = constant(boolean.class, true);
@@ -366,17 +306,6 @@ private static MethodHandle[] initEquals() {
return mhs;
}

private static MethodHandle[] initArraysToString() {
MethodHandle[] mhs = new MethodHandle[Wrapper.COUNT];
for (Wrapper wrapper : Wrapper.values()) {
if (wrapper == Wrapper.VOID) continue;

Class<?> arrayType = wrapper.arrayType();
mhs[wrapper.ordinal()] = findStatic(Arrays.class, "toString", methodType(String.class, arrayType));
}
return mhs;
}

private static MethodHandle[] initHashCode() {
MethodHandle[] mhs = new MethodHandle[Wrapper.COUNT];
for (Wrapper wrapper : Wrapper.values()) {
@@ -591,33 +520,6 @@ private static int inlineObjectHashCode(Object o) {
}
};

/**
* Invoke the bootstrap methods hashCode for the given primitive class object.
* @param o the instance to hash.
* @return the string representation of the given primitive class object.
*/
static String inlineObjectToString(Object o) {
try {
// Note: javac disallows user to call super.hashCode if user implementated
// risk for recursion for experts crafting byte-code
if (!o.getClass().isPrimitiveClass())
throw new InternalError("must be primitive type: " + o.getClass().getName());
Class<?> type = o.getClass().asValueType();
return (String) TOSTRING_METHOD_HANDLES.get(type).invoke(o);
} catch (Error|RuntimeException e) {
throw e;
} catch (Throwable e) {
if (VERBOSE) e.printStackTrace();
throw new InternalError(e);
}
}

private static ClassValue<MethodHandle> TOSTRING_METHOD_HANDLES = new ClassValue<>() {
@Override protected MethodHandle computeValue(Class<?> type) {
return MethodHandleBuilder.inlineTypeToString(type.asValueType());
}
};

private static final Comparator<MethodHandle> TYPE_SORTER = (mh1, mh2) -> {
// sort the getters with the return type
Class<?> t1 = mh1.type().returnType();
@@ -122,8 +122,6 @@ VarHandle memoryAccessVarHandle(Class<?> carrier, boolean skipAlignmentMaskCheck
*/
VarHandle insertCoordinates(VarHandle target, int pos, Object... values);

String inlineObjectToString(Object o);

/**
* Returns a native method handle with given arguments as fallback and steering info.
*
@@ -394,7 +394,7 @@ static Object test6() {

static void verifyTest6() {
Object n = test6();
Asserts.assertEQ(n.toString(), "[MyValue6 foo=124]");
Asserts.assertEQ(n.toString(), "MyValue6@" + Integer.toHexString(n.hashCode()));
}

static Object test6Box() {
@@ -403,7 +403,7 @@ static Object test6Box() {

static void verifyTest6Box() {
Object n = test6Box();
Asserts.assertEQ(n.toString(), "[MyValue6Box foo=124]");
Asserts.assertEQ(n.toString(), "MyValue6Box@" + Integer.toHexString(n.hashCode()));
}

static int test7(MyValue7[][] arr) {
@@ -54,15 +54,15 @@ public static V make(char c, long l, int i) {

public static void main(String... args) throws Throwable {
V v = __WithField(V.make('a', 5, 10).c, 'b');
if (!v.toString().equals("[WithFieldAccessorTest$V c=b l=5 i=10]")) {
if (!v.toString().equals("WithFieldAccessorTest$V@" + Integer.toHexString(v.hashCode()))) {
throw new AssertionError("Withfield of 'c' didn't work!" + v.toString());
}
v = __WithField(V.make('a', 5, 10).l, 25);
if (!v.toString().equals("[WithFieldAccessorTest$V c=a l=25 i=10]")) {
if (!v.toString().equals("WithFieldAccessorTest$V@" + Integer.toHexString(v.hashCode()))) {
throw new AssertionError("Withfield of 'l' didn't work!" + v.toString());
}
v = __WithField(V.make('a', 5, 10).i, 20);
if (!v.toString().equals("[WithFieldAccessorTest$V c=a l=5 i=20]")) {
if (!v.toString().equals("WithFieldAccessorTest$V@" + Integer.toHexString(v.hashCode()))) {
throw new AssertionError("Withfield of 'i' didn't work!" + v.toString());
}
}
@@ -26,9 +26,12 @@
* @test
* @summary test Object methods on inline types
* @run testng/othervm -Xint -Dvalue.bsm.salt=1 ObjectMethods
* @run testng/othervm -Xcomp -Dvalue.bsm.salt=1 ObjectMethods
* @run testng/othervm -Dvalue.bsm.salt=1 -XX:InlineFieldMaxFlatSize=0 ObjectMethods
*/

/* To be enabled by JDK-8267932
* @run testng/othervm -Xcomp -Dvalue.bsm.salt=1 ObjectMethods
*/
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
@@ -128,33 +131,28 @@ public void testNumber(Number n1, Number n2, boolean isSubstitutable, boolean is
assertTrue(n1.equals(n2) == isEquals);
}


@DataProvider(name="toStringTests")
Object[][] toStringTests() {
return new Object[][] {
{ Point.makePoint(100, 200), "[Point x=100 y=200]" },
{ Line.makeLine(1, 2, 3, 4), "[Line p1=[Point x=1 y=2] p2=[Point x=3 y=4]]"},
{ VALUE,
"[Value char_v=z byte_v=1 boolean_v=false int_v=0 short_v=3 long_v=4 double_v=0.0 " +
"float_v=0.0 number_v=[Value$IntValue i=10] point_v=[Point x=200 y=200] point_ref=null ref_v=null]" },
{ VALUE1,
"[Value char_v=z byte_v=1 boolean_v=false int_v=0 short_v=3 long_v=4 double_v=0.0 " +
"float_v=0.0 number_v=[Value$IntValue i=20] point_v=[Point x=100 y=100] " +
"point_ref=[Point x=200 y=200] ref_v=[Point x=300 y=300]]" },
{ Point.makePoint(100, 200) },
{ Line.makeLine(1, 2, 3, 4) },
{ VALUE },
{ VALUE1 },
{ new Value.Builder()
.setReference(List.of("ref"))
.setNumber(new Value.IntNumber(99)).build(),
"[Value char_v=\u0000 byte_v=0 boolean_v=false int_v=0 short_v=0 long_v=0 double_v=0.0 " +
"float_v=0.0 number_v=99 point_v=[Point x=0 y=0] point_ref=null ref_v=[ref]]" },
.setNumber(new Value.IntNumber(99)).build() },
// enclosing instance field `this$0` should be filtered
{ MyValue1.default, "[ObjectMethods$MyValue1 p=[Point x=0 y=0] np=null]" },
{ new MyValue1(0,0, null), "[ObjectMethods$MyValue1 p=[Point x=0 y=0] np=null]" },
{ new MyValue1(0,0, P1), "[ObjectMethods$MyValue1 p=[Point x=0 y=0] np=[Point x=1 y=2]]" },
{ MyValue1.default },
{ new MyValue1(0,0, null) },
{ new MyValue1(0,0, P1) },
};
}

@Test(dataProvider="toStringTests")
public void testToString(Object o, String s) {
assertTrue(o.toString().equals(s), o.toString());
public void testToString(Object o) {
String expected = String.format("%s@%s", o.getClass().getName(), Integer.toHexString(o.hashCode()));
assertEquals(o.toString(), expected);
}

@DataProvider(name="hashcodeTests")
@@ -175,6 +173,7 @@ Object[][] hashcodeTests() {
@Test(dataProvider="hashcodeTests")
public void testHashCode(Object o, int hash) {
assertEquals(o.hashCode(), hash);
assertEquals(System.identityHashCode(o), hash);
}

private static Object[] hashCodeComponents(Object o) {
@@ -60,11 +60,6 @@ public static void main(String... args) throws Throwable {
assertEquals(hash, value.localHashCode());
assertEquals(hash, value.hashCode());

Method toString = test.getMethod("toString", valueClass);
String s = (String)toString.invoke(null, value);
assertEquals(s, value.localToString());
assertEquals(s, value.toString());

Method equals = test.getMethod("equals", valueClass, Object.class);
boolean rc = (boolean)equals.invoke(null, value, value);
if (!rc) {
@@ -91,12 +86,6 @@ List<Object> values() {
public int localHashCode() {
return values().hashCode();
}

public String localToString() {
System.out.println(l);
return String.format("[%s i=%s d=%s s=%s l=%s]", Value.class.getName(),
i, String.valueOf(d), s, l.toString());
}
}

/*
@@ -165,21 +154,6 @@ static void generate(Class<?> c, String className, Path path) throws IOException
mv.visitMaxs(-1, -1);
mv.visitEnd();

mv = cw.visitMethod(
ACC_PUBLIC + ACC_STATIC + ACC_FINAL,
"toString",
Type.getMethodDescriptor(Type.getType(String.class), type),
null,
null);

mv.visitVarInsn(ALOAD, 0);
mv.visitInvokeDynamicInsn("toString",
Type.getMethodDescriptor(Type.getType(String.class), type),
bootstrap, type);
mv.visitInsn(ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();

cw.visitEnd();

byte[] classBytes = cw.toByteArray();
@@ -38,7 +38,7 @@ public int compareTo(String o) {
return 0;
}
};
if (!o.toString().equals("[AnonymousValueType$1 x=10]"))
if (!o.toString().equals("AnonymousValueType$1@" + Integer.toHexString(o.hashCode())))
throw new AssertionError("Broken");
}
}
@@ -47,7 +47,7 @@ public static void main(String [] args) {
InlineDiamondTest<String> idt = new InlineDiamondTest<>();
I<String> is = idt.get();
String toString = is.toString();
if (!toString.equals("[InlineDiamondTest$Y x=42]"))
if (!toString.equals("InlineDiamondTest$Y@" + Integer.toHexString(is.hashCode())))
throw new AssertionError("Expected: " + toString);
}
}
@@ -43,7 +43,7 @@ static primitive class XNodeWrapper implements I {
}
public static void main(String [] args) {
I i = foo(null);
if (!i.toString().equals("[LubWithInlines$XNodeWrapper i=42]"))
if (!i.toString().equals("LubWithInlines$XNodeWrapper@" + Integer.toHexString(i.hashCode())))
throw new AssertionError("Unexpected: " + i);
}
}
@@ -55,8 +55,7 @@ public int localHashCode() {

public String localToString() {
System.out.println(l);
return String.format("[%s i=%s d=%s s=%s l=%s]", Value.class.getName(),
i, String.valueOf(d), s, l.toString());
return String.format("%s@%s", Value.class.getName(), Integer.toHexString(localHashCode()));
}

@Override