Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,11 @@ public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDe
continue;
}

if (MethodReferenceHelper.convertToMethodReference(root)) {
decompileRecord.add("ProcessLambda", root);
continue;
}

if (InlineSingleBlockHelper.inlineSingleBlocks(root)) {
decompileRecord.add("InlineSingleBlocks", root);
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package org.jetbrains.java.decompiler.modules.decompiler;

import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.code.FullInstructionSequence;
import org.jetbrains.java.decompiler.code.Instruction;
import org.jetbrains.java.decompiler.main.ClassesProcessor;
import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode;
import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode.LambdaInformation;
import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement;
import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement;
import org.jetbrains.java.decompiler.struct.StructClass;
import org.jetbrains.java.decompiler.struct.StructMethod;
import org.jetbrains.java.decompiler.struct.consts.LinkConstant;
import org.jetbrains.java.decompiler.util.InterpreterUtil;

import java.io.IOException;
import java.util.Iterator;

public class MethodReferenceHelper {
public static boolean convertToMethodReference(RootStatement root) throws IOException {
return convertToMethodReferenceRec(root);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to also modify existing lambdas which could be represented with a method reference. Perhaps it should be behind a decompiler option

}

public static boolean convertToMethodReferenceRec(Statement stat) throws IOException {
boolean result = false;
for (Statement subStat : stat.getStats()) {
result |= convertToMethodReferenceRec(subStat);
}
if (stat.getExprents() != null) {
for (int i = 0; i < stat.getExprents().size(); i++) {
Exprent exp = stat.getExprents().get(i);
for (Exprent subExp : exp.getAllExprents(true)) {
if (convertToMethodReference(stat, i, subExp)) {
result |= true;
}
for (int j = 0; j < stat.getExprents().size(); j++) {
if (stat.getExprents().get(j) == exp) {
i = j;
break;
}
}
if (removeInstanceAssignment(stat, i, subExp)) {
result |= true;
}
for (int j = 0; j < stat.getExprents().size(); j++) {
if (stat.getExprents().get(j) == exp) {
i = j;
break;
}
}
}
}
}
return result;
}

private static boolean convertToMethodReference(Statement stat, int i, Exprent exp) throws IOException {
if (!(exp instanceof NewExprent newExprent)
|| !newExprent.isLambda()
|| newExprent.isMethodReference())
return false;
String className = newExprent.getNewType().value;
ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(className);
LambdaInformation info = node.lambdaInformation;
StructClass struct = DecompilerContext.getStructContext().getClass(info.content_class_name);
StructMethod method = struct.getMethod(info.content_method_key);
if (!method.hasModifier(CodeConstants.ACC_STATIC))
return false;
method.expandData(struct);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, does this need to manually parse the instructions or would going through the exprents suffice?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ClassWrapper on the ClassNode isn't set at this point and even if it was the exprents for lambdas are almost certainly not generated at this point since they are the last methods in the class file.

I did consider doing it when the lambda is written to the output like with array constructor references but that would prevent inlining the variable since if it happened when the lambda is written nothing in codeToJava would see that the variable assignment is removed whereas if the variable is removed during codeToJava then there I don't think there would be anyway for the variable to be inlined correctly since lambdas can't call a method on an instance without the instance being referenced via a variable.

FullInstructionSequence seq = method.getInstructionSequence();
Iterator<Instruction> iterator = seq.iterator();
int argument = 0;

if (!iterator.hasNext())
return false;
Instruction next = iterator.next();

// new instance
if (next.opcode == CodeConstants.opc_new) {
if (!iterator.hasNext())
return false;
next = iterator.next();
if (next.opcode != CodeConstants.opc_dup)
return false;

if (!iterator.hasNext())
return false;
next = iterator.next();
}

// Load arguments
while (iterator.hasNext()
&& next.opcode >= CodeConstants.opc_iload && next.opcode <= CodeConstants.opc_aload
&& next.operand(0) == argument) {
next = iterator.next();
argument++;
}

boolean varargs = false;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this code doing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The varargs variable is used for checking if the amount of method parameters of the lambda is what is expected.

int varargsCount = 0;
// varargs array length
if (next.opcode >= CodeConstants.opc_bipush && next.opcode <= CodeConstants.opc_sipush) {
varargsCount = next.operand(0);
varargs = true;

// varargs array creation
if (!iterator.hasNext())
return false;
next = iterator.next();
if (next.opcode < CodeConstants.opc_newarray || next.opcode > CodeConstants.opc_anewarray)
return false;
for (int j = 0; j < varargsCount; j++) {

// duplicate varargs array
if (!iterator.hasNext())
return false;
next = iterator.next();
if (next.opcode != CodeConstants.opc_dup)
return false;

// varargs array index
if (!iterator.hasNext())
return false;
next = iterator.next();
if (next.opcode < CodeConstants.opc_bipush || next.opcode > CodeConstants.opc_sipush
|| next.operand(0) != j)
return false;

// load argument for varargs array
if (!iterator.hasNext())
return false;
next = iterator.next();
if (next.opcode < CodeConstants.opc_iload || next.opcode > CodeConstants.opc_aload
|| next.operand(0) != argument + j)
return false;

// store argument in varargs array
if (!iterator.hasNext())
return false;
next = iterator.next();
if (next.opcode < CodeConstants.opc_iastore || next.opcode > CodeConstants.opc_sastore)
return false;
}

if (!iterator.hasNext())
return false;
next = iterator.next();
}
// invoke method
if (next.opcode < CodeConstants.opc_invokevirtual || next.opcode > CodeConstants.opc_invokeinterface)
return false;

Instruction invoke = next;

if (!iterator.hasNext())
return false;
next = iterator.next();

if (next.opcode == CodeConstants.opc_pop) {
if (!iterator.hasNext())
return false;
next = iterator.next();
}

// return
if (next.opcode < CodeConstants.opc_ireturn || next.opcode > CodeConstants.opc_return)
return false;

if (iterator.hasNext())
return false;

LinkConstant link = struct.getPool().getLinkConstant(invoke.operand(0));
boolean instance = invoke.opcode != CodeConstants.opc_invokestatic
&& !link.elementname.equals(CodeConstants.INIT_NAME);

String newClass = link.classname;
String newMethod = link.elementname;
String newDescriptor = link.descriptor;
String newKey = InterpreterUtil.makeUniqueKey(newMethod, newDescriptor);

StructClass newStructClass = DecompilerContext.getStructContext().getClass(newClass);
if (newStructClass == null)
return false;
StructMethod newStructMethod = newStructClass.getMethodRecursive(newMethod, newDescriptor);
if (newStructMethod == null)
return false;

if (newStructMethod.hasModifier(CodeConstants.ACC_SYNTHETIC))
return false;

ClassNode newNode = DecompilerContext.getClassProcessor().getMapRootClasses().get(newClass);
if (newNode != null && newNode.type == ClassesProcessor.ClassNode.Type.ANONYMOUS)
return false;

if (method.methodDescriptor().params.length - (varargs ? varargsCount - 1 : 0) != newStructMethod.methodDescriptor().params.length + (instance ? 1 : 0))
return false;
if (newExprent.getConstructor().getLstParameters().size() > (instance ? 1 : 0))
return false;
info.content_class_name = newClass;
info.content_method_name = newMethod;
info.content_method_descriptor = newDescriptor;
info.content_method_invocation_type = switch (invoke.opcode) {
case CodeConstants.opc_invokevirtual -> CodeConstants.CONSTANT_MethodHandle_REF_invokeVirtual;
case CodeConstants.opc_invokespecial -> instance ? CodeConstants.CONSTANT_MethodHandle_REF_newInvokeSpecial : CodeConstants.CONSTANT_MethodHandle_REF_invokeSpecial;
case CodeConstants.opc_invokestatic -> CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic;
case CodeConstants.opc_invokeinterface -> CodeConstants.CONSTANT_MethodHandle_REF_invokeInterface;
default -> -1;
};
info.content_method_key = newKey;
info.is_method_reference = true;
info.is_content_method_static = invoke.opcode == CodeConstants.opc_invokestatic;
newExprent.setMethodReference(true);
InvocationExprent constructor = newExprent.getConstructor();
if (instance && constructor.getLstParameters().size() == 1) {
constructor.setInstance(constructor.getLstParameters().get(0));
}

if (i > 0) {
Exprent instanceExp = newExprent.getConstructor().getInstance();
Exprent previous = stat.getExprents().get(i - 1);
if (instanceExp instanceof VarExprent varExp
&& previous instanceof AssignmentExprent assignment
&& varExp.equalsVersions(assignment.getLeft())
&& !varExp.isVarReferenced(stat.getTopParent(), (VarExprent) assignment.getLeft())) {
newExprent.getConstructor().setInstance(assignment.getRight());
newExprent.getConstructor().getLstParameters().set(0, assignment.getRight());
stat.getExprents().remove(i - 1);
}
}
return true;
}

private static boolean removeInstanceAssignment(Statement stat, int i, Exprent exp) {
if (exp instanceof NewExprent newExp
&& newExp.isLambda()
&& newExp.isMethodReference()
&& i >= 2
&& newExp.getConstructor().getInstance() instanceof VarExprent stackVar
&& stackVar.isStack()) {
Exprent nonNull = stat.getExprents().get(i - 1);
Exprent assign = stat.getExprents().get(i - 2);
if (nonNull instanceof InvocationExprent inv
&& inv.getClassname().equals("java/util/Objects")
&& inv.getName().equals("requireNonNull")
&& inv.getStringDescriptor().equals("(Ljava/lang/Object;)Ljava/lang/Object;")
&& stackVar.equalsVersions(inv.getLstParameters().get(0))
&& assign instanceof AssignmentExprent stackAssign
&& stackVar.equalsVersions(stackAssign.getLeft())) {
newExp.getConstructor().setInstance(stackAssign.getRight());
newExp.getConstructor().getLstParameters().set(0, stackAssign.getRight());
stat.getExprents().remove(i - 1);
stat.getExprents().remove(i - 2);
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,10 @@ public boolean isMethodReference() {
return methodReference;
}

public void setMethodReference(boolean methodReference) {
this.methodReference = methodReference;
}

public String getLambdaMethodKey() {
ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(newType.value);
if (node != null && constructor != null) {
Expand Down
2 changes: 2 additions & 0 deletions test/org/jetbrains/java/decompiler/SingleClassesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,8 @@ private void registerDefault() {
register(JAVA_8, "TestTryLoopSimpleFinally");
register(JAVA_8, "TestTryLoopReturnFinally");
register(JASM, "TestNumberCompareToBoolean");

register(JAVA_25, "TestMethodReferenceJ25");
}

private void registerEntireClassPath() {
Expand Down
40 changes: 4 additions & 36 deletions testData/results/pkg/TestGenericsQualified.dec
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

public class TestGenericsQualified {
public Comparator<String> field = Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(i -> i.toString());// 10
public Comparator<String> field = Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(String::toString);// 10
public CompletableFuture<String> field2 = CompletableFuture.<String>supplyAsync(() -> "").thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "2"));// 11
public Optional<String> field3 = Optional.of("").map(s -> s + "3");// 12
public Stream<String> field4 = Stream.of("1", "2").sorted(Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(i -> i.toString()));// 13 14
public Comparator<String> field5 = Comparator.comparing(String::length).thenComparing(i -> i.toString());// 15
public Stream<String> field4 = Stream.of("1", "2").sorted(Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(String::toString));// 13 14
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this output is wrong. In the original source, these are regular lambdas and not references.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would have to match the lambda that javac generates when converting a method reference to a lambda.

I could require the variable to exist but then it would only work for java 25.

public Comparator<String> field5 = Comparator.comparing(String::length).thenComparing(String::toString);// 15
public Comparator<TestGenericsQualified> field6 = Comparator.<TestGenericsQualified, Integer>comparing(TestGenericsQualified::get).reversed();// 16

public int get() {
Expand All @@ -22,7 +22,7 @@ public class TestGenericsQualified {
}

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

Expand Down Expand Up @@ -99,14 +99,6 @@ class 'pkg/TestGenericsQualified' {
7 8
}

method 'lambda$new$1 (Ljava/lang/String;)Ljava/lang/String;' {
0 8
1 8
2 8
3 8
4 8
}

method 'lambda$new$2 ()Ljava/lang/String;' {
0 9
1 9
Expand Down Expand Up @@ -151,22 +143,6 @@ class 'pkg/TestGenericsQualified' {
7 11
}

method 'lambda$new$7 (Ljava/lang/String;)Ljava/lang/String;' {
0 11
1 11
2 11
3 11
4 11
}

method 'lambda$new$8 (Ljava/lang/String;)Ljava/lang/String;' {
0 12
1 12
2 12
3 12
4 12
}

method 'get ()I' {
0 16
1 16
Expand Down Expand Up @@ -199,14 +175,6 @@ class 'pkg/TestGenericsQualified' {
6 24
7 24
}

method 'lambda$method$10 (Ljava/lang/String;)Ljava/lang/String;' {
0 24
1 24
2 24
3 24
4 24
}
}

Lines mapping:
Expand Down
Loading