Skip to content

Commit

Permalink
Start work on generic types on the lattice
Browse files Browse the repository at this point in the history
  • Loading branch information
jaskarth committed May 1, 2024
1 parent 4181a8c commit 7611b1c
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public AssignmentExprent(Exprent left, Exprent right, FunctionExprent.FunctionTy

@Override
public VarType getExprType() {
// Union together types
// join the types on the lattice
VarType rType = VarType.join(left.getExprType(), right.getExprType());
// TODO: maybe there's a better default for null
// No possible result? Return the left's type.
return rType == null ? left.getExprType() : rType;
}

Expand Down Expand Up @@ -159,8 +159,7 @@ public TextBuffer toJava(int indent) {

buffer.addStartBytecodeMapping(bytecode);

if (this.left instanceof VarExprent && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILER_COMMENTS)) {
VarExprent varLeft = (VarExprent) this.left;
if (this.left instanceof VarExprent varLeft && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILER_COMMENTS)) {

if (varLeft.isDefinition() && varLeft.getProcessor() != null) {
if (varLeft.getProcessor().getSyntheticSemaphores().contains(varLeft.getIndex())) {
Expand Down
44 changes: 40 additions & 4 deletions src/org/jetbrains/java/decompiler/struct/gen/VarType.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.modules.decompiler.ValidationHelper;
import org.jetbrains.java.decompiler.struct.StructClass;
import org.jetbrains.java.decompiler.struct.gen.generics.GenericType;
import org.jetbrains.java.decompiler.util.InterpreterUtil;

public class VarType {
Expand Down Expand Up @@ -378,17 +379,23 @@ public boolean higherInLatticeThan(VarType other) {
case OBJECT:
// BOT -> null -> Impl... -> Object -> TOP

// if other is null, then we must be higher in the lattice
// if other is null, then, as an object, we must be higher in the lattice
if (other.type == CodeType.NULL) {
// TODO: check for 'this' not being null
return true;
} else if (this.equals(VARTYPE_OBJECT)) {
// if we are object, then we are higher than everything other than TOP
return other.type == CodeType.OBJECT && !other.equals(VARTYPE_OBJECT);
return !other.equals(VARTYPE_OBJECT);
}

// Check base if neither 'this' and 'other' are generic, or if the generic types satisfy the condition.

boolean checkBase = true;
if (isGeneric() && other.isGeneric()) {
checkBase = shouldCheckGenericBase((GenericType) this, (GenericType) other);
}

// Given that B extends A, B is lower in the lattice when compared to A.
if (other.type == CodeType.OBJECT && !other.value.equals(value)) {
if (checkBase && other.type == CodeType.OBJECT && !other.value.equals(value)) {
if (DecompilerContext.getStructContext().instanceOf(other.value, value)) {
return true;
}
Expand All @@ -398,6 +405,32 @@ public boolean higherInLatticeThan(VarType other) {
return res;
}

// For generic types 't' and 'other', should we check the base type to determine if t <: other?
// E.g. List<Type> <: Collection<Type>
private static boolean shouldCheckGenericBase(GenericType t, GenericType other) {
if (t.argumentsEqual(other)) {
return t.getWildcard() == other.getWildcard();
} else {
// Arguments not equal. Check them, one by one.
if (t.getArguments().size() == other.getArguments().size()) {
for (int i = 0; i < t.getArguments().size(); i++) {
VarType a1 = t.getArguments().get(i);
VarType a2 = other.getArguments().get(i);

// List<? extends Type> <: Collection<Type>
// List<? super Type> <: Collection<Type>
if (a2 != null && a1 instanceof GenericType ga1 && ga1.getWildcard() != GenericType.WILDCARD_NO) {
if (a1.value.equals(a2.value) && !a2.isGeneric()) {
return true;
}
}
}
}
}

return false;
}

// higherEqualInLattice BUT we also check for assignability relations
public boolean higherCrossFamilyThan(VarType other, boolean equal) {
// Check higher (equal) within the same lattice
Expand All @@ -411,6 +444,7 @@ public boolean higherCrossFamilyThan(VarType other, boolean equal) {
}
}

// c.f. https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.10.1
boolean res = false;
switch (this.type) {
case DOUBLE: // float, long, and integer can be assigned to double
Expand Down Expand Up @@ -447,6 +481,7 @@ public boolean higherCrossFamilyThan(VarType other, boolean equal) {
// - B extends A
// - C extends A
// meet(B, C) must be null
// TODO: no it doesn't! it can be a union type, that way it preserves information!
return VARTYPE_NULL;
}
}
Expand All @@ -473,6 +508,7 @@ public boolean higherCrossFamilyThan(VarType other, boolean equal) {
return VARTYPE_INT;
}
case OBJECT:
// TODO: can make an intersection type?
StructClass cl = DecompilerContext.getStructContext().findCommonAncestor(type1.value, type2.value);
if (cl != null) {
return new VarType(cl.qualifiedName, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public VarType getParent() {
return parent;
}

public List<VarType> getArguments() {
public List<@Nullable VarType> getArguments() {
return arguments;
}
@Override
Expand Down
1 change: 1 addition & 0 deletions test/org/jetbrains/java/decompiler/SingleClassesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ private void registerJavaRuntime() {
// TODO: wrong cast in lambda for array
register(JAVA_8, "TestArrayGenerics");
register(JAVA_8, "TestArrayArg");
register(JAVA_8, "TestGenericLattice");
}

private void registerLiterals() {
Expand Down
149 changes: 149 additions & 0 deletions testData/results/pkg/TestGenericLattice.dec
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package pkg;

import java.util.Collection;
import java.util.List;

public class TestGenericLattice {
Collection<TestGenericLattice.Cat> cc;
List<? extends TestGenericLattice.Cat> lec;
List<? super TestGenericLattice.Cat> lsc;
Collection<? extends TestGenericLattice.Cat> cec1;
Collection<? extends TestGenericLattice.Cat> cec2;
Collection<? super TestGenericLattice.Cat> csc1;
Collection<? super TestGenericLattice.Cat> csc2;
List<? extends TestGenericLattice.Animal> lea1;
List<? extends TestGenericLattice.Animal> lea2;
List<? super TestGenericLattice.Animal> lsa;
List<? super TestGenericLattice.Cat> lsc1;
List<?> w1;
List r1;

public void testAssignable() {
List<TestGenericLattice.Animal> la = null;// 28
List<TestGenericLattice.Cat> lc = null;// 29
this.cc = lc;// 30
this.lec = lc;// 33
this.lsc = lc;// 34
this.cec1 = this.lec;// 37
this.cec2 = lc;// 38
this.csc1 = this.lsc;// 39
this.csc2 = lc;// 40
this.lea1 = this.lec;// 43
this.lea2 = la;// 44
this.lsa = la;// 46
this.lsc1 = this.lsa;// 49
this.w1 = lc;// 52
this.r1 = lc;// 54
}// 55

static class Animal {
}

static class Cat extends TestGenericLattice.Animal {
}
}

class 'pkg/TestGenericLattice' {
method 'testAssignable ()V' {
0 21
1 21
2 22
3 22
4 23
5 23
6 23
7 23
8 23
9 24
a 24
b 24
c 24
d 24
e 25
f 25
10 25
11 25
12 25
13 26
14 26
15 26
16 26
17 26
18 26
19 26
1a 26
1b 27
1c 27
1d 27
1e 27
1f 27
20 28
21 28
22 28
23 28
24 28
25 28
26 28
27 28
28 29
29 29
2a 29
2b 29
2c 29
2d 30
2e 30
2f 30
30 30
31 30
32 30
33 30
34 30
35 31
36 31
37 31
38 31
39 31
3a 32
3b 32
3c 32
3d 32
3e 32
3f 33
40 33
41 33
42 33
43 33
44 33
45 33
46 33
47 34
48 34
49 34
4a 34
4b 34
4c 35
4d 35
4e 35
4f 35
50 35
51 36
}
}

Lines mapping:
28 <-> 22
29 <-> 23
30 <-> 24
33 <-> 25
34 <-> 26
37 <-> 27
38 <-> 28
39 <-> 29
40 <-> 30
43 <-> 31
44 <-> 32
46 <-> 33
49 <-> 34
52 <-> 35
54 <-> 36
55 <-> 37
56 changes: 56 additions & 0 deletions testData/src/java8/pkg/TestGenericLattice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package pkg;

import java.util.Collection;
import java.util.List;

// Taken roughly from: https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
public class TestGenericLattice {
static class Animal {}

static class Cat extends Animal {}

// these need to be fields to not get deleted by javac
Collection<Cat> cc;
List<? extends Cat> lec;
List<? super Cat> lsc;
Collection<? extends Cat> cec1;
Collection<? extends Cat> cec2;
Collection<? super Cat> csc1;
Collection<? super Cat> csc2;
List<? extends Animal> lea1;
List<? extends Animal> lea2;
List<? super Animal> lsa;
List<? super Cat> lsc1;
List<?> w1;
List r1;

public void testAssignable() {
List<Animal> la = null;
List<Cat> lc = null;
cc = lc; // List <: Collection

// Assign into wildcards
lec = lc;
lsc = lc;

// into supertype
cec1 = lec;
cec2 = lc;
csc1 = lsc;
csc2 = lc;

// Into supertype of parameter
lea1 = lec;
lea2 = la;

lsa = la;

// contravariance nightmare zone
lsc1 = lsa;

// rawtypes and wildcards
w1 = lc;

r1 = lc;
}
}

0 comments on commit 7611b1c

Please sign in to comment.