Skip to content

Commit d6436aa

Browse files
committed
Convert to method reference and inline instance variables
1 parent 0fc2542 commit d6436aa

File tree

8 files changed

+455
-51
lines changed

8 files changed

+455
-51
lines changed

src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDe
346346
continue;
347347
}
348348

349+
if (MethodReferenceHelper.convertToMethodReference(root)) {
350+
decompileRecord.add("ProcessLambda", root);
351+
continue;
352+
}
353+
349354
if (InlineSingleBlockHelper.inlineSingleBlocks(root)) {
350355
decompileRecord.add("InlineSingleBlocks", root);
351356
continue;
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
package org.jetbrains.java.decompiler.modules.decompiler;
2+
3+
import org.jetbrains.java.decompiler.code.CodeConstants;
4+
import org.jetbrains.java.decompiler.code.FullInstructionSequence;
5+
import org.jetbrains.java.decompiler.code.Instruction;
6+
import org.jetbrains.java.decompiler.main.ClassesProcessor;
7+
import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode;
8+
import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode.LambdaInformation;
9+
import org.jetbrains.java.decompiler.main.DecompilerContext;
10+
import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
11+
import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement;
12+
import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement;
13+
import org.jetbrains.java.decompiler.struct.StructClass;
14+
import org.jetbrains.java.decompiler.struct.StructMethod;
15+
import org.jetbrains.java.decompiler.struct.consts.LinkConstant;
16+
import org.jetbrains.java.decompiler.util.InterpreterUtil;
17+
18+
import java.io.IOException;
19+
import java.util.Iterator;
20+
21+
public class MethodReferenceHelper {
22+
public static boolean convertToMethodReference(RootStatement root) throws IOException {
23+
return convertToMethodReferenceRec(root);
24+
}
25+
26+
public static boolean convertToMethodReferenceRec(Statement stat) throws IOException {
27+
boolean result = false;
28+
for (Statement subStat : stat.getStats()) {
29+
result |= convertToMethodReferenceRec(subStat);
30+
}
31+
if (stat.getExprents() != null) {
32+
for (int i = 0; i < stat.getExprents().size(); i++) {
33+
Exprent exp = stat.getExprents().get(i);
34+
for (Exprent subExp : exp.getAllExprents(true)) {
35+
if (convertToMethodReference(stat, i, subExp)) {
36+
result |= true;
37+
}
38+
for (int j = 0; j < stat.getExprents().size(); j++) {
39+
if (stat.getExprents().get(j) == exp) {
40+
i = j;
41+
break;
42+
}
43+
}
44+
if (removeInstanceAssignment(stat, i, subExp)) {
45+
result |= true;
46+
}
47+
for (int j = 0; j < stat.getExprents().size(); j++) {
48+
if (stat.getExprents().get(j) == exp) {
49+
i = j;
50+
break;
51+
}
52+
}
53+
}
54+
}
55+
}
56+
return result;
57+
}
58+
59+
private static boolean convertToMethodReference(Statement stat, int i, Exprent exp) throws IOException {
60+
if (exp instanceof NewExprent newExprent
61+
&& newExprent.isLambda()
62+
&& !newExprent.isMethodReference()) {
63+
if (stat.getTopParent().mt.getName().contains("<init>")) {
64+
System.out.println();
65+
}
66+
String className = newExprent.getNewType().value;
67+
ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(className);
68+
LambdaInformation info = node.lambdaInformation;
69+
StructClass struct = DecompilerContext.getStructContext().getClass(info.content_class_name);
70+
StructMethod method = struct.getMethod(info.content_method_key);
71+
if (!method.hasModifier(CodeConstants.ACC_STATIC))
72+
return false;
73+
method.expandData(struct);
74+
FullInstructionSequence seq = method.getInstructionSequence();
75+
Iterator<Instruction> iterator = seq.iterator();
76+
Instruction next = null;
77+
int argument = 0;
78+
79+
if (!iterator.hasNext())
80+
return false;
81+
next = iterator.next();
82+
83+
// new instance
84+
if (next.opcode == CodeConstants.opc_new) {
85+
if (!iterator.hasNext())
86+
return false;
87+
next = iterator.next();
88+
if (next.opcode != CodeConstants.opc_dup)
89+
return false;
90+
91+
if (!iterator.hasNext())
92+
return false;
93+
next = iterator.next();
94+
}
95+
96+
// Load arguments
97+
while (iterator.hasNext()
98+
&& next.opcode >= CodeConstants.opc_iload && next.opcode <= CodeConstants.opc_aload
99+
&& next.operand(0) == argument) {
100+
next = iterator.next();
101+
argument++;
102+
}
103+
104+
if (next == null)
105+
return false;
106+
boolean varargs = false;
107+
int varargsCount = 0;
108+
// varargs array length
109+
if (next.opcode >= CodeConstants.opc_bipush && next.opcode <= CodeConstants.opc_sipush) {
110+
varargsCount = next.operand(0);
111+
varargs = true;
112+
113+
// varargs array creation
114+
if (!iterator.hasNext())
115+
return false;
116+
next = iterator.next();
117+
if (next.opcode < CodeConstants.opc_newarray || next.opcode > CodeConstants.opc_anewarray)
118+
return false;
119+
for (int j = 0; j < varargsCount; j++) {
120+
121+
// duplicate varargs array
122+
if (!iterator.hasNext())
123+
return false;
124+
next = iterator.next();
125+
if (next.opcode != CodeConstants.opc_dup)
126+
return false;
127+
128+
// varargs array index
129+
if (!iterator.hasNext())
130+
return false;
131+
next = iterator.next();
132+
if (next.opcode < CodeConstants.opc_bipush || next.opcode > CodeConstants.opc_sipush
133+
|| next.operand(0) != j)
134+
return false;
135+
136+
// load argument for varargs array
137+
if (!iterator.hasNext())
138+
return false;
139+
next = iterator.next();
140+
if (next.opcode < CodeConstants.opc_iload || next.opcode > CodeConstants.opc_aload
141+
|| next.operand(0) != argument + j)
142+
return false;
143+
144+
// store argument in varargs array
145+
if (!iterator.hasNext())
146+
return false;
147+
next = iterator.next();
148+
if (next.opcode < CodeConstants.opc_iastore || next.opcode > CodeConstants.opc_sastore)
149+
return false;
150+
}
151+
152+
if (!iterator.hasNext())
153+
return false;
154+
next = iterator.next();
155+
}
156+
// invoke method
157+
if (next.opcode < CodeConstants.opc_invokevirtual || next.opcode > CodeConstants.opc_invokeinterface)
158+
return false;
159+
160+
Instruction invoke = next;
161+
162+
if (!iterator.hasNext())
163+
return false;
164+
next = iterator.next();
165+
166+
if (next.opcode == CodeConstants.opc_pop) {
167+
if (!iterator.hasNext())
168+
return false;
169+
next = iterator.next();
170+
}
171+
172+
// return
173+
if (next.opcode < CodeConstants.opc_ireturn || next.opcode > CodeConstants.opc_return)
174+
return false;
175+
176+
if (iterator.hasNext())
177+
return false;
178+
179+
LinkConstant link = struct.getPool().getLinkConstant(invoke.operand(0));
180+
boolean instance = invoke.opcode != CodeConstants.opc_invokestatic
181+
&& !link.elementname.equals(CodeConstants.INIT_NAME);
182+
183+
String newClass = link.classname;
184+
String newMethod = link.elementname;
185+
String newDescriptor = link.descriptor;
186+
String newKey = InterpreterUtil.makeUniqueKey(newMethod, newDescriptor);
187+
188+
StructClass newStructClass = DecompilerContext.getStructContext().getClass(newClass);
189+
if (newStructClass == null)
190+
return false;
191+
StructMethod newStructMethod = newStructClass.getMethodRecursive(newMethod, newDescriptor);
192+
if (newStructMethod == null)
193+
return false;
194+
195+
if (newStructMethod.hasModifier(CodeConstants.ACC_SYNTHETIC))
196+
return false;
197+
198+
ClassNode newNode = DecompilerContext.getClassProcessor().getMapRootClasses().get(newClass);
199+
if (newNode != null && newNode.type == ClassesProcessor.ClassNode.Type.ANONYMOUS)
200+
return false;
201+
202+
if (method.methodDescriptor().params.length - (varargs ? varargsCount - 1 : 0) != newStructMethod.methodDescriptor().params.length + (instance ? 1 : 0))
203+
return false;
204+
if (newExprent.getConstructor().getLstParameters().size() > (instance ? 1 : 0))
205+
return false;
206+
info.content_class_name = newClass;
207+
info.content_method_name = newMethod;
208+
info.content_method_descriptor = newDescriptor;
209+
info.content_method_invocation_type = switch (invoke.opcode) {
210+
case CodeConstants.opc_invokevirtual -> CodeConstants.CONSTANT_MethodHandle_REF_invokeVirtual;
211+
case CodeConstants.opc_invokespecial -> instance ? CodeConstants.CONSTANT_MethodHandle_REF_newInvokeSpecial : CodeConstants.CONSTANT_MethodHandle_REF_invokeSpecial;
212+
case CodeConstants.opc_invokestatic -> CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic;
213+
case CodeConstants.opc_invokeinterface -> CodeConstants.CONSTANT_MethodHandle_REF_invokeInterface;
214+
default -> -1;
215+
};
216+
info.content_method_key = newKey;
217+
info.is_method_reference = true;
218+
info.is_content_method_static = invoke.opcode == CodeConstants.opc_invokestatic;
219+
newExprent.setMethodReference(true);
220+
InvocationExprent constructor = newExprent.getConstructor();
221+
if (instance && constructor.getLstParameters().size() == 1) {
222+
constructor.setInstance(constructor.getLstParameters().get(0));
223+
}
224+
225+
if (i > 0) {
226+
Exprent instanceExp = newExprent.getConstructor().getInstance();
227+
Exprent previous = stat.getExprents().get(i - 1);
228+
if (instanceExp instanceof VarExprent varExp
229+
&& previous instanceof AssignmentExprent assignment
230+
&& varExp.equalsVersions(assignment.getLeft())
231+
&& !varExp.isVarReferenced(stat.getTopParent(), (VarExprent) assignment.getLeft())) {
232+
newExprent.getConstructor().setInstance(assignment.getRight());
233+
newExprent.getConstructor().getLstParameters().set(0, assignment.getRight());
234+
stat.getExprents().remove(i - 1);
235+
}
236+
}
237+
return true;
238+
}
239+
return false;
240+
}
241+
242+
private static boolean removeInstanceAssignment(Statement stat, int i, Exprent exp) {
243+
if (exp instanceof NewExprent newExp
244+
&& newExp.isLambda()
245+
&& newExp.isMethodReference()
246+
&& i >= 2
247+
&& newExp.getConstructor().getInstance() instanceof VarExprent stackVar
248+
&& stackVar.isStack()) {
249+
Exprent nonNull = stat.getExprents().get(i - 1);
250+
Exprent assign = stat.getExprents().get(i - 2);
251+
if (nonNull instanceof InvocationExprent inv
252+
&& inv.getClassname().equals("java/util/Objects")
253+
&& inv.getName().equals("requireNonNull")
254+
&& inv.getStringDescriptor().equals("(Ljava/lang/Object;)Ljava/lang/Object;")
255+
&& stackVar.equalsVersions(inv.getLstParameters().get(0))
256+
&& assign instanceof AssignmentExprent stackAssign
257+
&& stackVar.equalsVersions(stackAssign.getLeft())) {
258+
newExp.getConstructor().setInstance(stackAssign.getRight());
259+
stat.getExprents().remove(i - 1);
260+
stat.getExprents().remove(i - 2);
261+
return true;
262+
}
263+
}
264+
return false;
265+
}
266+
}

src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,10 @@ public boolean isMethodReference() {
851851
return methodReference;
852852
}
853853

854+
public void setMethodReference(boolean methodReference) {
855+
this.methodReference = methodReference;
856+
}
857+
854858
public String getLambdaMethodKey() {
855859
ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(newType.value);
856860
if (node != null && constructor != null) {

test/org/jetbrains/java/decompiler/SingleClassesTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,8 @@ private void registerDefault() {
793793
register(JAVA_8, "TestTryLoopSimpleFinally");
794794
register(JAVA_8, "TestTryLoopReturnFinally");
795795
register(JASM, "TestNumberCompareToBoolean");
796+
797+
register(JAVA_25, "TestMethodReferenceJ25");
796798
}
797799

798800
private void registerEntireClassPath() {

testData/results/pkg/TestGenericsQualified.dec

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import java.util.concurrent.CompletableFuture;
66
import java.util.stream.Stream;
77

88
public class TestGenericsQualified {
9-
public Comparator<String> field = Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(i -> i.toString());// 10
9+
public Comparator<String> field = Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(String::toString);// 10
1010
public CompletableFuture<String> field2 = CompletableFuture.<String>supplyAsync(() -> "").thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "2"));// 11
1111
public Optional<String> field3 = Optional.of("").map(s -> s + "3");// 12
12-
public Stream<String> field4 = Stream.of("1", "2").sorted(Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(i -> i.toString()));// 13 14
13-
public Comparator<String> field5 = Comparator.comparing(String::length).thenComparing(i -> i.toString());// 15
12+
public Stream<String> field4 = Stream.of("1", "2").sorted(Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(String::toString));// 13 14
13+
public Comparator<String> field5 = Comparator.comparing(String::length).thenComparing(String::toString);// 15
1414
public Comparator<TestGenericsQualified> field6 = Comparator.<TestGenericsQualified, Integer>comparing(TestGenericsQualified::get).reversed();// 16
1515

1616
public int get() {
@@ -22,7 +22,7 @@ public class TestGenericsQualified {
2222
}
2323

2424
public Comparator<String> method() {
25-
return Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(i -> i.toString());// 27
25+
return Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(String::toString);// 27
2626
}
2727
}
2828

@@ -99,14 +99,6 @@ class 'pkg/TestGenericsQualified' {
9999
7 8
100100
}
101101

102-
method 'lambda$new$1 (Ljava/lang/String;)Ljava/lang/String;' {
103-
0 8
104-
1 8
105-
2 8
106-
3 8
107-
4 8
108-
}
109-
110102
method 'lambda$new$2 ()Ljava/lang/String;' {
111103
0 9
112104
1 9
@@ -151,22 +143,6 @@ class 'pkg/TestGenericsQualified' {
151143
7 11
152144
}
153145

154-
method 'lambda$new$7 (Ljava/lang/String;)Ljava/lang/String;' {
155-
0 11
156-
1 11
157-
2 11
158-
3 11
159-
4 11
160-
}
161-
162-
method 'lambda$new$8 (Ljava/lang/String;)Ljava/lang/String;' {
163-
0 12
164-
1 12
165-
2 12
166-
3 12
167-
4 12
168-
}
169-
170146
method 'get ()I' {
171147
0 16
172148
1 16
@@ -199,14 +175,6 @@ class 'pkg/TestGenericsQualified' {
199175
6 24
200176
7 24
201177
}
202-
203-
method 'lambda$method$10 (Ljava/lang/String;)Ljava/lang/String;' {
204-
0 24
205-
1 24
206-
2 24
207-
3 24
208-
4 24
209-
}
210178
}
211179

212180
Lines mapping:

0 commit comments

Comments
 (0)