Commit 2a60ac47 authored by Skylot's avatar Skylot

core: annotate generated code with reference to used methods

parent 9cd72fe1
package jadx.api;
public final class CodePosition {
private final JavaClass cls;
private final int line;
private final int offset;
public CodePosition(JavaClass cls, int line, int offset) {
this.cls = cls;
this.line = line;
this.offset = offset;
}
public CodePosition(int line, int offset) {
this.cls = null;
this.line = line;
this.offset = offset;
}
public JavaClass getJavaClass() {
return cls;
}
public int getLine() {
return line;
}
public int getOffset() {
return offset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CodePosition that = (CodePosition) o;
return line == that.line && offset == that.offset;
}
@Override
public int hashCode() {
return line + 31 * offset;
}
@Override
public String toString() {
return line + ":" + offset + (cls != null ? " " + cls : "");
}
}
...@@ -2,6 +2,7 @@ package jadx.api; ...@@ -2,6 +2,7 @@ package jadx.api;
import jadx.core.codegen.CodeWriter; import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeFlag; import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
...@@ -12,6 +13,7 @@ import java.util.ArrayList; ...@@ -12,6 +13,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
public final class JavaClass { public final class JavaClass {
...@@ -27,12 +29,23 @@ public final class JavaClass { ...@@ -27,12 +29,23 @@ public final class JavaClass {
this.cls = classNode; this.cls = classNode;
} }
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompile();
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
}
public void decompile() { public void decompile() {
if (decompiler == null) { if (decompiler == null) {
throw new JadxRuntimeException("Can't decompile inner class"); throw new JadxRuntimeException("Can't decompile inner class");
} }
decompiler.processClass(cls); if (cls.getCode() == null) {
load(); decompiler.processClass(cls);
load();
}
} }
private void load() { private void load() {
...@@ -78,13 +91,32 @@ public final class JavaClass { ...@@ -78,13 +91,32 @@ public final class JavaClass {
} }
} }
public String getCode() { private Map<CodePosition, Object> getCodeAnnotations() {
CodeWriter code = cls.getCode(); getCode();
if (code == null) { return cls.getCode().getAnnotations();
decompile(); }
code = cls.getCode();
public CodePosition getDefinitionPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
Object obj = map.get(new CodePosition(line, offset));
if (obj instanceof LineAttrNode) {
ClassNode clsNode = null;
if (obj instanceof ClassNode) {
clsNode = (ClassNode) obj;
} else if (obj instanceof MethodNode) {
clsNode = ((MethodNode) obj).getParentClass();
} else if (obj instanceof FieldNode) {
clsNode = ((FieldNode) obj).getParentClass();
}
if (clsNode != null) {
clsNode = clsNode.getParentClass();
JavaClass jCls = new JavaClass(decompiler, clsNode);
jCls.decompile();
int defLine = ((LineAttrNode) obj).getDecompiledLine();
return new CodePosition(jCls, defLine, 0);
}
} }
return code != null ? code.toString() : "error processing class"; return null;
} }
public String getFullName() { public String getFullName() {
...@@ -115,12 +147,22 @@ public final class JavaClass { ...@@ -115,12 +147,22 @@ public final class JavaClass {
return methods; return methods;
} }
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override @Override
public String toString() { public boolean equals(Object o) {
return getFullName(); return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
} }
public int getDecompiledLine() { @Override
return cls.getDecompiledLine(); public int hashCode() {
return cls.hashCode();
}
@Override
public String toString() {
return getFullName();
} }
} }
...@@ -12,6 +12,7 @@ import jadx.core.dex.visitors.FallbackModeVisitor; ...@@ -12,6 +12,7 @@ import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor; import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor; import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.SimplifyVisitor; import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.ProcessVariables; import jadx.core.dex.visitors.regions.ProcessVariables;
...@@ -82,6 +83,7 @@ public class Jadx { ...@@ -82,6 +83,7 @@ public class Jadx {
passes.add(new MethodInlineVisitor()); passes.add(new MethodInlineVisitor());
passes.add(new ClassModifier()); passes.add(new ClassModifier());
passes.add(new PrepareForCodeGen());
} }
passes.add(new CodeGen(args)); passes.add(new CodeGen(args));
return passes; return passes;
......
...@@ -47,7 +47,7 @@ public class AnnotationGen { ...@@ -47,7 +47,7 @@ public class AnnotationGen {
return; return;
} }
for (Annotation a : aList.getAll()) { for (Annotation a : aList.getAll()) {
code.add(formatAnnotation(a)); formatAnnotation(code, a);
code.add(' '); code.add(' ');
} }
} }
...@@ -66,26 +66,25 @@ public class AnnotationGen { ...@@ -66,26 +66,25 @@ public class AnnotationGen {
} }
} else { } else {
code.startLine(); code.startLine();
code.add(formatAnnotation(a)); formatAnnotation(code, a);
} }
} }
} }
private CodeWriter formatAnnotation(Annotation a) { private void formatAnnotation(CodeWriter code, Annotation a) {
CodeWriter code = new CodeWriter();
code.add('@'); code.add('@');
code.add(classGen.useClass(a.getType())); code.add(classGen.useClass(a.getType()));
Map<String, Object> vl = a.getValues(); Map<String, Object> vl = a.getValues();
if (!vl.isEmpty()) { if (!vl.isEmpty()) {
code.add('('); code.add('(');
if (vl.size() == 1 && vl.containsKey("value")) { if (vl.size() == 1 && vl.containsKey("value")) {
code.add(encValueToString(vl.get("value"))); encodeValue(code, vl.get("value"));
} else { } else {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) { for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
Entry<String, Object> e = it.next(); Entry<String, Object> e = it.next();
code.add(e.getKey()); code.add(e.getKey());
code.add(" = "); code.add(" = ");
code.add(encValueToString(e.getValue())); encodeValue(code, e.getValue());
if (it.hasNext()) { if (it.hasNext()) {
code.add(", "); code.add(", ");
} }
...@@ -93,7 +92,6 @@ public class AnnotationGen { ...@@ -93,7 +92,6 @@ public class AnnotationGen {
} }
code.add(')'); code.add(')');
} }
return code;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -122,70 +120,52 @@ public class AnnotationGen { ...@@ -122,70 +120,52 @@ public class AnnotationGen {
} }
// TODO: refactor this boilerplate code // TODO: refactor this boilerplate code
@SuppressWarnings("unchecked") public void encodeValue(CodeWriter code, Object val) {
public String encValueToString(Object val) {
if (val == null) { if (val == null) {
return "null"; code.add("null");
return;
} }
if (val instanceof String) { if (val instanceof String) {
return StringUtils.unescapeString((String) val); code.add(StringUtils.unescapeString((String) val));
} } else if (val instanceof Integer) {
if (val instanceof Integer) { code.add(TypeGen.formatInteger((Integer) val));
return TypeGen.formatInteger((Integer) val); } else if (val instanceof Character) {
} code.add(StringUtils.unescapeChar((Character) val));
if (val instanceof Character) { } else if (val instanceof Boolean) {
return StringUtils.unescapeChar((Character) val); code.add(Boolean.TRUE.equals(val) ? "true" : "false");
} } else if (val instanceof Float) {
if (val instanceof Boolean) { code.add(TypeGen.formatFloat((Float) val));
return Boolean.TRUE.equals(val) ? "true" : "false"; } else if (val instanceof Double) {
} code.add(TypeGen.formatDouble((Double) val));
if (val instanceof Float) { } else if (val instanceof Long) {
return TypeGen.formatFloat((Float) val); code.add(TypeGen.formatLong((Long) val));
} } else if (val instanceof Short) {
if (val instanceof Double) { code.add(TypeGen.formatShort((Short) val));
return TypeGen.formatDouble((Double) val); } else if (val instanceof Byte) {
} code.add(TypeGen.formatByte((Byte) val));
if (val instanceof Long) { } else if (val instanceof ArgType) {
return TypeGen.formatLong((Long) val); code.add(TypeGen.translate(classGen, (ArgType) val)).add(".class");
} } else if (val instanceof FieldInfo) {
if (val instanceof Short) {
return TypeGen.formatShort((Short) val);
}
if (val instanceof Byte) {
return TypeGen.formatByte((Byte) val);
}
if (val instanceof ArgType) {
return TypeGen.translate(classGen, (ArgType) val) + ".class";
}
if (val instanceof FieldInfo) {
// must be a static field // must be a static field
FieldInfo field = (FieldInfo) val; FieldInfo field = (FieldInfo) val;
// FIXME: !!code from InsnGen.sfield code.add(InsnGen.makeStaticFieldAccess(field, classGen));
String thisClass = cls.getFullName(); } else if (val instanceof List) {
if (field.getDeclClass().getFullName().equals(thisClass)) { code.add('{');
return field.getName(); List list = (List) val;
} else { Iterator it = list.iterator();
return classGen.useClass(field.getDeclClass()) + '.' + field.getName(); while (it.hasNext()) {
}
}
if (val instanceof List) {
StringBuilder str = new StringBuilder();
str.append('{');
List<Object> list = (List<Object>) val;
for (Iterator<Object> it = list.iterator(); it.hasNext(); ) {
Object obj = it.next(); Object obj = it.next();
str.append(encValueToString(obj)); encodeValue(code, obj);
if (it.hasNext()) { if (it.hasNext()) {
str.append(", "); code.add(", ");
} }
} }
str.append('}'); code.add('}');
return str.toString(); } else if (val instanceof Annotation) {
} formatAnnotation(code, (Annotation) val);
if (val instanceof Annotation) { } else {
return formatAnnotation((Annotation) val).toString(); // TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
} }
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
} }
} }
...@@ -151,7 +151,7 @@ public class ClassGen { ...@@ -151,7 +151,7 @@ public class ClassGen {
} }
} }
clsCode.attachAnnotation(cls); clsCode.attachDefinition(cls);
} }
public boolean makeGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) { public boolean makeGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
...@@ -228,8 +228,8 @@ public class ClassGen { ...@@ -228,8 +228,8 @@ public class ClassGen {
if (cls.getAccessFlags().isAnnotation()) { if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName()); Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) { if (def != null) {
String v = annotationGen.encValueToString(def); code.add(" default ");
code.add(" default ").add(v); annotationGen.encodeValue(code, def);
} }
} }
code.add(';'); code.add(';');
...@@ -282,11 +282,11 @@ public class ClassGen { ...@@ -282,11 +282,11 @@ public class ClassGen {
if (fv.getValue() == null) { if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType())); code.add(TypeGen.literalToString(0, f.getType()));
} else { } else {
code.add(annotationGen.encValueToString(fv.getValue())); annotationGen.encodeValue(code, fv.getValue());
} }
} }
code.add(';'); code.add(';');
code.attachAnnotation(f); code.attachDefinition(f);
} }
return code; return code;
} }
......
package jadx.core.codegen; package jadx.core.codegen;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.LineAttrNode; import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
...@@ -7,6 +8,7 @@ import java.io.File; ...@@ -7,6 +8,7 @@ import java.io.File;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -17,7 +19,7 @@ public class CodeWriter { ...@@ -17,7 +19,7 @@ public class CodeWriter {
private static final int MAX_FILENAME_LENGTH = 128; private static final int MAX_FILENAME_LENGTH = 128;
public static final String NL = System.getProperty("line.separator"); public static final String NL = System.getProperty("line.separator");
public static final String INDENT = "\t"; public static final String INDENT = " ";
private static final String[] INDENT_CACHE = { private static final String[] INDENT_CACHE = {
"", "",
...@@ -33,7 +35,8 @@ public class CodeWriter { ...@@ -33,7 +35,8 @@ public class CodeWriter {
private int indent; private int indent;
private int line = 1; private int line = 1;
private Map<Object, Integer> annotations = Collections.emptyMap(); private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
public CodeWriter() { public CodeWriter() {
this.indent = 0; this.indent = 0;
...@@ -47,56 +50,66 @@ public class CodeWriter { ...@@ -47,56 +50,66 @@ public class CodeWriter {
public CodeWriter startLine() { public CodeWriter startLine() {
addLine(); addLine();
buf.append(indentStr); addIndent();
return this; return this;
} }
public CodeWriter startLine(char c) { public CodeWriter startLine(char c) {
addLine(); addLine();
buf.append(indentStr); addIndent();
buf.append(c); add(c);
return this; return this;
} }
public CodeWriter startLine(String str) { public CodeWriter startLine(String str) {
addLine(); addLine();
buf.append(indentStr); addIndent();
buf.append(str); add(str);
return this; return this;
} }
public CodeWriter startLine(int ind, String str) { public CodeWriter startLine(int ind, String str) {
addLine(); addLine();
buf.append(indentStr); addIndent();
for (int i = 0; i < ind; i++) { for (int i = 0; i < ind; i++) {
buf.append(INDENT); addIndent();
} }
buf.append(str); add(str);
return this; return this;
} }
public CodeWriter add(Object obj) { public CodeWriter add(Object obj) {
buf.append(obj); add(obj.toString());
return this; return this;
} }
public CodeWriter add(String str) { public CodeWriter add(String str) {
buf.append(str); buf.append(str);
offset += str.length();
return this; return this;
} }
public CodeWriter add(char c) { public CodeWriter add(char c) {
buf.append(c); buf.append(c);
offset++;
return this; return this;
} }
@Deprecated
public CodeWriter add(CodeWriter code) { public CodeWriter add(CodeWriter code) {
line--; line--;
for (Map.Entry<Object, Integer> entry : code.annotations.entrySet()) { for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
attachAnnotation(entry.getKey(), line + entry.getValue()); CodePosition pos = entry.getKey();
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
} }
line += code.line; line += code.line;
buf.append(code); String str = code.toString();
buf.append(str);
if (str.contains(NL)) {
offset = code.offset;
} else {
offset += code.offset;
}
return this; return this;
} }
...@@ -108,25 +121,12 @@ public class CodeWriter { ...@@ -108,25 +121,12 @@ public class CodeWriter {
private void addLine() { private void addLine() {
buf.append(NL); buf.append(NL);
line++; line++;
offset = 0;
} }
public int getLine() { public CodeWriter addIndent() {
return line;
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, line);
}
public Object attachAnnotation(Object obj, int line) {
if (annotations.isEmpty()) {
annotations = new HashMap<Object, Integer>();
}
return annotations.put(obj, line);
}
public CodeWriter indent() {
buf.append(indentStr); buf.append(indentStr);
offset += indentStr.length();
return this; return this;
} }
...@@ -169,16 +169,49 @@ public class CodeWriter { ...@@ -169,16 +169,49 @@ public class CodeWriter {
updateIndent(); updateIndent();
} }
private static class DefinitionWrapper {
private final LineAttrNode node;
private DefinitionWrapper(LineAttrNode node) {
this.node = node;
}
public LineAttrNode getNode() {
return node;
}
}
public Object attachDefinition(LineAttrNode obj) {
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, new CodePosition(line, offset + 1));
}
private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<CodePosition, Object>();
}
return annotations.put(pos, obj);
}
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
public void finish() { public void finish() {
buf.trimToSize(); buf.trimToSize();
for (Map.Entry<Object, Integer> entry : annotations.entrySet()) { Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
Object v = entry.getKey(); while (it.hasNext()) {
if (v instanceof LineAttrNode) { Map.Entry<CodePosition, Object> entry = it.next();
LineAttrNode l = (LineAttrNode) v; Object v = entry.getValue();
l.setDecompiledLine(entry.getValue()); if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
it.remove();
} }
} }
annotations.clear();
} }
private static String removeFirstEmptyLine(String str) { private static String removeFirstEmptyLine(String str) {
......
...@@ -16,6 +16,7 @@ import jadx.core.dex.instructions.FillArrayNode; ...@@ -16,6 +16,7 @@ import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.GotoNode; import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType; import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.SwitchNode; import jadx.core.dex.instructions.SwitchNode;
...@@ -38,6 +39,7 @@ import jadx.core.utils.InsnUtils; ...@@ -38,6 +39,7 @@ import jadx.core.utils.InsnUtils;
import jadx.core.utils.RegionUtils; import jadx.core.utils.RegionUtils;
import jadx.core.utils.StringUtils; import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
...@@ -57,12 +59,7 @@ public class InsnGen { ...@@ -57,12 +59,7 @@ public class InsnGen {
protected final RootNode root; protected final RootNode root;
private final boolean fallback; private final boolean fallback;
private static enum IGState { private static enum Flags {
SKIP,
NO_SEMICOLON,
NO_RESULT,
BODY_ONLY, BODY_ONLY,
BODY_ONLY_NOWRAP, BODY_ONLY_NOWRAP,
} }
...@@ -113,7 +110,7 @@ public class InsnGen { ...@@ -113,7 +110,7 @@ public class InsnGen {
} else if (arg.isLiteral()) { } else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg)); code.add(lit((LiteralArg) arg));
} else if (arg.isInsnWrap()) { } else if (arg.isInsnWrap()) {
IGState flag = wrap ? IGState.BODY_ONLY : IGState.BODY_ONLY_NOWRAP; Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag); makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
} else if (arg.isNamed()) { } else if (arg.isNamed()) {
code.add(((NamedArg) arg).getName()); code.add(((NamedArg) arg).getName());
...@@ -164,18 +161,17 @@ public class InsnGen { ...@@ -164,18 +161,17 @@ public class InsnGen {
code.add(field.getName()); code.add(field.getName());
} }
protected String staticField(FieldInfo field) { public static String makeStaticFieldAccess(FieldInfo field, ClassGen clsGen) {
String thisClass = mth.getParentClass().getFullName();
ClassInfo declClass = field.getDeclClass(); ClassInfo declClass = field.getDeclClass();
if (thisClass.startsWith(declClass.getFullName())) { if (clsGen.getClassNode().getFullName().startsWith(declClass.getFullName())) {
return field.getName(); return field.getName();
} }
// Android specific resources class handler // Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass(); ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) { if (parentClass != null && parentClass.getShortName().equals("R")) {
return useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName(); return clsGen.useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
} }
return useClass(declClass) + '.' + field.getName(); return clsGen.useClass(declClass) + '.' + field.getName();
} }
private void fieldPut(IndexInsnNode insn) { private void fieldPut(IndexInsnNode insn) {
...@@ -190,6 +186,10 @@ public class InsnGen { ...@@ -190,6 +186,10 @@ public class InsnGen {
} }
} }
protected String staticField(FieldInfo field) {
return makeStaticFieldAccess(field, mgen.getClassGen());
}
public String useClass(ClassInfo cls) { public String useClass(ClassInfo cls) {
return mgen.getClassGen().useClass(cls); return mgen.getClassGen().useClass(cls);
} }
...@@ -202,31 +202,25 @@ public class InsnGen { ...@@ -202,31 +202,25 @@ public class InsnGen {
return makeInsn(insn, code, null); return makeInsn(insn, code, null);
} }
private boolean makeInsn(InsnNode insn, CodeWriter code, IGState flag) throws CodegenException { private boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
try { try {
EnumSet<IGState> state = EnumSet.noneOf(IGState.class); if (insn.getType() == InsnType.NOP) {
if (flag == IGState.BODY_ONLY || flag == IGState.BODY_ONLY_NOWRAP) { return false;
}
EnumSet<Flags> state = EnumSet.noneOf(Flags.class);
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
state.add(flag); state.add(flag);
makeInsnBody(code, insn, state); makeInsnBody(code, insn, state);
} else { } else {
CodeWriter body = new CodeWriter(code.getIndent());
makeInsnBody(body, insn, state);
if (state.contains(IGState.SKIP)) {
return false;
}
code.startLine(); code.startLine();
if (insn.getSourceLine() != 0) { if (insn.getSourceLine() != 0) {
code.attachAnnotation(insn.getSourceLine()); code.attachAnnotation(insn.getSourceLine());
} }
if (insn.getResult() != null && !state.contains(IGState.NO_RESULT)) { if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
code.add(assignVar(insn)).add(" = "); code.add(assignVar(insn)).add(" = ");
} }
code.add(body); makeInsnBody(code, insn, state);
code.add(';');
if (!state.contains(IGState.NO_SEMICOLON)) {
code.add(';');
}
} }
} catch (Throwable th) { } catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th); throw new CodegenException(mth, "Error generate insn: " + insn, th);
...@@ -234,7 +228,7 @@ public class InsnGen { ...@@ -234,7 +228,7 @@ public class InsnGen {
return true; return true;
} }
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<IGState> state) throws CodegenException { private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<Flags> state) throws CodegenException {
switch (insn.getType()) { switch (insn.getType()) {
case CONST_STR: case CONST_STR:
String str = ((ConstStringNode) insn).getString(); String str = ((ConstStringNode) insn).getString();
...@@ -257,7 +251,7 @@ public class InsnGen { ...@@ -257,7 +251,7 @@ public class InsnGen {
case CHECK_CAST: case CHECK_CAST:
case CAST: { case CAST: {
boolean wrap = state.contains(IGState.BODY_ONLY); boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
} }
...@@ -270,13 +264,18 @@ public class InsnGen { ...@@ -270,13 +264,18 @@ public class InsnGen {
} }
break; break;
} }
case ARITH: case ARITH:
makeArith((ArithNode) insn, code, state); makeArith((ArithNode) insn, code, state);
break; break;
case ARITH_ONEARG:
makeArithOneArg((ArithNode) insn, code, state);
break;
case NEG: case NEG:
String base = "-" + arg(insn.getArg(0)); String base = "-" + arg(insn.getArg(0));
if (state.contains(IGState.BODY_ONLY)) { if (state.contains(Flags.BODY_ONLY)) {
code.add('(').add(base).add(')'); code.add('(').add(base).add(')');
} else { } else {
code.add(base); code.add(base);
...@@ -311,7 +310,7 @@ public class InsnGen { ...@@ -311,7 +310,7 @@ public class InsnGen {
break; break;
case INSTANCE_OF: { case INSTANCE_OF: {
boolean wrap = state.contains(IGState.BODY_ONLY); boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
} }
...@@ -400,7 +399,7 @@ public class InsnGen { ...@@ -400,7 +399,7 @@ public class InsnGen {
} }
} }
// TODO: wrap in braces only if necessary // TODO: wrap in braces only if necessary
if (state.contains(IGState.BODY_ONLY)) { if (state.contains(Flags.BODY_ONLY)) {
code.add('(').add(sb.toString()).add(')'); code.add('(').add(sb.toString()).add(')');
} else { } else {
code.add(sb.toString()); code.add(sb.toString());
...@@ -410,16 +409,12 @@ public class InsnGen { ...@@ -410,16 +409,12 @@ public class InsnGen {
case MONITOR_ENTER: case MONITOR_ENTER:
if (isFallback()) { if (isFallback()) {
code.add("monitor-enter(").add(arg(insn.getArg(0))).add(')'); code.add("monitor-enter(").add(arg(insn.getArg(0))).add(')');
} else {
state.add(IGState.SKIP);
} }
break; break;
case MONITOR_EXIT: case MONITOR_EXIT:
if (isFallback()) { if (isFallback()) {
code.add("monitor-exit(").add(arg(insn, 0)).add(')'); code.add("monitor-exit(").add(arg(insn, 0)).add(')');
} else {
state.add(IGState.SKIP);
} }
break; break;
...@@ -439,10 +434,6 @@ public class InsnGen { ...@@ -439,10 +434,6 @@ public class InsnGen {
code.add(arg(insn, 0)); code.add(arg(insn, 0));
break; break;
case NOP:
state.add(IGState.SKIP);
break;
/* fallback mode instructions */ /* fallback mode instructions */
case IF: case IF:
assert isFallback() : "if insn in not fallback mode"; assert isFallback() : "if insn in not fallback mode";
...@@ -472,7 +463,6 @@ public class InsnGen { ...@@ -472,7 +463,6 @@ public class InsnGen {
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';'); code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
code.decIndent(); code.decIndent();
code.startLine('}'); code.startLine('}');
state.add(IGState.NO_SEMICOLON);
break; break;
case NEW_INSTANCE: case NEW_INSTANCE:
...@@ -556,7 +546,7 @@ public class InsnGen { ...@@ -556,7 +546,7 @@ public class InsnGen {
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}'); code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
} }
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<IGState> state) private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<Flags> state)
throws CodegenException { throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType()); ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) { if (cls != null && cls.isAnonymous()) {
...@@ -579,9 +569,7 @@ public class InsnGen { ...@@ -579,9 +569,7 @@ public class InsnGen {
return; return;
} }
if (insn.isSelf()) { if (insn.isSelf()) {
// skip throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
state.add(IGState.SKIP);
return;
} }
if (insn.isSuper()) { if (insn.isSuper()) {
code.add("super"); code.add("super");
...@@ -632,6 +620,9 @@ public class InsnGen { ...@@ -632,6 +620,9 @@ public class InsnGen {
} }
break; break;
} }
if (callMthNode != null) {
code.attachAnnotation(callMthNode);
}
code.add(callMth.getName()); code.add(callMth.getName());
generateArguments(code, insn, k, callMthNode); generateArguments(code, insn, k, callMthNode);
} }
...@@ -678,7 +669,7 @@ public class InsnGen { ...@@ -678,7 +669,7 @@ public class InsnGen {
IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE); IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE);
InsnNode inl = ((MethodInlineAttr) mia).getInsn(); InsnNode inl = ((MethodInlineAttr) mia).getInsn();
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) { if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, IGState.BODY_ONLY); makeInsn(inl, code, Flags.BODY_ONLY);
} else { } else {
// remap args // remap args
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()]; InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
...@@ -705,7 +696,7 @@ public class InsnGen { ...@@ -705,7 +696,7 @@ public class InsnGen {
} }
} }
} }
makeInsn(inl, code, IGState.BODY_ONLY); makeInsn(inl, code, Flags.BODY_ONLY);
// revert changes // revert changes
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) { for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
inl.replaceArg(e.getValue(), e.getKey()); inl.replaceArg(e.getValue(), e.getKey());
...@@ -713,14 +704,14 @@ public class InsnGen { ...@@ -713,14 +704,14 @@ public class InsnGen {
} }
} }
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<IGState> state) throws CodegenException { private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
String cond = ConditionGen.make(this, insn.getCondition()); String cond = ConditionGen.make(this, insn.getCondition());
CodeWriter th = arg(insn.getArg(0), false); CodeWriter th = arg(insn.getArg(0), false);
CodeWriter els = arg(insn.getArg(1), false); CodeWriter els = arg(insn.getArg(1), false);
if (th.toString().equals("true") && els.toString().equals("false")) { if (th.toString().equals("true") && els.toString().equals("false")) {
code.add(cond); code.add(cond);
} else { } else {
if (state.contains(IGState.BODY_ONLY)) { if (state.contains(Flags.BODY_ONLY)) {
code.add("((").add(cond).add(')').add(" ? ").add(th).add(" : ").add(els).add(')'); code.add("((").add(cond).add(')').add(" ? ").add(th).add(" : ").add(els).add(')');
} else { } else {
code.add('(').add(cond).add(')').add(" ? ").add(th).add(" : ").add(els); code.add('(').add(cond).add(')').add(" ? ").add(th).add(" : ").add(els);
...@@ -728,33 +719,40 @@ public class InsnGen { ...@@ -728,33 +719,40 @@ public class InsnGen {
} }
} }
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<IGState> state) throws CodegenException { private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
ArithOp op = insn.getOp(); ArithOp op = insn.getOp();
CodeWriter v1 = arg(insn.getArg(0)); if (state.contains(Flags.BODY_ONLY)) {
CodeWriter v2 = arg(insn.getArg(1));
if (state.contains(IGState.BODY_ONLY)) {
// wrap insn in brackets for save correct operation order // wrap insn in brackets for save correct operation order
code.add('(').add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2).add(')'); code.add('(');
} else if (state.contains(IGState.BODY_ONLY_NOWRAP)) { addArg(code, insn.getArg(0));
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2); code.add(' ');
code.add(op.getSymbol());
code.add(' ');
addArg(code, insn.getArg(1));
code.add(')');
} else { } else {
CodeWriter res = arg(insn.getResult()); addArg(code, insn.getArg(0));
if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) { code.add(' ');
state.add(IGState.NO_RESULT); code.add(op.getSymbol());
// "++" or "--" code.add(' ');
if (insn.getArg(1).isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) { addArg(code, insn.getArg(1));
LiteralArg lit = (LiteralArg) insn.getArg(1); }
if (lit.isInteger() && lit.getLiteral() == 1) { }
code.add(assignVar(insn)).add(op.getSymbol()).add(op.getSymbol());
return; private void makeArithOneArg(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
} ArithOp op = insn.getOp();
} InsnArg arg = insn.getArg(0);
// +=, -= ... // "++" or "--"
v2 = arg(insn.getArg(1), false); if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
code.add(assignVar(insn)).add(' ').add(op.getSymbol()).add("= ").add(v2); LiteralArg lit = (LiteralArg) arg;
} else { if (lit.isInteger() && lit.getLiteral() == 1) {
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2); String opSymbol = op.getSymbol();
code.add(assignVar(insn)).add(opSymbol).add(opSymbol);
return;
} }
} }
// +=, -= ...
code.add(assignVar(insn)).add(' ').add(op.getSymbol()).add("= ");
addArg(code, arg, false);
} }
} }
...@@ -61,13 +61,13 @@ public class MethodGen { ...@@ -61,13 +61,13 @@ public class MethodGen {
public boolean addDefinition(CodeWriter code) { public boolean addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) { if (mth.getMethodInfo().isClassInit()) {
code.startLine("static"); code.startLine("static");
code.attachAnnotation(mth); code.attachDefinition(mth);
return true; return true;
} }
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) { if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments // don't add method name and arguments
code.startLine(); code.startLine();
code.attachAnnotation(mth); code.attachDefinition(mth);
return false; return false;
} }
annotationGen.addForMethod(code, mth); annotationGen.addForMethod(code, mth);
...@@ -110,17 +110,15 @@ public class MethodGen { ...@@ -110,17 +110,15 @@ public class MethodGen {
)); ));
} }
} }
code.add(makeArguments(args)); addMethodArguments(code, args);
code.add(")"); code.add(')');
annotationGen.addThrows(mth, code); annotationGen.addThrows(mth, code);
code.attachAnnotation(mth); code.attachDefinition(mth);
return true; return true;
} }
public CodeWriter makeArguments(List<RegisterArg> args) { private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
CodeWriter argsCode = new CodeWriter();
MethodParameters paramsAnnotation = MethodParameters paramsAnnotation =
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS); (MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
...@@ -154,7 +152,6 @@ public class MethodGen { ...@@ -154,7 +152,6 @@ public class MethodGen {
argsCode.add(", "); argsCode.add(", ");
} }
} }
return argsCode;
} }
/** /**
......
...@@ -51,7 +51,7 @@ public class ArithNode extends InsnNode { ...@@ -51,7 +51,7 @@ public class ArithNode extends InsnNode {
} }
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) { public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH, 1); super(InsnType.ARITH_ONEARG, 1);
this.op = op; this.op = op;
setResult(res); setResult(res);
addArg(a); addArg(a);
......
...@@ -54,6 +54,7 @@ public enum InsnType { ...@@ -54,6 +54,7 @@ public enum InsnType {
CONTINUE, CONTINUE,
STR_CONCAT, // strings concatenation STR_CONCAT, // strings concatenation
ARITH_ONEARG,
TERNARY, TERNARY,
ARGS, // just generate arguments ARGS, // just generate arguments
......
...@@ -50,7 +50,10 @@ public class ClassNode extends LineAttrNode implements ILoadable { ...@@ -50,7 +50,10 @@ public class ClassNode extends LineAttrNode implements ILoadable {
private Map<Object, FieldNode> constFields = Collections.emptyMap(); private Map<Object, FieldNode> constFields = Collections.emptyMap();
private List<ClassNode> innerClasses = Collections.emptyList(); private List<ClassNode> innerClasses = Collections.emptyList();
// store decompiled code
private CodeWriter code; private CodeWriter code;
// store parent for inner classes or 'this' otherwise
private ClassNode parentClass;
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException { public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
this.dex = dex; this.dex = dex;
...@@ -332,6 +335,19 @@ public class ClassNode extends LineAttrNode implements ILoadable { ...@@ -332,6 +335,19 @@ public class ClassNode extends LineAttrNode implements ILoadable {
return searchMethodByName(MethodInfo.fromDex(dex, id).getShortId()); return searchMethodByName(MethodInfo.fromDex(dex, id).getShortId());
} }
public ClassNode getParentClass() {
if (parentClass == null) {
if (clsInfo.isInner()) {
ClassNode parent = dex().resolveClass(clsInfo.getParentClass());
parent = parent == null ? this : parent;
parentClass = parent;
} else {
parentClass = this;
}
}
return parentClass;
}
public List<ClassNode> getInnerClasses() { public List<ClassNode> getInnerClasses() {
return innerClasses; return innerClasses;
} }
......
...@@ -10,12 +10,14 @@ import com.android.dx.io.ClassData.Field; ...@@ -10,12 +10,14 @@ import com.android.dx.io.ClassData.Field;
public class FieldNode extends LineAttrNode { public class FieldNode extends LineAttrNode {
private final ClassNode parent;
private final FieldInfo fieldInfo; private final FieldInfo fieldInfo;
private final AccessInfo accFlags; private final AccessInfo accFlags;
private ArgType type; // store signature private ArgType type; // store signature
public FieldNode(ClassNode cls, Field field) { public FieldNode(ClassNode cls, Field field) {
this.parent = cls;
this.fieldInfo = FieldInfo.fromDex(cls.dex(), field.getFieldIndex()); this.fieldInfo = FieldInfo.fromDex(cls.dex(), field.getFieldIndex());
this.type = fieldInfo.getType(); this.type = fieldInfo.getType();
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD); this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
...@@ -41,6 +43,10 @@ public class FieldNode extends LineAttrNode { ...@@ -41,6 +43,10 @@ public class FieldNode extends LineAttrNode {
this.type = type; this.type = type;
} }
public ClassNode getParentClass() {
return parent;
}
@Override @Override
public int hashCode() { public int hashCode() {
return fieldInfo.hashCode(); return fieldInfo.hashCode();
......
...@@ -159,7 +159,7 @@ public class ClassModifier extends AbstractVisitor { ...@@ -159,7 +159,7 @@ public class ClassModifier extends AbstractVisitor {
&& af.isPublic() && af.isPublic()
&& mth.getArguments(false).isEmpty()) { && mth.getArguments(false).isEmpty()) {
List<BlockNode> bb = mth.getBasicBlocks(); List<BlockNode> bb = mth.getBasicBlocks();
if (bb.isEmpty() || allBlocksEmpty(bb)) { if (bb == null || bb.isEmpty() || allBlocksEmpty(bb)) {
mth.getAttributes().add(AttributeFlag.DONT_GENERATE); mth.getAttributes().add(AttributeFlag.DONT_GENERATE);
} }
} }
......
package jadx.core.dex.visitors;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
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.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxException;
import java.util.Iterator;
import java.util.List;
public class PrepareForCodeGen extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
List<BlockNode> blocks = mth.getBasicBlocks();
if (blocks == null) {
return;
}
for (BlockNode block : blocks) {
removeInstructions(block);
modifyArith(block);
}
}
private static void removeInstructions(BlockNode block) {
Iterator<InsnNode> it = block.getInstructions().iterator();
while (it.hasNext()) {
InsnNode insn = it.next();
switch (insn.getType()) {
case NOP:
case MONITOR_ENTER:
case MONITOR_EXIT:
it.remove();
break;
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
if (co.isSelf()) {
it.remove();
}
break;
}
}
}
private static void modifyArith(BlockNode block) {
List<InsnNode> list = block.getInstructions();
for (int i = 0; i < list.size(); i++) {
InsnNode insn = list.get(i);
if (insn.getType() == InsnType.ARITH) {
ArithNode arith = (ArithNode) insn;
RegisterArg res = arith.getResult();
InsnArg arg = arith.getArg(0);
if (res.equals(arg)) {
ArithNode newArith = new ArithNode(arith.getOp(), res, arith.getArg(1));
list.set(i, newArith);
}
}
}
}
}
package jadx.tests.internal; package jadx.tests.internal;
import jadx.api.InternalJadxTest; import jadx.api.InternalJadxTest;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
import org.junit.Test; import org.junit.Test;
import static junit.framework.Assert.assertEquals;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
...@@ -39,15 +31,9 @@ public class TestFieldIncrement extends InternalJadxTest { ...@@ -39,15 +31,9 @@ public class TestFieldIncrement extends InternalJadxTest {
@Test @Test
public void test() { public void test() {
ClassNode cls = getClassNode(TestCls.class); ClassNode cls = getClassNode(TestCls.class);
MethodNode mth = getMethod(cls, "method");
List<InsnNode> insns = mth.getBasicBlocks().get(0).getInstructions();
assertEquals(insns.size(), 1);
InsnNode insnNode = insns.get(0);
assertEquals(InsnType.ARITH, insnNode.getType());
assertEquals(ArithOp.ADD, ((ArithNode) insnNode).getOp());
String code = cls.getCode().toString(); String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("instanceField++;")); assertThat(code, containsString("instanceField++;"));
assertThat(code, containsString("staticField--;")); assertThat(code, containsString("staticField--;"));
assertThat(code, containsString("result += s + '_';")); assertThat(code, containsString("result += s + '_';"));
......
...@@ -37,5 +37,7 @@ public class TestSynchronized extends InternalJadxTest { ...@@ -37,5 +37,7 @@ public class TestSynchronized extends InternalJadxTest {
assertThat(code, containsString("public synchronized boolean test1() {")); assertThat(code, containsString("public synchronized boolean test1() {"));
assertThat(code, containsString("return this.f")); assertThat(code, containsString("return this.f"));
assertThat(code, containsString("synchronized (this.o) {")); assertThat(code, containsString("synchronized (this.o) {"));
assertThat(code, not(containsString(makeIndent(3) + ";")));
} }
} }
package jadx.tests.internal.arith;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestArith extends InternalJadxTest {
public static class TestCls {
public void method(int a) {
a += 2;
}
public void method2(int a) {
a++;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("a += 2;"));
assertThat(code, containsString("a++;"));
}
}
package jadx.tests.internal.arith;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestArith2 extends InternalJadxTest {
public static class TestCls {
public int test1(int a) {
return (a + 2) * 3;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("return (a + 2) * 3;"));
assertThat(code, not(containsString("a + 2 * 3")));
}
}
package jadx.tests.internal; package jadx.tests.internal.debuginfo;
import jadx.api.InternalJadxTest; import jadx.api.InternalJadxTest;
import jadx.core.codegen.CodeWriter; import jadx.core.codegen.CodeWriter;
......
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