Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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,267 @@
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
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.

It's probably reasonable to flip this if to reduce a layer of nesting

&& newExprent.isLambda()
&& !newExprent.isMethodReference()) {
if (stat.getTopParent().mt.getName().contains("<init>")) {
System.out.println();
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.

Debug line

}
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);
FullInstructionSequence seq = method.getInstructionSequence();
Iterator<Instruction> iterator = seq.iterator();
Instruction next = null;
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.

Unnecessary initializer

int argument = 0;

if (!iterator.hasNext())
return false;
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++;
}

if (next == null)
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 should never be true (FullInstructionSequence seems to only ever have non-null instructions). Did you mean !iterator.hasNext()?

return false;
boolean varargs = false;
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;
}
return false;
}

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