Skip to content

Convert lambdas to method references when javac compiles a method reference to a lambda#532

Open
coehlrich wants to merge 4 commits into
Vineflower:develop/1.12.0from
coehlrich:inline-instance-variable
Open

Convert lambdas to method references when javac compiles a method reference to a lambda#532
coehlrich wants to merge 4 commits into
Vineflower:develop/1.12.0from
coehlrich:inline-instance-variable

Conversation

@coehlrich
Copy link
Copy Markdown
Contributor

Converts lambdas to method references when javac compiles a method reference to a lambda, removes synthetic instance variable, and removes Objects.requireNonNull.

Copy link
Copy Markdown
Member

@sschr15 sschr15 left a comment

Choose a reason for hiding this comment

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

I have some small nitpicks, but overall it looks good to me.

manual bytecode parsing is scary

&& 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

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()?

}

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


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

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

@sschr15 sschr15 requested a review from jaskarth December 20, 2025 00:51
Copy link
Copy Markdown
Member

@jaskarth jaskarth left a comment

Choose a reason for hiding this comment

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

Thanks a lot for working on this! Overall, I don't really understand the approach taken here. I'll look at the generated code and see if there might be a better way.

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.

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.

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.

@Toffikk
Copy link
Copy Markdown

Toffikk commented Apr 24, 2026

Any updates on this PR? As the last comment is from over 4 months ago and this still doesn’t have any assigned priority and well, the changes made here are somewhat crucial for proper decompilation of bytecode emitted by java 25+

@jaskarth jaskarth added Type: Enhancement New feature or request Priority: Medium Medium priority Subsystem: Variables Anything concerning variables, types, assignments, and casting labels Apr 26, 2026
@jaskarth
Copy link
Copy Markdown
Member

The fix for the synthetic variable inlining has been pushed separately to develop/1.12. The fix for converting lambdas to method references needs further thought on coming up with a fix that doesn't impact non-reference lambdas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: Medium Medium priority Subsystem: Variables Anything concerning variables, types, assignments, and casting Type: Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants