Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct synthetic parameter handling in inner classes #98

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions FernFlower-Patches/0029-Improve-inferred-generic-types.patch
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ index 8f69e433dce5541e5f42693c67fbb9f8610821d2..1f27ed6c5ab25460b0e67c728c7fdcaa
public void getBytecodeRange(BitSet values) {
measureBytecode(values, lstOperands);
diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
index ebb845dff674965ff3e0e8011db33dae5270e24d..a02c40a8a8a2ad1a1888d8a5370625c29c858e27 100644
index ebb845dff674965ff3e0e8011db33dae5270e24d..81a2b58f169819e4d320265dda487a4843f2c581 100644
--- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
+++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
@@ -52,6 +52,7 @@ public class InvocationExprent extends Exprent {
Expand Down Expand Up @@ -303,7 +303,7 @@ index ebb845dff674965ff3e0e8011db33dae5270e24d..a02c40a8a8a2ad1a1888d8a5370625c2
+ StructClass mthCls = DecompilerContext.getStructContext().getClass(classname);
+
+ if (desc != null && mthCls != null) {
+ boolean isNew = functype == TYP_INIT && mthCls.getSignature() != null;
+ boolean isNew = functype == TYP_INIT;
+ boolean isGenNew = isNew && mthCls.getSignature() != null;
+ if (desc.getSignature() != null || isGenNew) {
+ Map<VarType, List<VarType>> named = getNamedGenerics();
Expand Down Expand Up @@ -463,7 +463,7 @@ index ebb845dff674965ff3e0e8011db33dae5270e24d..a02c40a8a8a2ad1a1888d8a5370625c2
+
+ int j = 0;
+ for (int i = start; i < lstParameters.size(); ++i) {
+ if (mask == null || mask.get(i) != null) {
+ if (mask == null || mask.get(i) == null) {
Copy link
Member

@LexManos LexManos May 28, 2021

Choose a reason for hiding this comment

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

Huh.. wonder how this ever worked. Also probably need to change isNew to isGenNew a few lines above where the mask is set so that we can make sure desc.getSignature is not null. Err.. nevermind its inside a desc.getSignature() != null if. But that should probably be changed for proper fix.

+ VarType paramType = desc.getSignature().parameterTypes.get(j++);
+ if (paramType.isGeneric()) {
+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ index 1232e643fae990a7a3a9ee5f2e2ece46e607f934..1a085709ed7a120d109542e4bf710101
}
\ No newline at end of file
diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
index a02c40a8a8a2ad1a1888d8a5370625c29c858e27..44927da60f640d940d0ccb6c52d8e4eba42ed2aa 100644
index 81a2b58f169819e4d320265dda487a4843f2c581..7b23c31e8989f86ad7245ef508d4e90762274367 100644
--- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
+++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
@@ -166,6 +166,11 @@ public class InvocationExprent extends Exprent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Subject: [PATCH] Add explicit cast to invocations of java/nio/Buffer
Java 9+ added overrides to these functions to return the specific subclass, however, when there is a compiler "bug" that when targeting release * or below, it will still reference these new methods, causing exceptions at runtime on Java 8.

diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
index 44927da60f640d940d0ccb6c52d8e4eba42ed2aa..74e4440c8cb062db041ca853b7292196e89b106f 100644
index 7b23c31e8989f86ad7245ef508d4e90762274367..a55e37e7ecc6d11c18d692434d935ecf436282ab 100644
--- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
+++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
@@ -46,6 +46,8 @@ public class InvocationExprent extends Exprent {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: zml <zml@stellardrift.ca>
Date: Thu, 13 May 2021 23:55:33 -0700
Subject: [PATCH] More correctly handle inner class synthetic parameters


diff --git a/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java b/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java
index fe372662be3fbd310f94831b47f21b92033a851c..f50e767ab81de739cad2059b5a8279fcd7cdba25 100644
--- a/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java
+++ b/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java
@@ -35,6 +35,8 @@ import java.util.*;
Copy link
Member

@LexManos LexManos May 28, 2021

Choose a reason for hiding this comment

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

I've tested this and it does not seem to effect decompiled code at all. So i'm gunna split this out and we can discuss it in more depth later.

import java.util.Map.Entry;

public class NestedClassProcessor {
+ public static final String ENUM_SYNTHETIC_SUPER = "<synthetic_super>";
+
public void processClass(ClassNode root, ClassNode node) {
// hide synthetic lambda content methods
if (node.type == ClassNode.CLASS_LAMBDA && !node.lambdaInformation.is_method_reference) {
@@ -386,7 +388,6 @@ public class NestedClassProcessor {
for (ClassNode nd : node.nested) {
if (nd.type != ClassNode.CLASS_LAMBDA &&
!nd.classStruct.isSynthetic() &&
- (nd.access & CodeConstants.ACC_STATIC) == 0 &&
(nd.access & CodeConstants.ACC_INTERFACE) == 0) {
clTypes |= nd.type;

@@ -503,23 +504,12 @@ public class NestedClassProcessor {

if (interPairMask == null) { // member or local and never instantiated
interPairMask = interMask != null ? new ArrayList<>(interMask) : new ArrayList<>();
-
- boolean found = false;
-
- for (int i = 0; i < interPairMask.size(); i++) {
- if (interPairMask.get(i) != null) {
- if (found) {
- interPairMask.set(i, null);
- }
- found = true;
- }
- }
+ } else {
+ mergeListSignatures(interPairMask, interMask, true);
}

- mergeListSignatures(interPairMask, interMask, true);
-
for (VarFieldPair pair : interPairMask) {
- if (pair != null && !pair.fieldKey.isEmpty()) {
+ if (pair != null && !pair.fieldKey.isEmpty() && !NestedClassProcessor.ENUM_SYNTHETIC_SUPER.equals(pair.fieldKey)) {
nestedNode.mapFieldsToVars.put(pair.fieldKey, pair.varPair);
}
}
@@ -764,7 +754,14 @@ public class NestedClassProcessor {
List<VarFieldPair> fields = new ArrayList<>(md.params.length);

int varIndex = 1;
- for (int i = 0; i < md.params.length; i++) { // no static methods allowed
+ int start = 0;
+ if ((wrapper.getClassStruct().getAccessFlags() & CodeConstants.ACC_ENUM) != 0) { // enums have 2x synthetic params. not handled by normal detection because they go to the superclass constructor rather than to synthetic fields
+ fields.add(new VarFieldPair(NestedClassProcessor.ENUM_SYNTHETIC_SUPER, new VarVersionPair(-1, 0)));
+ fields.add(new VarFieldPair(NestedClassProcessor.ENUM_SYNTHETIC_SUPER, new VarVersionPair(-1, 0)));
+ varIndex += 2;
+ start += 2;
+ }
+ for (int i = start; i < md.params.length; i++) { // no static methods allowed
String keyField = getEnclosingVarField(cl, method, graph, varIndex);
fields.add(keyField == null ? null : new VarFieldPair(keyField, new VarVersionPair(-1, 0))); // TODO: null?
varIndex += md.params[i].stackSize;
@@ -819,17 +816,31 @@ public class NestedClassProcessor {
return fd.getName().contains("$") && fd.hasModifier(CodeConstants.ACC_FINAL) && fd.hasModifier(CodeConstants.ACC_PRIVATE);
}

+ /**
+ * Merge two sets of method signatures.
+ *
+ * This ensures that synthetic parameters are properly captured when field writes may go through another constructor.
+ *
+ * @param first the list to modify
+ * @param second the list compared against
+ * @param both if the comparison list should also be modified to align
+ */
private static void mergeListSignatures(List<VarFieldPair> first, List<VarFieldPair> second, boolean both) {
- int i = 1;
+ // Upstream this merges from the end of the list forwards
+ // However, synthetic parameters are added at the beginnings of method signatures (at least usually)
+ // so we really want to start from the beginning
+ // if it ends up that this assumption is not true, we'd have to do some sort of fuzzy matching here, or earlier
+ // on traverse multiple steps in the graph in getEnclosingVarField
Copy link
Member

Choose a reason for hiding this comment

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

As discussed in Discord, this assumption is incorrect.
Would need a deeper dive into the compiler to find all cases where synthetics are added to the beginning or end.

+ int i = 0;

- while (first.size() > i && second.size() > i) {
- VarFieldPair fObj = first.get(first.size() - i);
- VarFieldPair sObj = second.get(second.size() - i);
+ while (i < first.size() && i < second.size()) {
+ VarFieldPair fObj = first.get(i);
+ VarFieldPair sObj = second.get(i);

if (!isEqual(both, fObj, sObj)) {
- first.set(first.size() - i, null);
+ first.set(i, null);
if (both) {
- second.set(second.size() - i, null);
+ second.set(i, null);
}
}
else if (fObj != null) {
@@ -844,44 +855,15 @@ public class NestedClassProcessor {
i++;
}

- for (int j = 1; j <= first.size() - i; j++) {
+ for (int j = i; j < first.size(); j++) {
first.set(j, null);
}

if (both) {
- for (int j = 1; j <= second.size() - i; j++) {
+ for (int j = i; j < second.size(); j++) {
second.set(j, null);
}
}
-
- // first
- if (first.isEmpty()) {
- if (!second.isEmpty() && both) {
- second.set(0, null);
- }
- }
- else if (second.isEmpty()) {
- first.set(0, null);
- }
- else {
- VarFieldPair fObj = first.get(0);
- VarFieldPair sObj = second.get(0);
-
- if (!isEqual(both, fObj, sObj)) {
- first.set(0, null);
- if (both) {
- second.set(0, null);
- }
- }
- else if (fObj != null) {
- if (fObj.varPair.var == -1) {
- fObj.varPair = sObj.varPair;
- }
- else {
- sObj.varPair = fObj.varPair;
- }
- }
- }
}

private static boolean isEqual(boolean both, VarFieldPair fObj, VarFieldPair sObj) {