fix: inline desugared lambda classes (#467)
This commit is contained in:
@@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -59,7 +58,6 @@ public final class JadxDecompiler {
|
||||
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
private CodeGen codeGen;
|
||||
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
@@ -97,7 +95,6 @@ public final class JadxDecompiler {
|
||||
|
||||
void init() {
|
||||
this.passes = Jadx.getPassesList(args);
|
||||
this.codeGen = new CodeGen();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
@@ -106,7 +103,6 @@ public final class JadxDecompiler {
|
||||
xmlParser = null;
|
||||
root = null;
|
||||
passes = null;
|
||||
codeGen = null;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
@@ -215,9 +211,11 @@ public final class JadxDecompiler {
|
||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
}
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
}
|
||||
@@ -289,7 +287,7 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
void processClass(ClassNode cls) {
|
||||
ProcessClass.process(cls, passes, codeGen);
|
||||
ProcessClass.process(cls, passes, true);
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
|
||||
@@ -2,8 +2,6 @@ package jadx.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
@@ -19,8 +17,8 @@ public final class ProcessClass {
|
||||
private ProcessClass() {
|
||||
}
|
||||
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
|
||||
if (codeGen == null && cls.getState() == PROCESSED) {
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
|
||||
if (!generateCode && cls.getState() == PROCESSED) {
|
||||
return;
|
||||
}
|
||||
synchronized (getSyncObj(cls)) {
|
||||
@@ -33,9 +31,9 @@ public final class ProcessClass {
|
||||
}
|
||||
cls.setState(PROCESSED);
|
||||
}
|
||||
if (cls.getState() == PROCESSED && codeGen != null) {
|
||||
if (cls.getState() == PROCESSED && generateCode) {
|
||||
processDependencies(cls, passes);
|
||||
codeGen.visit(cls);
|
||||
CodeGen.generate(cls);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||
@@ -48,6 +46,6 @@ public final class ProcessClass {
|
||||
}
|
||||
|
||||
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, null));
|
||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public class ClassGen {
|
||||
imports.clear();
|
||||
}
|
||||
clsCode.add(clsBody);
|
||||
return clsCode;
|
||||
return clsCode.finish();
|
||||
}
|
||||
|
||||
public void addClassCode(CodeWriter code) throws CodegenException {
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public class CodeGen {
|
||||
|
||||
public boolean visit(ClassNode cls) throws CodegenException {
|
||||
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
|
||||
CodeWriter clsCode = clsGen.makeClass();
|
||||
clsCode.finish();
|
||||
cls.setCode(clsCode);
|
||||
return false;
|
||||
public static void generate(ClassNode cls) throws CodegenException {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
cls.setCode(CodeWriter.EMPTY);
|
||||
} else {
|
||||
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
|
||||
cls.setCode(clsGen.makeClass());
|
||||
}
|
||||
}
|
||||
|
||||
private CodeGen() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ public class CodeWriter {
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT_STR = " ";
|
||||
|
||||
public static final CodeWriter EMPTY = new CodeWriter().finish();
|
||||
|
||||
private static final boolean ADD_LINE_NUMBERS = false;
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
@@ -250,7 +252,7 @@ public class CodeWriter {
|
||||
return lineMap;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
public CodeWriter finish() {
|
||||
removeFirstEmptyLine();
|
||||
buf.trimToSize();
|
||||
code = buf.toString();
|
||||
@@ -266,11 +268,12 @@ public class CodeWriter {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void removeFirstEmptyLine() {
|
||||
int len = NL.length();
|
||||
if (buf.substring(0, len).equals(NL)) {
|
||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
||||
buf.delete(0, len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -559,7 +558,7 @@ public class InsnGen {
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
||||
inlineAnonymousConstr(code, cls, insn);
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -577,20 +576,14 @@ public class InsnGen {
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
// anonymous class construction
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
code.add("/* anonymous class already generated */");
|
||||
ErrorsCounter.methodWarn(mth, "Anonymous class already generated: " + cls);
|
||||
return;
|
||||
}
|
||||
private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
MethodNode defCtr = cls.getDefaultConstructor();
|
||||
if (defCtr != null) {
|
||||
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
@@ -123,7 +124,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
accFlagsValue = cls.getAccessFlags();
|
||||
}
|
||||
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
|
||||
|
||||
markAnonymousClass(this);
|
||||
buildCache();
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
|
||||
@@ -394,6 +395,29 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
&& getDefaultConstructor() != null;
|
||||
}
|
||||
|
||||
public boolean isLambdaCls() {
|
||||
return accessFlags.isSynthetic() && accessFlags.isFinal()
|
||||
&& clsInfo.getType().getObject().contains(".-$$Lambda$")
|
||||
&& countStaticFields() == 0;
|
||||
}
|
||||
|
||||
private int countStaticFields() {
|
||||
int c = 0;
|
||||
for (FieldNode field : fields) {
|
||||
if (field.getAccessFlags().isStatic()) {
|
||||
c++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private static void markAnonymousClass(ClassNode cls) {
|
||||
if (cls.isAnonymous() || cls.isLambdaCls()) {
|
||||
cls.add(AFlag.ANONYMOUS_CLASS);
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode getClassInitMth() {
|
||||
return searchMethodByName("<clinit>()V");
|
||||
|
||||
@@ -54,40 +54,46 @@ public class ClassModifier extends AbstractVisitor {
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
return false;
|
||||
}
|
||||
removeSyntheticFields(cls);
|
||||
cls.getMethods().forEach(mth -> removeSyntheticMethods(cls, mth));
|
||||
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
|
||||
|
||||
markAnonymousClass(cls);
|
||||
removeSyntheticFields(cls);
|
||||
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
|
||||
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void markAnonymousClass(ClassNode cls) {
|
||||
if (cls.isAnonymous()) {
|
||||
cls.add(AFlag.ANONYMOUS_CLASS);
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove synthetic fields if type is outer class or class will be inlined (anonymous)
|
||||
*/
|
||||
private static void removeSyntheticFields(ClassNode cls) {
|
||||
if (!cls.getClassInfo().isInner() || cls.getAccessFlags().isStatic()) {
|
||||
if (cls.getAccessFlags().isStatic()) {
|
||||
return;
|
||||
}
|
||||
// remove fields if it is synthetic and type is a outer class
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
|
||||
ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType());
|
||||
ClassNode fieldsCls = cls.dex().resolveClass(clsInfo);
|
||||
ClassInfo parentClass = cls.getClassInfo().getParentClass();
|
||||
if (fieldsCls != null && parentClass.equals(fieldsCls.getClassInfo())) {
|
||||
int found = 0;
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) {
|
||||
found++;
|
||||
boolean inline = cls.contains(AFlag.ANONYMOUS_CLASS);
|
||||
if (inline || cls.getClassInfo().isInner()) {
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
|
||||
ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType());
|
||||
ClassNode fieldsCls = cls.dex().resolveClass(clsInfo);
|
||||
ClassInfo parentClass = cls.getClassInfo().getParentClass();
|
||||
if (fieldsCls != null
|
||||
&& (inline || parentClass.equals(fieldsCls.getClassInfo()))) {
|
||||
int found = 0;
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) {
|
||||
found++;
|
||||
}
|
||||
}
|
||||
if (found != 0) {
|
||||
field.addAttr(new FieldReplaceAttr(fieldsCls.getClassInfo()));
|
||||
field.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
if (found != 0) {
|
||||
field.addAttr(new FieldReplaceAttr(parentClass));
|
||||
field.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +139,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void removeSyntheticMethods(ClassNode cls, MethodNode mth) {
|
||||
private static void removeSyntheticMethods(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
@@ -141,6 +147,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
if (!af.isSynthetic()) {
|
||||
return;
|
||||
}
|
||||
ClassNode cls = mth.getParentClass();
|
||||
if (removeBridgeMethod(cls, mth)) {
|
||||
if (Consts.DEBUG) {
|
||||
mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method");
|
||||
|
||||
@@ -11,6 +11,7 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
@@ -90,6 +91,9 @@ public class DependencyCollector extends AbstractVisitor {
|
||||
} else if (insn instanceof InvokeNode) {
|
||||
ClassInfo declClass = ((InvokeNode) insn).getCallMth().getDeclClass();
|
||||
addDep(dex, depList, declClass);
|
||||
} else if (insn instanceof ConstructorInsn) {
|
||||
ClassInfo declClass = ((ConstructorInsn) insn).getCallMth().getDeclClass();
|
||||
addDep(dex, depList, declClass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
@@ -136,7 +138,10 @@ public class RegionUtils {
|
||||
return !notEmpty(container);
|
||||
}
|
||||
|
||||
public static boolean notEmpty(IContainer container) {
|
||||
public static boolean notEmpty(@Nullable IContainer container) {
|
||||
if (container == null) {
|
||||
return false;
|
||||
}
|
||||
if (container instanceof IBlock) {
|
||||
return !((IBlock) container).getInstructions().isEmpty();
|
||||
} else if (container instanceof IRegion) {
|
||||
|
||||
@@ -139,7 +139,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
protected void decompile(JadxDecompiler jadx, ClassNode cls) {
|
||||
List<IDexTreeVisitor> passes = getPassesList(jadx);
|
||||
ProcessClass.process(cls, passes, new CodeGen());
|
||||
ProcessClass.process(cls, passes, true);
|
||||
}
|
||||
|
||||
protected void decompileWithoutUnload(JadxDecompiler jadx, ClassNode cls) {
|
||||
@@ -168,7 +168,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
protected void generateClsCode(ClassNode cls) {
|
||||
try {
|
||||
new CodeGen().visit(cls);
|
||||
CodeGen.generate(cls);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage());
|
||||
|
||||
@@ -15,12 +15,11 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
@@ -77,7 +76,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
||||
for (ClassNode classNode : root.getClasses(true)) {
|
||||
String clsFullName = classNode.getClassInfo().getFullName();
|
||||
if (clsPattern.matcher(clsFullName).matches()) {
|
||||
if (processCls(mthPattern, passes, classNode)) {
|
||||
if (processCls(jadx, mthPattern, passes, classNode)) {
|
||||
processed++;
|
||||
}
|
||||
}
|
||||
@@ -85,7 +84,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
||||
assertThat("No classes processed", processed, greaterThan(0));
|
||||
}
|
||||
|
||||
private boolean processCls(@Nullable Pattern mthPattern, List<IDexTreeVisitor> passes, ClassNode classNode) {
|
||||
private boolean processCls(JadxDecompiler jadx, @Nullable Pattern mthPattern, List<IDexTreeVisitor> passes, ClassNode classNode) {
|
||||
classNode.load();
|
||||
boolean decompile = false;
|
||||
if (mthPattern == null) {
|
||||
@@ -101,11 +100,8 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
||||
if (!decompile) {
|
||||
return false;
|
||||
}
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
DepthTraversal.visit(visitor, classNode);
|
||||
}
|
||||
try {
|
||||
new CodeGen().visit(classNode);
|
||||
ProcessClass.process(classNode, passes, true);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Codegen failed", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user