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

JDK-8252061 [lword] Support Object::toString for inline type #155

Closed
wants to merge 4 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
@@ -675,7 +675,7 @@
template(url_void_signature, "(Ljava/net/URL;)V") \
\
template(java_lang_invoke_ValueBootstrapMethods, "java/lang/invoke/ValueBootstrapMethods") \
template(isSubstitutable_name, "isSubstitutable0") \
template(isSubstitutable_name, "isSubstitutable") \
template(inlineObjectHashCode_name, "inlineObjectHashCode") \
\
template(jdk_internal_vm_jni_SubElementSelector, "jdk/internal/vm/jni/SubElementSelector") \
@@ -26,7 +26,9 @@
package java.lang;

import jdk.internal.HotSpotIntrinsicCandidate;
import jdk.internal.access.SharedSecrets;

import java.lang.invoke.ValueBootstrapMethods;
import java.util.Objects;

/**
@@ -227,21 +229,31 @@ public boolean equals(Object obj) {
* person to read.
* It is recommended that all subclasses override this method.
* <p>
* The {@code toString} method for class {@code Object}
* returns a string consisting of the name of the class of which the
* object is an instance, the at-sign character `{@code @}', and
* the unsigned hexadecimal representation of the hash code of the
* object. In other words, this method returns a string equal to the
* If this object is an instance of an identity class, then
* the {@code toString} method returns a string consisting of the name
* of the class of which the object is an instance, the at-sign character
* `{@code @}', and the unsigned hexadecimal representation of the hash code
* of the object. In other words, this method returns a string equal to the
* value of:
* <blockquote>
* <pre>
* getClass().getName() + '@' + Integer.toHexString(hashCode())
* </pre></blockquote>
* <p>
* If this object is an instance of an inline class, then
* the {@code toString} method returns a string which contains
* the name of the inline class, and string representations of
* all its fields. The precise format produced by this method
* is unspecified and subject to change.
*
* @return a string representation of the object.
*/
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
if (getClass().isInlineClass()) {
return SharedSecrets.getJavaLangInvokeAccess().inlineObjectToString(this);
} else {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}

/**
@@ -1887,6 +1887,11 @@ private MemoryAccessVarHandleBase checkMemoryAccessHandle(VarHandle handle) {
}
return base;
}

@Override
public String inlineObjectToString(Object o) {
return ValueBootstrapMethods.inlineObjectToString(o);
}
});
}

Large diffs are not rendered by default.

@@ -189,4 +189,6 @@ VarHandle memoryAccessVarHandle(Class<?> carrier, long alignmentMask,
* Used by {@code jdk.incubator.foreign.MemoryHandles}.
*/
VarHandle insertCoordinates(VarHandle target, int pos, Object... values);

