Commit 7654661b authored by Skylot's avatar Skylot

fix: inline desugared lambda classes (#467)

parent 51a9c741
......@@ -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 {
......
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;
}
markAnonymousClass(cls);
removeSyntheticFields(cls);
cls.getMethods().forEach(mth -> removeSyntheticMethods(cls, mth));
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
markAnonymousClass(cls);
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);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment