Skip to content

Commit 355e935

Browse files
authored
Bytecode: Avoid casts to supertype (#205)
1 parent a784414 commit 355e935

File tree

11 files changed

+312
-93
lines changed

11 files changed

+312
-93
lines changed

sourcegen-bytecode-writer/src/main/java/io/micronaut/sourcegen/bytecode/expression/CastExpressionWriter.java

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
import io.micronaut.inject.ast.ClassElement;
1919
import io.micronaut.sourcegen.bytecode.MethodContext;
2020
import io.micronaut.sourcegen.bytecode.TypeUtils;
21+
import io.micronaut.sourcegen.model.ClassDef;
2122
import io.micronaut.sourcegen.model.ClassTypeDef;
23+
import io.micronaut.sourcegen.model.EnumDef;
2224
import io.micronaut.sourcegen.model.ExpressionDef;
2325
import io.micronaut.sourcegen.model.ObjectDef;
26+
import io.micronaut.sourcegen.model.RecordDef;
2427
import io.micronaut.sourcegen.model.TypeDef;
2528
import org.objectweb.asm.commons.GeneratorAdapter;
2629

@@ -61,34 +64,71 @@ private static void cast(GeneratorAdapter generatorAdapter, MethodContext contex
6164
if (!from.isPrimitive() && to.isPrimitive()) {
6265
unbox(generatorAdapter, context, to);
6366
}
64-
} else if (!from.makeNullable().equals(to.makeNullable())) {
65-
if (from instanceof ClassTypeDef.ClassElementType fromElement) {
66-
ClassElement fromClassElement = fromElement.classElement();
67-
if (to instanceof ClassTypeDef.ClassElementType toElement) {
68-
if (!fromClassElement.isAssignable(toElement.classElement())) {
69-
checkCast(generatorAdapter, context, from, to);
70-
}
71-
} else if (to instanceof ClassTypeDef.JavaClass toClass) {
72-
if (!fromClassElement.isAssignable(toClass.type())) {
73-
checkCast(generatorAdapter, context, from, to);
74-
}
75-
} else if (to instanceof ClassTypeDef.ClassName toClassName) {
76-
if (!fromClassElement.isAssignable(toClassName.className())) {
77-
checkCast(generatorAdapter, context, from, to);
78-
}
79-
} else {
80-
checkCast(generatorAdapter, context, from, to);
81-
}
82-
} else if (from instanceof ClassTypeDef.JavaClass fromClass && to instanceof ClassTypeDef.JavaClass toClass) {
83-
if (!toClass.type().isAssignableFrom(fromClass.type())) {
84-
checkCast(generatorAdapter, context, from, to);
85-
}
86-
} else {
87-
checkCast(generatorAdapter, context, from, to);
67+
} else if (needsCast(from, to)) {
68+
checkCast(generatorAdapter, context, from, to);
69+
}
70+
}
71+
72+
private static boolean needsCast(TypeDef from, TypeDef to) {
73+
if (from.makeNullable().equals(to.makeNullable())) {
74+
return false;
75+
}
76+
if (from instanceof ClassTypeDef.Parameterized parameterized) {
77+
return needsCast(parameterized.rawType(), to);
78+
}
79+
if (to instanceof ClassTypeDef.Parameterized parameterized) {
80+
return needsCast(from, parameterized.rawType());
81+
}
82+
if (from instanceof ClassTypeDef.ClassElementType fromElement) {
83+
return needsCast(fromElement.classElement(), to);
84+
}
85+
if (from instanceof ClassTypeDef.JavaClass fromClass) {
86+
if (to instanceof ClassTypeDef.JavaClass toClass) {
87+
return !toClass.type().isAssignableFrom(fromClass.type());
88+
}
89+
}
90+
if (from instanceof ClassTypeDef.ClassDefType fromClassDef) {
91+
ClassTypeDef fromSuperclass = getSuperclass(fromClassDef.objectDef());
92+
if (fromSuperclass != null) {
93+
return needsCast(fromSuperclass, to);
94+
}
95+
}
96+
return true;
97+
}
98+
99+
private static boolean needsCast(ClassElement from, TypeDef to) {
100+
if (to instanceof ClassTypeDef.ClassElementType toElement) {
101+
return !from.isAssignable(toElement.classElement());
102+
}
103+
if (to instanceof ClassTypeDef.JavaClass toClass) {
104+
return !from.isAssignable(toClass.type());
105+
}
106+
if (to instanceof ClassTypeDef.ClassName toClassName) {
107+
return !from.isAssignable(toClassName.name());
108+
}
109+
if (to instanceof ClassTypeDef.ClassDefType toClassDefType) {
110+
if (from.isAssignable(toClassDefType.getName())) {
111+
return false;
88112
}
113+
return !from.isAssignable(toClassDefType.getName());
89114
}
115+
return true;
90116
}
91117

118+
private static ClassTypeDef getSuperclass(ObjectDef objectDef) {
119+
if (objectDef instanceof ClassDef classDef) {
120+
return classDef.getSuperclass();
121+
}
122+
if (objectDef instanceof EnumDef) {
123+
return ClassTypeDef.of(Enum.class);
124+
}
125+
if (objectDef instanceof RecordDef) {
126+
return ClassTypeDef.of(Record.class);
127+
}
128+
return null;
129+
}
130+
131+
92132
private static void checkCast(GeneratorAdapter generatorAdapter, MethodContext context, TypeDef from, TypeDef to) {
93133
TypeDef toType = ObjectDef.getContextualType(context.objectDef(), to);
94134
if (!toType.makeNullable().equals(from.makeNullable())) {

sourcegen-bytecode-writer/src/test/java/io/micronaut/sourcegen/bytecode/ByteCodeWriterTest.java

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.io.PrintStream;
2424
import java.io.PrintWriter;
2525
import java.io.StringWriter;
26+
import java.util.AbstractList;
27+
import java.util.List;
2628
import java.util.Map;
2729

2830
import static io.micronaut.sourcegen.bytecode.DecompilerUtils.decompileToJava;
@@ -1519,6 +1521,169 @@ Object invoke() {
15191521
""", decompileToJava(bytes));
15201522
}
15211523

1524+
@Test
1525+
void testCastingClassDefWithSuperclass() {
1526+
1527+
ClassDef myList = ClassDef.builder("example.MyList")
1528+
.superclass(ClassTypeDef.of(AbstractList.class))
1529+
.build();
1530+
1531+
ClassDef classDef = ClassDef.builder("example.Test")
1532+
.addMethod(MethodDef.builder("load")
1533+
.returns(ClassTypeDef.of(List.class))
1534+
.build((aThis, methodParameters) -> myList.asTypeDef()
1535+
.instantiate().returning())
1536+
)
1537+
.build();
1538+
1539+
StringWriter bytecodeWriter = new StringWriter();
1540+
byte[] bytes = generateFile(classDef, bytecodeWriter);
1541+
1542+
String bytecode = bytecodeWriter.toString();
1543+
1544+
Assertions.assertEquals("""
1545+
// class version 61.0 (61)
1546+
// access flags 0x0
1547+
// signature Ljava/lang/Object;
1548+
// declaration: example/Test
1549+
class example/Test {
1550+
1551+
1552+
// access flags 0x0
1553+
<init>()V
1554+
ALOAD 0
1555+
INVOKESPECIAL java/lang/Object.<init> ()V
1556+
RETURN
1557+
1558+
// access flags 0x0
1559+
load()Ljava/util/List;
1560+
NEW example/MyList
1561+
DUP
1562+
INVOKESPECIAL example/MyList.<init> ()V
1563+
ARETURN
1564+
}
1565+
""", bytecode);
1566+
1567+
Assertions.assertEquals("""
1568+
package example;
1569+
1570+
import java.util.List;
1571+
1572+
class Test {
1573+
List load() {
1574+
return new MyList();
1575+
}
1576+
}
1577+
""", decompileToJava(bytes));
1578+
}
1579+
1580+
@Test
1581+
void testCastingThisClassDefWithSuperclass() {
1582+
1583+
ClassDef classDef = ClassDef.builder("example.Test")
1584+
.superclass(TypeDef.parameterized(AbstractList.class, Number.class))
1585+
.addMethod(MethodDef.builder("load")
1586+
.returns(ClassTypeDef.of(List.class))
1587+
.build((aThis, methodParameters) -> aThis.type().instantiate().returning())
1588+
)
1589+
.build();
1590+
1591+
StringWriter bytecodeWriter = new StringWriter();
1592+
byte[] bytes = generateFile(classDef, bytecodeWriter);
1593+
1594+
String bytecode = bytecodeWriter.toString();
1595+
1596+
Assertions.assertEquals("""
1597+
// class version 61.0 (61)
1598+
// access flags 0x0
1599+
// signature Ljava/util/AbstractList<Ljava/lang/Number;>;
1600+
// declaration: example/Test extends java.util.AbstractList<java.lang.Number>
1601+
class example/Test extends java/util/AbstractList {
1602+
1603+
1604+
// access flags 0x0
1605+
<init>()V
1606+
ALOAD 0
1607+
INVOKESPECIAL java/util/AbstractList.<init> ()V
1608+
RETURN
1609+
1610+
// access flags 0x0
1611+
load()Ljava/util/List;
1612+
NEW example/Test
1613+
DUP
1614+
INVOKESPECIAL example/Test.<init> ()V
1615+
ARETURN
1616+
}
1617+
""", bytecode);
1618+
1619+
Assertions.assertEquals("""
1620+
package example;
1621+
1622+
import java.util.AbstractList;
1623+
import java.util.List;
1624+
1625+
class Test extends AbstractList {
1626+
List load() {
1627+
return new Test();
1628+
}
1629+
}
1630+
""", decompileToJava(bytes));
1631+
}
1632+
1633+
@Test
1634+
void testCastingEnum() {
1635+
1636+
EnumDef enumDef = EnumDef.builder("example.MyEnum")
1637+
.addEnumConstant("A")
1638+
.addEnumConstant("B")
1639+
.build();
1640+
1641+
ClassDef classDef = ClassDef.builder("example.Test")
1642+
.addMethod(MethodDef.builder("load")
1643+
.returns(Enum.class)
1644+
.build((aThis, methodParameters) -> enumDef.asTypeDef()
1645+
.getStaticField(enumDef.getField("A"))
1646+
.returning())
1647+
)
1648+
.build();
1649+
1650+
StringWriter bytecodeWriter = new StringWriter();
1651+
byte[] bytes = generateFile(classDef, bytecodeWriter);
1652+
1653+
String bytecode = bytecodeWriter.toString();
1654+
1655+
Assertions.assertEquals("""
1656+
// class version 61.0 (61)
1657+
// access flags 0x0
1658+
// signature Ljava/lang/Object;
1659+
// declaration: example/Test
1660+
class example/Test {
1661+
1662+
1663+
// access flags 0x0
1664+
<init>()V
1665+
ALOAD 0
1666+
INVOKESPECIAL java/lang/Object.<init> ()V
1667+
RETURN
1668+
1669+
// access flags 0x0
1670+
load()Ljava/lang/Enum;
1671+
GETSTATIC example/MyEnum.A : Lexample/MyEnum;
1672+
ARETURN
1673+
}
1674+
""", bytecode);
1675+
1676+
Assertions.assertEquals("""
1677+
package example;
1678+
1679+
class Test {
1680+
Enum load() {
1681+
return MyEnum.A;
1682+
}
1683+
}
1684+
""", decompileToJava(bytes));
1685+
}
1686+
15221687
private String toBytecode(ObjectDef objectDef) {
15231688
StringWriter stringWriter = new StringWriter();
15241689
generateFile(objectDef, stringWriter);

sourcegen-model/src/main/java/io/micronaut/sourcegen/model/ClassDef.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public final class ClassDef extends ObjectDef {
4040
private final ClassTypeDef superclass;
4141
private final StatementDef staticInitializer;
4242

43-
private ClassDef(ClassTypeDef type,
43+
private ClassDef(ClassTypeDef.ClassName className,
4444
EnumSet<Modifier> modifiers,
4545
List<FieldDef> fields,
4646
List<MethodDef> methods,
@@ -52,16 +52,17 @@ private ClassDef(ClassTypeDef type,
5252
ClassTypeDef superclass,
5353
List<ObjectDef> innerTypes,
5454
StatementDef staticInitializer) {
55-
super(type, modifiers, annotations, javadoc, methods, properties, superinterfaces, innerTypes);
55+
super(className, modifiers, annotations, javadoc, methods, properties, superinterfaces, innerTypes);
56+
ClassTypeDef.of(this);
5657
this.fields = fields;
5758
this.typeVariables = typeVariables;
5859
this.superclass = superclass;
5960
this.staticInitializer = staticInitializer;
6061
}
6162

6263
@Override
63-
public ClassDef withType(ClassTypeDef type) {
64-
return new ClassDef(type, modifiers, fields, methods, properties, annotations, javadoc, typeVariables, superinterfaces, superclass, innerTypes, staticInitializer);
64+
public ClassDef withClassName(ClassTypeDef.ClassName className) {
65+
return new ClassDef(className, modifiers, fields, methods, properties, annotations, javadoc, typeVariables, superinterfaces, superclass, innerTypes, staticInitializer);
6566
}
6667

6768
@Override
@@ -108,7 +109,7 @@ public FieldDef findField(String name) {
108109
public FieldDef getField(String name) {
109110
FieldDef field = findField(name);
110111
if (field == null) {
111-
throw new IllegalStateException("Class: " + this.name + " doesn't have a field: " + name);
112+
throw new IllegalStateException("Class: " + this.className + " doesn't have a field: " + name);
112113
}
113114
return null;
114115
}
@@ -122,8 +123,11 @@ public boolean hasField(String name) {
122123
if (superclass instanceof ClassTypeDef.ClassElementType classElementType) {
123124
return classElementType.classElement().findField(name).isPresent();
124125
}
125-
if (superclass instanceof ClassTypeDef.ClassDefType classDefType) {
126-
return classDefType.classDef().hasField(name);
126+
if (superclass instanceof ClassTypeDef.ClassDefType classDefType && classDefType.objectDef() instanceof ClassDef classDef) {
127+
return classDef.hasField(name);
128+
}
129+
if (superclass instanceof ClassTypeDef.ClassDefType classDefType && classDefType.objectDef() instanceof EnumDef enumDef) {
130+
return enumDef.hasField(name);
127131
}
128132
if (superclass instanceof ClassTypeDef.JavaClass javaClass) {
129133
try {
@@ -146,7 +150,7 @@ public StatementDef getStaticInitializer() {
146150

147151
@Override
148152
public String toString() {
149-
return "ClassDef{" + "name='" + name + '\'' + '}';
153+
return "ClassDef{" + "name='" + className + '\'' + '}';
150154
}
151155

152156
/**
@@ -199,7 +203,7 @@ public ClassDefBuilder addStaticInitializer(StatementDef staticInitializer) {
199203
}
200204

201205
public ClassDef build() {
202-
return new ClassDef(ClassTypeDef.of(name), modifiers, fields, methods, properties, annotations, javadoc, typeVariables, superinterfaces, superclass, innerTypes, staticInitializer);
206+
return new ClassDef(new ClassTypeDef.ClassName(name), modifiers, fields, methods, properties, annotations, javadoc, typeVariables, superinterfaces, superclass, innerTypes, staticInitializer);
203207
}
204208

205209
/**

0 commit comments

Comments
 (0)