String inlineObjectToString(Object o);
}
@@ -214,7 +214,6 @@ public static Symtab instance(Context context) {
public final Type trustMeType;
public final Type lambdaMetafactory;
public final Type stringConcatFactory;
public final Type valueBootstrapMethods;
public final Type repeatableType;
public final Type documentedType;
public final Type elementTypeType;
@@ -593,7 +592,6 @@ public <R, P> R accept(ElementVisitor<R, P> v, P p) {
nativeHeaderType = enterClass("java.lang.annotation.Native");
lambdaMetafactory = enterClass("java.lang.invoke.LambdaMetafactory");
stringConcatFactory = enterClass("java.lang.invoke.StringConcatFactory");
valueBootstrapMethods = enterClass("java.lang.invoke.ValueBootstrapMethods");
functionalInterfaceType = enterClass("java.lang.FunctionalInterface");
previewFeatureType = enterClass("jdk.internal.PreviewFeature");
previewFeatureInternalType = enterSyntheticAnnotation("jdk.internal.PreviewFeature+Annotation");
@@ -1084,9 +1084,6 @@ void finishClass(JCClassDecl tree, JCTree defaultConstructor, Env<AttrContext> e
if (isRecord) {
addRecordMembersIfNeeded(tree, env);
}
if ((tree.mods.flags & (Flags.VALUE | Flags.INTERFACE)) == Flags.VALUE && !tree.sym.type.hasTag(ERROR)) {
addValueMembers(tree, env);
}
if (tree.sym.isAnnotationType()) {
Assert.check(tree.sym.isCompleted());
tree.sym.setAnnotationTypeMetadata(new AnnotationTypeMetadata(tree.sym, annotate.annotationTypeSourceCompleter()));
@@ -1159,55 +1156,6 @@ private void addEnumMembers(JCClassDecl tree, Env<AttrContext> env) {
memberEnter.memberEnter(valueOf, env);
}

/** Add the implicit members for a value type to the parse tree and the symbol table.
*/
private void addValueMembers(JCClassDecl tree, Env<AttrContext> env) {

boolean requireToString = true;

for (JCTree def : tree.defs) {
if (def.getTag() == METHODDEF) {
JCMethodDecl methodDecl = (JCMethodDecl)def;
if (methodDecl.sym != null
&& methodDecl.sym.type != null
&& !methodDecl.sym.type.isErroneous()
&& (methodDecl.sym.flags() & STATIC) == 0) {
final List<Type> parameterTypes = methodDecl.sym.type.getParameterTypes();
if (parameterTypes.size() == 0) {
String name = methodDecl.name.toString();
if (name.equals("toString")) {
requireToString = false;
}
}
}
}
}

if (requireToString) {
make.at(tree.pos);
// Make a body comprising { throw new RuntimeException(""Internal error: This method must have been replaced by javac"); }
JCBlock body = make.Block(Flags.SYNTHETIC, List.of(make.Throw(
make.NewClass(null,
null,
make.Ident(names.fromString("RuntimeException")),
List.of(make.Literal(CLASS, "Internal error: This method must have been replaced by javac")),
null))));
// public String toString() { throw new RuntimeException(message); }
JCMethodDecl toString = make.
MethodDef(make.Modifiers(Flags.PUBLIC | Flags.FINAL),
names.toString,
make.Ident(names.fromString("String")),
List.nil(),
List.nil(),
List.nil(), // thrown
body,
null);
memberEnter.memberEnter(toString, env);
tree.defs = tree.defs.append(toString);
}

}

JCMethodDecl getCanonicalConstructorDecl(JCClassDecl tree) {
// let's check if there is a constructor with exactly the same arguments as the record components
List<Type> recordComponentErasedTypes = types.erasure(TreeInfo.recordFields(tree).map(vd -> vd.sym.type));
@@ -1079,42 +1079,6 @@ private int initCode(JCMethodDecl tree, Env<GenContext> env, boolean fatcode) {
return startpcCrt;
}

private void synthesizeValueMethod(JCMethodDecl methodDecl) {
if (!methodDecl.name.toString().equals("toString")) {
throw new AssertionError("Unexpected synthetic method body");
}

Name name = names.toString;
List<Type> argTypes = List.of(methodDecl.sym.owner.type);
Type resType = methodDecl.restype.type;

Type.MethodType indyType = new Type.MethodType(argTypes,
resType,
List.nil(),
syms.methodClass);

List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType,
syms.stringType,
syms.methodTypeType);

Symbol bsm = rs.resolveInternalMethod(methodDecl.pos(),
getAttrEnv(),
syms.valueBootstrapMethods,
names.fromString("makeBootstrapMethod"),
bsm_staticArgs,
null);

Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(name,
syms.noSymbol,
((MethodSymbol)bsm).asHandle(),
indyType,
List.nil().toArray(new LoadableConstant[0]));

code.emitop0(aload_0);
items.makeDynamicItem(dynSym).invoke();
code.emitop0(areturn);
}

public void visitVarDef(JCVariableDecl tree) {
VarSymbol v = tree.sym;
if (tree.init != null) {
@@ -1136,10 +1100,6 @@ public void visitSkip(JCSkip tree) {
}

public void visitBlock(JCBlock tree) {
if ((tree.flags & SYNTHETIC) != 0 && env.tree.hasTag(METHODDEF) && (((JCMethodDecl) env.tree).sym.owner.flags() & VALUE) != 0) {
synthesizeValueMethod((JCMethodDecl) env.tree);
return;
}
int limit = code.nextreg;
Env<GenContext> localEnv = env.dup(tree, new GenContext());
genStats(tree.stats, localEnv);
@@ -57,7 +57,16 @@ public class ObjectMethods {
.setLong(4L)
.setPoint(Point.makePoint(200, 200))
.setNumber(Value.Number.intValue(10)).build();

static final Value VALUE1 = new Value.Builder()
.setChar('z')
.setBoolean(false)
.setByte((byte)0x1)
.setShort((short)3)
.setLong(4L)
.setPoint(Point.makePoint(100, 100))
.setPointRef(Point.makePoint(200, 200))
.setReference(Point.makePoint(300, 300))
.setNumber(Value.Number.intValue(20)).build();
@DataProvider(name="equalsTests")
Object[][] equalsTests() {
return new Object[][]{
@@ -126,21 +135,18 @@ 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]]"},
{ new Value.Builder()
.setChar('z')
.setBoolean(false)
.setByte((byte)0x1)
.setShort((short)3)
.setLong(4L)
.setPoint(Point.makePoint(200, 200))
.setNumber(Value.Number.intValue(10)).build(),
{ 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=10] point_v=[Point x=200 y=200] ref_v=null]" },
"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]]" },
{ new Value.Builder()
.setReference(List.of("ref"))
.setNumber(new Value.IntNumber(99)).build(),
.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] ref_v=[ref]]" },
"float_v=0.0 number_v=99 point_v=[Point x=0 y=0] point_ref=null ref_v=[ref]]" },
// 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]" },
@@ -155,22 +161,12 @@ public void testToString(Object o, String s) {

@DataProvider(name="hashcodeTests")
Object[][] hashcodeTests() {
Value v = new Value.Builder()
.setChar('z')
.setBoolean(false)
.setByte((byte)0x1)
.setShort((short)3)
.setLong(4L)
.setFloat(1.2f)
.setDouble(0.5)
.setPoint(Point.makePoint(200, 200))
.setNumber(Value.Number.intValue(10))
.setReference(new Object()).build();
// this is sensitive to the order of the returned fields from Class::getDeclaredFields
return new Object[][]{
{ P1, hash(Point.class, 1, 2) },
{ LINE1, hash(Line.class, Point.makePoint(1, 2), Point.makePoint(3, 4)) },
{ v, hash(hashCodeComponents(v))},
{ VALUE, hash(hashCodeComponents(VALUE))},
{ VALUE1, hash(hashCodeComponents(VALUE1))},
{ Point.makePoint(0,0), hash(Point.class, 0, 0) },
{ Point.default, hash(Point.class, 0, 0) },
{ MyValue1.default, hash(MyValue1.class, Point.default, null) },
@@ -57,6 +57,10 @@ Object[][] substitutableCases() {
new Object[] { mixedValues, mixedValues},
new Object[] { valueBuilder().setPoint(p1).build(),
valueBuilder().setPoint(Point.makePoint(10, 10)).build() },
new Object[] { valueBuilder().setPointRef(p2).build(),
valueBuilder().setPointRef(Point.makePoint(20, 20)).build() },
new Object[] { valueBuilder().setReference(p2).build(),
valueBuilder().setReference(Point.makePoint(20, 20)).build() },
new Object[] { valueBuilder().setFloat(Float.NaN).setDouble(Double.NaN).setPoint(p1).build(),
valueBuilder().setFloat(Float.NaN).setDouble(Double.NaN).setPoint(l1.p1).build() },
new Object[] { valueBuilder().setFloat(Float.NaN).setDouble(Double.NaN).setNumber(number).build(),
@@ -90,6 +94,8 @@ Object[][] notSubstitutableCases() {
valueBuilder().setFloat(+0.0f).setDouble(-0.0).build() },
new Object[] { valueBuilder().setPoint(point).build(),
valueBuilder().setPoint(Point.makePoint(20, 20)).build() },
new Object[] { valueBuilder().setPointRef(point).build(),
valueBuilder().setPointRef(Point.makePoint(20, 20)).build() },
new Object[] { valueBuilder().setNumber(number).build(),
valueBuilder().setNumber(new Value.IntNumber(99)).build() },
new Object[] { valueBuilder().setNumber(Value.Number.intValue(1)).build(),
@@ -193,18 +199,18 @@ private static Object zerothElement(Object[] oa) {
return oa[0];
}

private static final Method IS_SUBSTITUTABLE0;
private static final Method IS_SUBSTITUTABLE;
static {
Method m = null;
try {
m = ValueBootstrapMethods.class.getDeclaredMethod("isSubstitutable0", Object.class, Object.class);
m = ValueBootstrapMethods.class.getDeclaredMethod("isSubstitutable", Object.class, Object.class);
m.setAccessible(true);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
IS_SUBSTITUTABLE0 = m;
IS_SUBSTITUTABLE = m;
}
private static boolean isSubstitutable(Object a, Object b) throws ReflectiveOperationException {
return (boolean) IS_SUBSTITUTABLE0.invoke(null, a, b);
return (boolean) IS_SUBSTITUTABLE.invoke(null, a, b);
}
}