未验证 提交 1efdcd7b 编写于 作者: S Skylot

feat: input plugin for java bytecode

上级 2d9bcdb8
......@@ -29,8 +29,9 @@ jobs:
name: Build with Gradle
env:
TERM: dumb
TEST_INPUT_PLUGIN: dx
with:
arguments: build dist copyExe --warning-mode=all
arguments: clean build dist copyExe --warning-mode=all
- name: Save bundle artifact
if: success() && github.event_name == 'push'
......
......@@ -16,12 +16,7 @@ java-8:
java-11:
stage: test
image: openjdk:11
script: ./gradlew clean build dist --warning-mode=all
java-15:
stage: test
image: openjdk:15
script: ./gradlew clean build dist --warning-mode=all
script: ./gradlew clean build dist copyExe --warning-mode=all
java-latest:
stage: test
......
......@@ -6,8 +6,8 @@ dependencies {
implementation(project(':jadx-core'))
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
implementation 'com.beust:jcommander:1.81'
implementation 'ch.qos.logback:logback-classic:1.2.5'
......
......@@ -13,6 +13,7 @@ dependencies {
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
}
test {
......
......@@ -576,6 +576,10 @@ public final class JadxDecompiler implements Closeable {
return args;
}
public JadxPluginManager getPluginManager() {
return pluginManager;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
......
......@@ -63,7 +63,7 @@ public final class JavaClass implements JavaNode {
}
public synchronized String getSmali() {
return cls.getSmali();
return cls.getDisassembledCode();
}
/**
......
......@@ -15,13 +15,6 @@ public class Consts {
public static final String CLASS_ENUM = "java.lang.Enum";
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
public static final String OVERRIDE_ANNOTATION = "Ljava/lang/Override;";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
......
......@@ -8,14 +8,15 @@ import java.util.Map.Entry;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
......@@ -47,12 +48,12 @@ public class AnnotationGen {
add(field, code);
}
public void addForParameter(ICodeWriter code, MethodParameters paramsAnnotations, int n) {
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) {
List<AnnotationsAttr> paramList = paramsAnnotations.getParamList();
if (n >= paramList.size()) {
return;
}
AnnotationsList aList = paramList.get(n);
AnnotationsAttr aList = paramList.get(n);
if (aList == null || aList.isEmpty()) {
return;
}
......@@ -63,13 +64,13 @@ public class AnnotationGen {
}
private void add(IAttributeNode node, ICodeWriter code) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
AnnotationsAttr aList = node.get(JadxAttrType.ANNOTATION_LIST);
if (aList == null || aList.isEmpty()) {
return;
}
for (IAnnotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG) && !aCls.equals(Consts.OVERRIDE_ANNOTATION)) {
if (!aCls.equals(Consts.OVERRIDE_ANNOTATION)) {
code.startLine();
formatAnnotation(code, a);
}
......@@ -131,16 +132,12 @@ public class AnnotationGen {
}
}
public EncodedValue getAnnotationDefaultValue(String name) {
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
EncodedValue defValue = an.getDefaultValue();
if (defValue != null) {
IAnnotation defAnnotation = (IAnnotation) defValue.getValue();
return defAnnotation.getValues().get(name);
}
public EncodedValue getAnnotationDefaultValue(MethodNode mth) {
AnnotationDefaultAttr defaultAttr = mth.get(JadxAttrType.ANNOTATION_DEFAULT);
if (defaultAttr == null) {
return null;
}
return null;
return defaultAttr.getValue();
}
// TODO: refactor this boilerplate code
......@@ -188,9 +185,9 @@ public class AnnotationGen {
case ENCODED_ENUM:
case ENCODED_FIELD:
// must be a static field
if (value instanceof IFieldData) {
FieldInfo field = FieldInfo.fromData(root, (IFieldData) value);
InsnGen.makeStaticFieldAccess(code, field, classGen);
if (value instanceof IFieldRef) {
FieldInfo fieldInfo = FieldInfo.fromRef(root, (IFieldRef) value);
InsnGen.makeStaticFieldAccess(code, fieldInfo, classGen);
} else if (value instanceof FieldInfo) {
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
} else {
......
......@@ -20,11 +20,12 @@ import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
......@@ -33,6 +34,7 @@ import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
......@@ -41,6 +43,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.EncodedValueUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
......@@ -396,21 +399,30 @@ public class ClassGen {
code.add(' ');
code.attachDefinition(f);
code.add(f.getAlias());
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) {
FieldInitInsnAttr initInsnAttr = f.get(AType.FIELD_INIT_INSN);
if (initInsnAttr != null) {
InsnGen insnGen = makeInsnGen(initInsnAttr.getInsnMth());
code.add(" = ");
if (fv.isConst()) {
EncodedValue encodedValue = fv.getEncodedValue();
if (encodedValue.getType() == EncodedType.ENCODED_NULL) {
addInsnBody(insnGen, code, initInsnAttr.getInsn());
} else {
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null) {
code.add(" = ");
if (constVal.getType() == EncodedType.ENCODED_NULL) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
if (!AndroidResourcesUtils.handleResourceFieldValue(cls, code, encodedValue)) {
annotationGen.encodeValue(cls.root(), code, encodedValue);
Object val = EncodedValueUtils.convertToConstValue(constVal);
if (val instanceof LiteralArg) {
long lit = ((LiteralArg) val).getLiteral();
if (!AndroidResourcesUtils.handleResourceFieldValue(cls, code, lit, f.getType())) {
// force literal type to be same as field (java bytecode can use different type)
code.add(TypeGen.literalToString(lit, f.getType(), cls, fallback));
}
} else {
annotationGen.encodeValue(cls.root(), code, constVal);
}
}
} else if (fv.isInsn()) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
}
}
code.add(';');
......
......@@ -398,11 +398,15 @@ public class InsnGen {
ArgType arrayType = ((NewArrayNode) insn).getArrayType();
code.add("new ");
useType(code, arrayType.getArrayRootElement());
code.add('[');
addArg(code, insn.getArg(0));
code.add(']');
int k = 0;
int argsCount = insn.getArgsCount();
for (; k < argsCount; k++) {
code.add('[');
addArg(code, insn.getArg(k), false);
code.add(']');
}
int dim = arrayType.getArrayDimension();
for (int i = 0; i < dim - 1; i++) {
for (; k < dim - 1; k++) {
code.add("[]");
}
break;
......@@ -572,7 +576,7 @@ public class InsnGen {
case FILL_ARRAY_DATA:
fallbackOnlyInsn(insn);
code.add("fill-array " + insn.toString());
code.add("fill-array " + insn);
break;
case SWITCH_DATA:
......@@ -580,6 +584,18 @@ public class InsnGen {
code.add(insn.toString());
break;
case MOVE_MULTI:
fallbackOnlyInsn(insn);
code.add("move-multi: ");
int len = insn.getArgsCount();
for (int i = 0; i < len - 1; i += 2) {
addArg(code, insn.getArg(i));
code.add(" = ");
addArg(code, insn.getArg(i + 1));
code.add("; ");
}
break;
default:
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
}
......
......@@ -13,11 +13,12 @@ import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
import jadx.core.Consts;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
......@@ -39,7 +40,6 @@ import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxOverflowException;
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
......@@ -103,6 +103,9 @@ public class MethodGen {
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.PUBLIC);
}
if (mth.getMethodInfo().isConstructor() && mth.getParentClass().isEnum()) {
ai = ai.remove(AccessInfo.VISIBILITY_FLAGS);
}
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
......@@ -113,9 +116,6 @@ public class MethodGen {
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
if (Consts.DEBUG) {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (clsAccFlags.isInterface() && !mth.isNoCode() && !mth.getAccessFlags().isStatic()) {
// add 'default' for method with code in interface
code.add("default ");
......@@ -153,9 +153,9 @@ public class MethodGen {
annotationGen.addThrows(mth, code);
// add default value if in annotation class
// add default value for annotation class
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName());
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth);
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(mth.root(), code, def);
......@@ -181,7 +181,7 @@ public class MethodGen {
}
private void addMethodArguments(ICodeWriter code, List<RegisterArg> args) {
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
MethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
Iterator<RegisterArg> it = args.iterator();
while (it.hasNext()) {
......@@ -189,7 +189,7 @@ public class MethodGen {
SSAVar ssaVar = mthArg.getSVar();
CodeVar var;
if (ssaVar == null) {
// null for abstract or interface methods
// abstract or interface methods
var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode());
} else {
var = ssaVar.getCodeVar();
......@@ -291,17 +291,21 @@ public class MethodGen {
public void addFallbackMethodCode(ICodeWriter code, FallbackOption fallbackOption) {
if (fallbackOption != FALLBACK_MODE) {
// load original instructions
List<JadxError> errors = mth.getAll(AType.JADX_ERROR); // preserve error before unload
try {
// load original instructions
mth.unload();
mth.load();
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
DepthTraversal.visit(visitor, mth);
}
} catch (DecodeException e) {
errors.forEach(err -> mth.addAttr(AType.JADX_ERROR, err));
} catch (Exception e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
} finally {
errors.forEach(err -> mth.addAttr(AType.JADX_ERROR, err));
}
}
InsnNode[] insnArr = mth.getInstructions();
......
......@@ -12,9 +12,10 @@ import jadx.api.ICodeWriter;
import jadx.api.data.ICodeComment;
import jadx.api.data.annotations.CustomOffsetRef;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
......@@ -291,12 +292,9 @@ public class RegionGen extends InsnGen {
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replaced with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null && valueAttr.isConst()) {
Object value = valueAttr.getEncodedValue().getValue();
if (value != null) {
code.add(" /* ").add(value.toString()).add(" */");
}
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
}
}
} else if (k instanceof Integer) {
......
......@@ -9,10 +9,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
......@@ -443,7 +444,7 @@ public class Deobfuscator {
@Nullable
private String getAliasFromSourceFile(ClassNode cls) {
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
SourceFileAttr sourceFileAttr = cls.get(JadxAttrType.SOURCE_FILE);
if (sourceFileAttr == null) {
return null;
}
......@@ -468,7 +469,7 @@ public class Deobfuscator {
if (otherCls != null) {
return null;
}
cls.remove(AType.SOURCE_FILE);
cls.remove(JadxAttrType.SOURCE_FILE);
return name;
}
......
package jadx.core.dex.attributes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
......@@ -28,7 +23,6 @@ import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
......@@ -40,14 +34,12 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*
* @param <T> attribute class implementation
*/
@SuppressWarnings("InstantiationOfUtilityClass")
public class AType<T extends IAttribute> {
public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
// class, method, field, insn
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
// class, method, field
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// class, method
......@@ -56,19 +48,17 @@ public class AType<T extends IAttribute> {
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
// class
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
// field
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
// method
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
......@@ -96,14 +86,4 @@ public class AType<T extends IAttribute> {
// register
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
public static final Set<AType<?>> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList(
SOURCE_FILE,
ANNOTATION_LIST,
ANNOTATION_MTH_PARAMETERS,
FIELD_INIT,
FIELD_REPLACE,
METHOD_INLINE,
METHOD_OVERRIDE,
SKIP_MTH_ARGS));
}
......@@ -4,14 +4,16 @@ import java.util.ArrayList;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.utils.Utils;
public class AttrList<T> implements IAttribute {
public class AttrList<T> implements IJadxAttribute {
private final AType<AttrList<T>> type;
private final IJadxAttrType<AttrList<T>> type;
private final List<T> list = new ArrayList<>();
public AttrList(AType<AttrList<T>> type) {
public AttrList(IJadxAttrType<AttrList<T>> type) {
this.type = type;
}
......@@ -20,7 +22,7 @@ public class AttrList<T> implements IAttribute {
}
@Override
public AType<AttrList<T>> getType() {
public IJadxAttrType<AttrList<T>> getAttrType() {
return type;
}
......
......@@ -3,6 +3,8 @@ package jadx.core.dex.attributes;
import java.util.List;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
public abstract class AttrNode implements IAttributeNode {
......@@ -16,12 +18,17 @@ public abstract class AttrNode implements IAttributeNode {
}
@Override
public void addAttr(IAttribute attr) {
public void addAttr(IJadxAttribute attr) {
initStorage().add(attr);
}
@Override
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
public void addAttrs(List<IJadxAttribute> list) {
initStorage().add(list);
}
@Override
public <T> void addAttr(IJadxAttrType<AttrList<T>> type, T obj) {
initStorage().add(type, obj);
}
......@@ -34,8 +41,8 @@ public abstract class AttrNode implements IAttributeNode {
}
@Override
public <T extends IAttribute> void copyAttributeFrom(AttrNode attrNode, AType<T> attrType) {
IAttribute attr = attrNode.get(attrType);
public <T extends IJadxAttribute> void copyAttributeFrom(AttrNode attrNode, AType<T> attrType) {
IJadxAttribute attr = attrNode.get(attrType);
if (attr != null) {
this.addAttr(attr);
}
......@@ -45,7 +52,7 @@ public abstract class AttrNode implements IAttributeNode {
* Remove attribute in this node, add copy from other if exists
*/
@Override
public <T extends IAttribute> void rewriteAttributeFrom(AttrNode attrNode, AType<T> attrType) {
public <T extends IJadxAttribute> void rewriteAttributeFrom(AttrNode attrNode, AType<T> attrType) {
remove(attrType);
copyAttributeFrom(attrNode, attrType);
}
......@@ -71,12 +78,12 @@ public abstract class AttrNode implements IAttributeNode {
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
public <T extends IJadxAttribute> boolean contains(IJadxAttrType<T> type) {
return storage.contains(type);
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
public <T extends IJadxAttribute> T get(IJadxAttrType<T> type) {
return storage.get(type);
}
......@@ -86,7 +93,7 @@ public abstract class AttrNode implements IAttributeNode {
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
public <T> List<T> getAll(IJadxAttrType<AttrList<T>> type) {
return storage.getAll(type);
}
......@@ -97,13 +104,13 @@ public abstract class AttrNode implements IAttributeNode {
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
public <T extends IJadxAttribute> void remove(IJadxAttrType<T> type) {
storage.remove(type);
unloadIfEmpty();
}
@Override
public void removeAttr(IAttribute attr) {
public void removeAttr(IJadxAttribute attr) {
storage.remove(attr);
unloadIfEmpty();
}
......@@ -115,7 +122,7 @@ public abstract class AttrNode implements IAttributeNode {
}
/**
* Remove all attribute with exceptions from {@link AType#SKIP_ON_UNLOAD}
* Remove all attribute
*/
public void unloadAttributes() {
if (storage == EMPTY_ATTR_STORAGE) {
......
......@@ -9,7 +9,10 @@ import java.util.Map;
import java.util.Set;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
......@@ -28,22 +31,34 @@ public class AttributeStorage {
}
private final Set<AFlag> flags;
private Map<AType<?>, IAttribute> attributes;
private Map<IJadxAttrType<?>, IJadxAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = Collections.emptyMap();
}
public AttributeStorage(List<IJadxAttribute> attributesList) {
this();
add(attributesList);
}
public void add(AFlag flag) {
flags.add(flag);
}
public void add(IAttribute attr) {
writeAttributes().put(attr.getType(), attr);
public void add(IJadxAttribute attr) {
writeAttributes().put(attr.getAttrType(), attr);
}
public void add(List<IJadxAttribute> list) {
Map<IJadxAttrType<?>, IJadxAttribute> map = writeAttributes();
for (IJadxAttribute attr : list) {
map.put(attr.getAttrType(), attr);
}
}
public <T> void add(AType<AttrList<T>> type, T obj) {
public <T> void add(IJadxAttrType<AttrList<T>> type, T obj) {
AttrList<T> list = get(type);
if (list == null) {
list = new AttrList<>(type);
......@@ -61,21 +76,21 @@ public class AttributeStorage {
return flags.contains(flag);
}
public <T extends IAttribute> boolean contains(AType<T> type) {
public <T extends IJadxAttribute> boolean contains(IJadxAttrType<T> type) {
return attributes.containsKey(type);
}
@SuppressWarnings("unchecked")
public <T extends IAttribute> T get(AType<T> type) {
public <T extends IJadxAttribute> T get(IJadxAttrType<T> type) {
return (T) attributes.get(type);
}
public IAnnotation getAnnotation(String cls) {
AnnotationsList aList = get(AType.ANNOTATION_LIST);
AnnotationsAttr aList = get(JadxAttrType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
public <T> List<T> getAll(AType<AttrList<T>> type) {
public <T> List<T> getAll(IJadxAttrType<AttrList<T>> type) {
AttrList<T> attrList = get(type);
if (attrList == null) {
return Collections.emptyList();
......@@ -87,23 +102,23 @@ public class AttributeStorage {
flags.remove(flag);
}
public <T extends IAttribute> void remove(AType<T> type) {
public <T extends IJadxAttribute> void remove(IJadxAttrType<T> type) {
if (!attributes.isEmpty()) {
attributes.remove(type);
}
}
public void remove(IAttribute attr) {
public void remove(IJadxAttribute attr) {
if (!attributes.isEmpty()) {
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
IJadxAttrType<? extends IJadxAttribute> type = attr.getAttrType();
IJadxAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
}
private Map<AType<?>, IAttribute> writeAttributes() {
private Map<IJadxAttrType<?>, IJadxAttribute> writeAttributes() {
if (attributes.isEmpty()) {
attributes = new IdentityHashMap<>(5);
}
......@@ -121,8 +136,7 @@ public class AttributeStorage {
if (attributes.isEmpty()) {
return;
}
Set<AType<?>> skipOnUnload = AType.SKIP_ON_UNLOAD;
attributes.keySet().removeIf(attrType -> !skipOnUnload.contains(attrType));
attributes.entrySet().removeIf(entry -> !entry.getValue().keepLoaded());
}
public List<String> getAttributeStrings() {
......@@ -134,7 +148,7 @@ public class AttributeStorage {
for (AFlag a : flags) {
list.add(a.toString());
}
for (IAttribute a : attributes.values()) {
for (IJadxAttribute a : attributes.values()) {
list.add(a.toAttrString());
}
return list;
......
......@@ -4,6 +4,8 @@ import java.util.Collections;
import java.util.List;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
public final class EmptyAttrStorage extends AttributeStorage {
......@@ -13,12 +15,12 @@ public final class EmptyAttrStorage extends AttributeStorage {
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
public <T extends IJadxAttribute> boolean contains(IJadxAttrType<T> type) {
return false;
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
public <T extends IJadxAttribute> T get(IJadxAttrType<T> type) {
return null;
}
......@@ -28,7 +30,7 @@ public final class EmptyAttrStorage extends AttributeStorage {
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
public <T> List<T> getAll(IJadxAttrType<AttrList<T>> type) {
return Collections.emptyList();
}
......@@ -43,12 +45,12 @@ public final class EmptyAttrStorage extends AttributeStorage {
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
public <T extends IJadxAttribute> void remove(IJadxAttrType<T> type) {
// ignore
}
@Override
public void remove(IAttribute attr) {
public void remove(IJadxAttribute attr) {
// ignore
}
......
package jadx.core.dex.attributes.fldinit;
package jadx.core.dex.attributes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import static java.util.Objects.requireNonNull;
public final class FieldInitInsnAttr extends FieldInitAttr {
public final class FieldInitInsnAttr extends PinnedAttribute {
private final MethodNode mth;
private final InsnNode insn;
FieldInitInsnAttr(MethodNode mth, InsnNode insn) {
public FieldInitInsnAttr(MethodNode mth, InsnNode insn) {
this.mth = requireNonNull(mth);
this.insn = requireNonNull(insn);
}
@Override
public InsnNode getInsn() {
return insn;
}
@Override
public MethodNode getInsnMth() {
return mth;
}
@Override
public boolean isInsn() {
return true;
public IJadxAttrType<? extends IJadxAttribute> getAttrType() {
return AType.FIELD_INIT_INSN;
}
@Override
......
package jadx.core.dex.attributes;
public interface IAttribute {
AType<? extends IAttribute> getType();
default String toAttrString() {
return this.toString();
}
}
......@@ -3,36 +3,40 @@ package jadx.core.dex.attributes;
import java.util.List;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
public interface IAttributeNode {
void add(AFlag flag);
void addAttr(IAttribute attr);
void addAttr(IJadxAttribute attr);
<T> void addAttr(AType<AttrList<T>> type, T obj);
void addAttrs(List<IJadxAttribute> list);
<T> void addAttr(IJadxAttrType<AttrList<T>> type, T obj);
void copyAttributesFrom(AttrNode attrNode);
<T extends IAttribute> void copyAttributeFrom(AttrNode attrNode, AType<T> attrType);
<T extends IJadxAttribute> void copyAttributeFrom(AttrNode attrNode, AType<T> attrType);
<T extends IAttribute> void rewriteAttributeFrom(AttrNode attrNode, AType<T> attrType);
<T extends IJadxAttribute> void rewriteAttributeFrom(AttrNode attrNode, AType<T> attrType);
boolean contains(AFlag flag);
<T extends IAttribute> boolean contains(AType<T> type);
<T extends IJadxAttribute> boolean contains(IJadxAttrType<T> type);
<T extends IAttribute> T get(AType<T> type);
<T extends IJadxAttribute> T get(IJadxAttrType<T> type);
IAnnotation getAnnotation(String cls);
<T> List<T> getAll(AType<AttrList<T>> type);
<T> List<T> getAll(IJadxAttrType<AttrList<T>> type);
void remove(AFlag flag);
<T extends IAttribute> void remove(AType<T> type);
<T extends IJadxAttribute> void remove(IJadxAttrType<T> type);
void removeAttr(IAttribute attr);
void removeAttr(IJadxAttribute attr);
void clearAttributes();
......
package jadx.core.dex.attributes.annotations;
import java.util.ArrayList;
import java.util.List;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.utils.Utils;
public class MethodParameters implements IAttribute {
public static void attach(ICodeNode node, List<List<IAnnotation>> annotationRefList) {
if (annotationRefList.isEmpty()) {
return;
}
List<AnnotationsList> list = new ArrayList<>(annotationRefList.size());
for (List<IAnnotation> annList : annotationRefList) {
list.add(AnnotationsList.pack(annList));
}
node.addAttr(new MethodParameters(list));
}
private final List<AnnotationsList> paramList;
public MethodParameters(List<AnnotationsList> paramsList) {
this.paramList = paramsList;
}
public List<AnnotationsList> getParamList() {
return paramList;
}
@Override
public AType<MethodParameters> getType() {
return AType.ANNOTATION_MTH_PARAMETERS;
}
@Override
public String toString() {
return Utils.listToString(paramList);
}
}
package jadx.core.dex.attributes.fldinit;
import java.util.Objects;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public abstract class FieldInitAttr implements IAttribute {
public static FieldInitAttr constValue(EncodedValue value) {
if (Objects.equals(value, EncodedValue.NULL)) {
return FieldInitConstAttr.NULL_VALUE;
}
return new FieldInitConstAttr(value);
}
public static FieldInitAttr insnValue(MethodNode mth, InsnNode insn) {
return new FieldInitInsnAttr(mth, insn);
}
public boolean isConst() {
return false;
}
public boolean isInsn() {
return false;
}
public EncodedValue getEncodedValue() {
throw new JadxRuntimeException("Wrong init type");
}
public InsnNode getInsn() {
throw new JadxRuntimeException("Wrong init type");
}
public MethodNode getInsnMth() {
throw new JadxRuntimeException("Wrong init type");
}
@Override
public AType<FieldInitAttr> getType() {
return AType.FIELD_INIT;
}
}
package jadx.core.dex.attributes.fldinit;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import static java.util.Objects.requireNonNull;
public final class FieldInitConstAttr extends FieldInitAttr {
public static final FieldInitAttr NULL_VALUE = new FieldInitConstAttr(EncodedValue.NULL);
private final EncodedValue value;
FieldInitConstAttr(EncodedValue value) {
this.value = requireNonNull(value);
}
@Override
public EncodedValue getEncodedValue() {
return value;
}
@Override
public boolean isConst() {
return true;
}
@Override
public String toString() {
return "INIT{" + value + '}';
}
}
......@@ -4,11 +4,11 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
public class ClassTypeVarsAttr implements IAttribute {
public class ClassTypeVarsAttr implements IJadxAttribute {
public static final ClassTypeVarsAttr EMPTY = new ClassTypeVarsAttr(Collections.emptyList(), Collections.emptyMap());
/**
......@@ -40,7 +40,7 @@ public class ClassTypeVarsAttr implements IAttribute {
}
@Override
public AType<ClassTypeVarsAttr> getType() {
public AType<ClassTypeVarsAttr> getAttrType() {
return AType.CLASS_TYPE_VARS;
}
......
......@@ -3,15 +3,15 @@ package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.List;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.utils.Utils;
/**
* List of variables to be declared at region start.
*/
public class DeclareVariablesAttr implements IAttribute {
public class DeclareVariablesAttr implements IJadxAttribute {
private final List<CodeVar> vars = new ArrayList<>();
......@@ -24,7 +24,7 @@ public class DeclareVariablesAttr implements IAttribute {
}
@Override
public AType<DeclareVariablesAttr> getType() {
public AType<DeclareVariablesAttr> getAttrType() {
return AType.DECLARE_VARIABLES;
}
......
......@@ -2,13 +2,13 @@ package jadx.core.dex.attributes.nodes;
import java.util.Objects;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
public class EdgeInsnAttr implements IAttribute {
public class EdgeInsnAttr implements IJadxAttribute {
private final BlockNode start;
private final BlockNode end;
......@@ -31,7 +31,7 @@ public class EdgeInsnAttr implements IAttribute {
}
@Override
public AType<AttrList<EdgeInsnAttr>> getType() {
public AType<AttrList<EdgeInsnAttr>> getAttrType() {
return AType.EDGE_INSN;
}
......
......@@ -2,14 +2,14 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
public class EnumClassAttr implements IAttribute {
public class EnumClassAttr implements IJadxAttribute {
public static class EnumField {
private final FieldNode field;
......@@ -63,7 +63,7 @@ public class EnumClassAttr implements IAttribute {
}
@Override
public AType<EnumClassAttr> getType() {
public AType<EnumClassAttr> getAttrType() {
return AType.ENUM_CLASS;
}
......
......@@ -5,11 +5,11 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.FieldNode;
public class EnumMapAttr implements IAttribute {
public class EnumMapAttr implements IJadxAttribute {
public static class KeyValueMap {
private final Map<Object, Object> map = new HashMap<>();
......@@ -51,7 +51,7 @@ public class EnumMapAttr implements IAttribute {
}
@Override
public AType<EnumMapAttr> getType() {
public AType<EnumMapAttr> getAttrType() {
return AType.ENUM_MAP;
}
......
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.InsnArg;
public class FieldReplaceAttr implements IAttribute {
public class FieldReplaceAttr extends PinnedAttribute {
public enum ReplaceWith {
CLASS_INSTANCE,
......@@ -38,7 +38,7 @@ public class FieldReplaceAttr implements IAttribute {
}
@Override
public AType<FieldReplaceAttr> getType() {
public AType<FieldReplaceAttr> getAttrType() {
return AType.FIELD_REPLACE;
}
......
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
public class ForceReturnAttr implements IAttribute {
public class ForceReturnAttr implements IJadxAttribute {
private final InsnNode returnInsn;
......@@ -18,7 +18,7 @@ public class ForceReturnAttr implements IAttribute {
}
@Override
public AType<ForceReturnAttr> getType() {
public AType<ForceReturnAttr> getAttrType() {
return AType.FORCE_RETURN;
}
......
......@@ -2,11 +2,11 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
public class GenericInfoAttr implements IAttribute {
public class GenericInfoAttr implements IJadxAttribute {
private final List<ArgType> genericTypes;
private boolean explicit;
......@@ -27,7 +27,7 @@ public class GenericInfoAttr implements IAttribute {
}
@Override
public AType<GenericInfoAttr> getType() {
public AType<GenericInfoAttr> getAttrType() {
return AType.GENERIC_INFO;
}
......
......@@ -3,12 +3,12 @@ package jadx.core.dex.attributes.nodes;
import java.util.HashSet;
import java.util.Set;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.Utils;
public class IgnoreEdgeAttr implements IAttribute {
public class IgnoreEdgeAttr implements IJadxAttribute {
private final Set<BlockNode> blocks = new HashSet<>(3);
......@@ -21,7 +21,7 @@ public class IgnoreEdgeAttr implements IAttribute {
}
@Override
public AType<IgnoreEdgeAttr> getType() {
public AType<IgnoreEdgeAttr> getAttrType() {
return AType.IGNORE_EDGE;
}
......
......@@ -35,6 +35,12 @@ public abstract class LineAttrNode extends AttrNode {
this.decompiledLine = decompiledLine;
}
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
if (this.getSourceLine() == 0) {
this.setSourceLine(lineAttrNode.getSourceLine());
}
}
public void copyLines(LineAttrNode lineAttrNode) {
setSourceLine(lineAttrNode.getSourceLine());
setDecompiledLine(lineAttrNode.getDecompiledLine());
......
......@@ -4,11 +4,11 @@ import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
public class LocalVarsDebugInfoAttr implements IAttribute {
public class LocalVarsDebugInfoAttr implements IJadxAttribute {
private final List<ILocalVar> localVars;
public LocalVarsDebugInfoAttr(List<ILocalVar> localVars) {
......@@ -20,7 +20,7 @@ public class LocalVarsDebugInfoAttr implements IAttribute {
}
@Override
public AType<LocalVarsDebugInfoAttr> getType() {
public AType<LocalVarsDebugInfoAttr> getAttrType() {
return AType.LOCAL_VARS_DEBUG_INFO;
}
......
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class LoopLabelAttr implements IAttribute {
public class LoopLabelAttr implements IJadxAttribute {
private final LoopInfo loop;
......@@ -16,7 +16,7 @@ public class LoopLabelAttr implements IAttribute {
}
@Override
public AType<LoopLabelAttr> getType() {
public AType<LoopLabelAttr> getAttrType() {
return AType.LOOP_LABEL;
}
......
......@@ -3,15 +3,15 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import java.util.Objects;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
public class MethodInlineAttr implements IAttribute {
public class MethodInlineAttr extends PinnedAttribute {
private static final MethodInlineAttr INLINE_NOT_NEEDED = new MethodInlineAttr(null, null);
......@@ -64,7 +64,7 @@ public class MethodInlineAttr implements IAttribute {
}
@Override
public AType<MethodInlineAttr> getType() {
public AType<MethodInlineAttr> getAttrType() {
return AType.METHOD_INLINE;
}
......
......@@ -3,12 +3,12 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import java.util.SortedSet;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.MethodNode;
public class MethodOverrideAttr implements IAttribute {
public class MethodOverrideAttr extends PinnedAttribute {
/**
* All methods overridden by current method. Current method excluded, empty for base method.
......@@ -46,7 +46,7 @@ public class MethodOverrideAttr implements IAttribute {
}
@Override
public AType<MethodOverrideAttr> getType() {
public AType<MethodOverrideAttr> getAttrType() {
return AType.METHOD_OVERRIDE;
}
......
......@@ -3,8 +3,8 @@ package jadx.core.dex.attributes.nodes;
import java.util.Collections;
import java.util.Set;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
import static jadx.core.utils.Utils.isEmpty;
......@@ -12,7 +12,7 @@ import static jadx.core.utils.Utils.isEmpty;
/**
* Set of known type variables at current method
*/
public class MethodTypeVarsAttr implements IAttribute {
public class MethodTypeVarsAttr implements IJadxAttribute {
private static final MethodTypeVarsAttr EMPTY = new MethodTypeVarsAttr(Collections.emptySet());
public static MethodTypeVarsAttr build(Set<ArgType> typeVars) {
......@@ -33,7 +33,7 @@ public class MethodTypeVarsAttr implements IAttribute {
}
@Override
public AType<MethodTypeVarsAttr> getType() {
public AType<MethodTypeVarsAttr> getAttrType() {
return AType.METHOD_TYPE_VARS;
}
......
......@@ -4,16 +4,16 @@ import java.util.LinkedList;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.PhiInsn;
public class PhiListAttr implements IAttribute {
public class PhiListAttr implements IJadxAttribute {
private final List<PhiInsn> list = new LinkedList<>();
@Override
public AType<PhiListAttr> getType() {
public AType<PhiListAttr> getAttrType() {
return AType.PHI_LIST;
}
......
......@@ -2,11 +2,11 @@ package jadx.core.dex.attributes.nodes;
import java.util.Objects;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
public class RegDebugInfoAttr implements IAttribute {
public class RegDebugInfoAttr implements IJadxAttribute {
private final ArgType type;
private final String name;
......@@ -25,7 +25,7 @@ public class RegDebugInfoAttr implements IAttribute {
}
@Override
public AType<RegDebugInfoAttr> getType() {
public AType<RegDebugInfoAttr> getAttrType() {
return AType.REG_DEBUG_INFO;
}
......
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.IAttribute;
public class RenameReasonAttr implements IAttribute {
public class RenameReasonAttr implements IJadxAttribute {
private String description;
......@@ -57,7 +57,7 @@ public class RenameReasonAttr implements IAttribute {
}
@Override
public AType<RenameReasonAttr> getType() {
public AType<RenameReasonAttr> getAttrType() {
return AType.RENAME_REASON;
}
......
......@@ -4,14 +4,14 @@ import java.util.BitSet;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SkipMethodArgsAttr implements IAttribute {
public class SkipMethodArgsAttr extends PinnedAttribute {
public static void skipArg(MethodNode mth, RegisterArg arg) {
int argNum = Utils.indexInListByRef(mth.getArgRegs(), arg);
......@@ -60,7 +60,7 @@ public class SkipMethodArgsAttr implements IAttribute {
}
@Override
public AType<SkipMethodArgsAttr> getType() {
public AType<SkipMethodArgsAttr> getAttrType() {
return AType.SKIP_MTH_ARGS;
}
......
......@@ -51,6 +51,30 @@ public class AccessInfo {
return new AccessInfo(accFlags & VISIBILITY_FLAGS, type);
}
public boolean isVisibilityWeakerThan(AccessInfo otherAccInfo) {
int thisVis = accFlags & VISIBILITY_FLAGS;
int otherVis = otherAccInfo.accFlags & VISIBILITY_FLAGS;
if (thisVis == otherVis) {
return false;
}
return orderedVisibility(thisVis) < orderedVisibility(otherVis);
}
private static int orderedVisibility(int flag) {
switch (flag) {
case AccessFlags.PRIVATE:
return 1;
case 0: // package-private
return 2;
case AccessFlags.PROTECTED:
return 3;
case AccessFlags.PUBLIC:
return 4;
default:
throw new JadxRuntimeException("Unexpected visibility flag: " + flag);
}
}
public boolean isPublic() {
return (accFlags & AccessFlags.PUBLIC) != 0;
}
......
......@@ -12,8 +12,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
......@@ -84,9 +84,9 @@ public class ConstStorage {
for (FieldNode f : staticFields) {
AccessInfo accFlags = f.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null && fv.isConst() && fv.getEncodedValue().getValue() != null) {
addConstField(cls, f, fv.getEncodedValue().getValue(), accFlags.isPublic());
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
addConstField(cls, f, constVal.getValue(), accFlags.isPublic());
}
}
}
......
......@@ -2,7 +2,7 @@ package jadx.core.dex.info;
import java.util.Objects;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.RootNode;
......@@ -26,9 +26,9 @@ public final class FieldInfo {
return root.getInfoStorage().getField(field);
}
public static FieldInfo fromData(RootNode root, IFieldData fieldData) {
ClassInfo declClass = ClassInfo.fromName(root, fieldData.getParentClassType());
FieldInfo field = new FieldInfo(declClass, fieldData.getName(), ArgType.parse(fieldData.getType()));
public static FieldInfo fromRef(RootNode root, IFieldRef fieldRef) {
ClassInfo declClass = ClassInfo.fromName(root, fieldRef.getParentClassType());
FieldInfo field = new FieldInfo(declClass, fieldRef.getName(), ArgType.parse(fieldRef.getType()));
return root.getInfoStorage().getField(field);
}
......
package jadx.core.dex.instructions;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.custom.IArrayPayload;
import jadx.api.plugins.input.insns.custom.ICustomPayload;
import jadx.api.plugins.input.insns.custom.ISwitchPayload;
import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
......@@ -35,7 +39,7 @@ public class InsnDecoder {
}
public InsnNode[] process(ICodeReader codeReader) {
InsnNode[] instructions = new InsnNode[codeReader.getInsnsCount()];
InsnNode[] instructions = new InsnNode[codeReader.getUnitsCount()];
codeReader.visitInstructions(rawInsn -> {
int offset = rawInsn.getOffset();
InsnNode insn;
......@@ -88,6 +92,14 @@ public class InsnDecoder {
InsnArg.reg(insn, 0, ArgType.NARROW),
InsnArg.reg(insn, 1, ArgType.NARROW));
case MOVE_MULTI:
int len = insn.getRegsCount();
InsnNode mmv = new InsnNode(InsnType.MOVE_MULTI, len);
for (int i = 0; i < len; i++) {
mmv.addArg(InsnArg.reg(insn, i, ArgType.UNKNOWN));
}
return mmv;
case MOVE_WIDE:
return insn(InsnType.MOVE,
InsnArg.reg(insn, 0, ArgType.WIDE),
......@@ -339,31 +351,31 @@ public class InsnDecoder {
ArgType castType = ArgType.parse(insn.getIndexAsType());
InsnNode checkCastInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
checkCastInsn.setResult(InsnArg.reg(insn, 0, castType));
checkCastInsn.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
checkCastInsn.addArg(InsnArg.reg(insn, insn.getRegsCount() == 2 ? 1 : 0, ArgType.UNKNOWN_OBJECT));
return checkCastInsn;
case IGET:
FieldInfo igetFld = FieldInfo.fromData(root, insn.getIndexAsField());
FieldInfo igetFld = FieldInfo.fromRef(root, insn.getIndexAsField());
InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1);
igetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(igetFld)));
igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType()));
return igetInsn;
case IPUT:
FieldInfo iputFld = FieldInfo.fromData(root, insn.getIndexAsField());
FieldInfo iputFld = FieldInfo.fromRef(root, insn.getIndexAsField());
InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2);
iputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(iputFld)));
iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType()));
return iputInsn;
case SGET:
FieldInfo sgetFld = FieldInfo.fromData(root, insn.getIndexAsField());
FieldInfo sgetFld = FieldInfo.fromRef(root, insn.getIndexAsField());
InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0);
sgetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(sgetFld)));
return sgetInsn;
case SPUT:
FieldInfo sputFld = FieldInfo.fromData(root, insn.getIndexAsField());
FieldInfo sputFld = FieldInfo.fromRef(root, insn.getIndexAsField());
InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1);
sputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(sputFld)));
return sputInsn;
......@@ -380,6 +392,8 @@ public class InsnDecoder {
return arrayGet(insn, ArgType.BOOLEAN);
case AGET_BYTE:
return arrayGet(insn, ArgType.BYTE);
case AGET_BYTE_BOOLEAN:
return arrayGet(insn, ArgType.BYTE_BOOLEAN);
case AGET_CHAR:
return arrayGet(insn, ArgType.CHAR);
case AGET_SHORT:
......@@ -395,6 +409,8 @@ public class InsnDecoder {
return arrayPut(insn, ArgType.BOOLEAN);
case APUT_BYTE:
return arrayPut(insn, ArgType.BYTE);
case APUT_BYTE_BOOLEAN:
return arrayPut(insn, ArgType.BYTE_BOOLEAN);
case APUT_CHAR:
return arrayPut(insn, ArgType.CHAR);
case APUT_SHORT:
......@@ -439,15 +455,12 @@ public class InsnDecoder {
return newInstInsn;
case NEW_ARRAY:
ArgType arrType = ArgType.parse(insn.getIndexAsType());
return new NewArrayNode(arrType,
InsnArg.reg(insn, 0, arrType),
InsnArg.typeImmutableReg(insn, 1, ArgType.INT));
return makeNewArray(insn);
case FILL_ARRAY_DATA:
return new FillArrayInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN_ARRAY), insn.getTarget());
case FILL_ARRAY_DATA_PAYLOAD:
return new FillArrayData(((IArrayPayload) insn.getPayload()));
return new FillArrayData(((IArrayPayload) Objects.requireNonNull(insn.getPayload())));
case FILLED_NEW_ARRAY:
return filledNewArray(insn, false);
......@@ -455,9 +468,9 @@ public class InsnDecoder {
return filledNewArray(insn, true);
case PACKED_SWITCH:
return new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), true);
return makeSwitch(insn, true);
case SPARSE_SWITCH:
return new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), false);
return makeSwitch(insn, false);
case PACKED_SWITCH_PAYLOAD:
case SPARSE_SWITCH_PAYLOAD:
......@@ -478,6 +491,29 @@ public class InsnDecoder {
}
}
@NotNull
private SwitchInsn makeSwitch(InsnData insn, boolean packed) {
SwitchInsn swInsn = new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), packed);
ICustomPayload payload = insn.getPayload();
if (payload != null) {
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
}
return swInsn;
}
private InsnNode makeNewArray(InsnData insn) {
ArgType indexType = ArgType.parse(insn.getIndexAsType());
int dim = (int) insn.getLiteral();
ArgType arrType = dim == 0 ? indexType : ArgType.array(indexType, dim);
int regsCount = insn.getRegsCount();
NewArrayNode newArr = new NewArrayNode(arrType, regsCount - 1);
newArr.setResult(InsnArg.reg(insn, 0, arrType));
for (int i = 1; i < regsCount; i++) {
newArr.addArg(InsnArg.typeImmutableReg(insn, i, ArgType.INT));
}
return newArr;
}
private ArgType tryResolveFieldType(FieldInfo igetFld) {
FieldNode fieldNode = root.resolveField(igetFld);
if (fieldNode != null) {
......@@ -531,7 +567,14 @@ public class InsnDecoder {
if (type == InvokeType.CUSTOM) {
return InvokeCustomBuilder.build(method, insn, isRange);
}
MethodInfo mthInfo = MethodInfo.fromRef(root, insn.getIndexAsMethod());
IMethodRef mthRef;
ICustomPayload payload = insn.getPayload();
if (payload != null) {
mthRef = ((IMethodRef) payload);
} else {
mthRef = insn.getIndexAsMethod();
}
MethodInfo mthInfo = MethodInfo.fromRef(root, mthRef);
return new InvokeNode(mthInfo, insn, type, isRange);
}
......
......@@ -11,6 +11,7 @@ public enum InsnType {
NOT,
MOVE,
MOVE_MULTI,
CAST,
RETURN,
......
......@@ -11,6 +11,7 @@ import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.custom.ICustomPayload;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
......@@ -28,7 +29,13 @@ public class InvokeCustomBuilder {
public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange) {
try {
ICallSite callSite = insn.getIndexAsCallSite();
ICallSite callSite;
ICustomPayload payload = insn.getPayload();
if (payload != null) {
callSite = (ICallSite) payload;
} else {
callSite = insn.getIndexAsCallSite();
}
callSite.load();
List<EncodedValue> values = callSite.getValues();
if (!checkLinkerMethod(values)) {
......@@ -38,7 +45,12 @@ public class InvokeCustomBuilder {
if (callMthHandle.getType().isField()) {
throw new JadxRuntimeException("Not yet supported");
}
return buildMethodCall(mth, insn, isRange, values, callMthHandle);
InvokeCustomNode resNode = buildMethodCall(mth, insn, isRange, values, callMthHandle);
int resReg = insn.getResultReg();
if (resReg != -1) {
resNode.setResult(InsnArg.reg(resReg, mth.getReturnType()));
}
return resNode;
} catch (Exception e) {
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
}
......@@ -75,7 +87,6 @@ public class InvokeCustomBuilder {
if (callMth != null) {
invokeCustomNode.getCallInsn().addAttr(callMth);
if (callMth.getAccessFlags().isSynthetic()
&& callMth.getUseIn().size() <= 1
&& callMth.getParentClass().equals(mth.getParentClass())) {
// inline only synthetic methods from same class
callMth.add(AFlag.DONT_GENERATE);
......
......@@ -28,11 +28,14 @@ public class InvokeNode extends BaseInvokeNode {
addReg(r, mth.getDeclClass().getType());
k++;
}
for (ArgType arg : mth.getArgumentsTypes()) {
addReg(isRange ? k : insn.getReg(k), arg);
k += arg.getRegCount();
}
int resReg = insn.getResultReg();
if (resReg != -1) {
setResult(InsnArg.reg(resReg, mth.getReturnType()));
}
}
public InvokeNode(MethodInfo mth, InvokeType invokeType, int argsCount) {
......
package jadx.core.dex.instructions;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
public class NewArrayNode extends InsnNode {
private final ArgType arrType;
public NewArrayNode(@NotNull ArgType arrType, RegisterArg res, InsnArg size) {
this(arrType);
setResult(res);
addArg(size);
}
private NewArrayNode(ArgType arrType) {
super(InsnType.NEW_ARRAY, 1);
public NewArrayNode(ArgType arrType, int argsCount) {
super(InsnType.NEW_ARRAY, argsCount);
this.arrType = arrType;
}
......@@ -26,6 +16,10 @@ public class NewArrayNode extends InsnNode {
return arrType;
}
public int getDimension() {
return arrType.getArrayDimension();
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
......@@ -40,7 +34,7 @@ public class NewArrayNode extends InsnNode {
@Override
public InsnNode copy() {
return copyCommonParams(new NewArrayNode(arrType));
return copyCommonParams(new NewArrayNode(arrType, getArgsCount()));
}
@Override
......
......@@ -16,6 +16,14 @@ public class SwitchData extends InsnNode {
this.targets = payload.getTargets();
}
public void fixTargets(int switchOffset) {
int size = this.size;
int[] targets = this.targets;
for (int i = 0; i < size; i++) {
targets[i] += switchOffset;
}
}
public int getSize() {
return size;
}
......
......@@ -33,16 +33,13 @@ public class SwitchInsn extends TargetInsnNode {
this.packed = packed;
}
public boolean needData() {
return this.switchData == null;
}
public void attachSwitchData(SwitchData data, int def) {
this.switchData = data;
this.def = def;
// fix targets
int switchOffset = getOffset();
int size = data.getSize();
int[] targets = data.getTargets();
for (int i = 0; i < size; i++) {
targets[i] += switchOffset;
}
}
@Override
......
......@@ -67,6 +67,7 @@ public abstract class ArgType {
public static final ArgType INT_FLOAT = unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
public static final ArgType INT_BOOLEAN = unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN);
public static final ArgType BYTE_BOOLEAN = unknown(PrimitiveType.BYTE, PrimitiveType.BOOLEAN);
protected int hash;
......@@ -149,6 +150,17 @@ public abstract class ArgType {
return new ArrayArg(vtype);
}
public static ArgType array(@NotNull ArgType type, int dimension) {
if (dimension == 1) {
return new ArrayArg(type);
}
ArgType arrType = type;
for (int i = 0; i < dimension; i++) {
arrType = new ArrayArg(arrType);
}
return arrType;
}
public static ArgType unknown(PrimitiveType... types) {
return new UnknownArg(types);
}
......
package jadx.core.dex.instructions.mods;
import java.util.Collection;
import java.util.function.Consumer;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
......@@ -27,6 +28,7 @@ public final class TernaryInsn extends InsnNode {
addArg(th);
addArg(els);
}
visitInsns(this::inheritMetadata);
}
private TernaryInsn() {
......@@ -57,6 +59,11 @@ public final class TernaryInsn extends InsnNode {
list.addAll(condition.getRegisterArgs());
}
public void visitInsns(Consumer<InsnNode> visitor) {
super.visitInsns(visitor);
condition.visitInsns(visitor);
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
......
......@@ -18,17 +18,19 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultClassAttr;
import jadx.api.plugins.input.data.attributes.types.InnerClassesAttr;
import jadx.api.plugins.input.data.attributes.types.InnerClsInfo;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.Consts;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.core.dex.attributes.fldinit.FieldInitConstAttr;
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
......@@ -113,11 +115,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
fld -> fields.add(FieldNode.build(this, fld)),
mth -> methods.add(MethodNode.build(this, mth)));
AnnotationsList.attach(this, cls.getAnnotations());
loadStaticValues(cls, fields);
initAccessFlags(cls);
addSourceFilenameAttr(cls.getSourceFile());
addAttrs(cls.getAttributes());
accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS);
initStaticValues(fields);
processAttributes(this);
buildCache();
} catch (Exception e) {
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
......@@ -130,18 +131,36 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.generics = generics;
}
/**
* Restore original access flags from Dalvik annotation if present
*/
private void initAccessFlags(IClassData cls) {
int accFlagsValue;
IAnnotation a = getAnnotation(Consts.DALVIK_INNER_CLASS);
if (a != null) {
accFlagsValue = (Integer) a.getValues().get("accessFlags").getValue();
} else {
accFlagsValue = cls.getAccessFlags();
private static void processAttributes(ClassNode cls) {
// move AnnotationDefault from cls to methods (dex specific)
AnnotationDefaultClassAttr defAttr = cls.get(JadxAttrType.ANNOTATION_DEFAULT_CLASS);
if (defAttr != null) {
cls.remove(JadxAttrType.ANNOTATION_DEFAULT_CLASS);
for (Map.Entry<String, EncodedValue> entry : defAttr.getValues().entrySet()) {
MethodNode mth = cls.searchMethodByShortName(entry.getKey());
if (mth != null) {
mth.addAttr(new AnnotationDefaultAttr(entry.getValue()));
} else {
cls.addWarnComment("Method from annotation default annotation not found: " + entry.getKey());
}
}
}
// check source file attribute
if (!cls.checkSourceFilenameAttr()) {
cls.remove(JadxAttrType.SOURCE_FILE);
}
}
private int getAccessFlags(IClassData cls) {
InnerClassesAttr innerClassesAttr = get(JadxAttrType.INNER_CLASSES);
if (innerClassesAttr != null) {
InnerClsInfo innerClsInfo = innerClassesAttr.getMap().get(cls.getType());
if (innerClsInfo != null) {
return innerClsInfo.getAccessFlags();
}
}
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
return cls.getAccessFlags();
}
public static ClassNode addSyntheticClass(RootNode root, String name, int accessFlags) {
......@@ -164,26 +183,18 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.parentClass = this;
}
private void loadStaticValues(IClassData cls, List<FieldNode> fields) {
private void initStaticValues(List<FieldNode> fields) {
if (fields.isEmpty()) {
return;
}
List<FieldNode> staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList());
for (FieldNode f : staticFields) {
if (f.getAccessFlags().isFinal()) {
if (f.getAccessFlags().isFinal() && f.get(JadxAttrType.CONSTANT_VALUE) == null) {
// incorrect initialization will be removed if assign found in constructor
f.addAttr(FieldInitConstAttr.NULL_VALUE);
f.addAttr(EncodedValue.NULL);
}
}
try {
List<EncodedValue> values = cls.getStaticFieldInitValues();
int count = values.size();
if (count == 0 || count > staticFields.size()) {
return;
}
for (int i = 0; i < count; i++) {
staticFields.get(i).addAttr(FieldInitAttr.constValue(values.get(i)));
}
// process const fields
root().getConstValues().processConstFields(this, staticFields);
} catch (Exception e) {
......@@ -191,26 +202,39 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
}
}
private void addSourceFilenameAttr(String fileName) {
if (fileName == null) {
return;
private boolean checkSourceFilenameAttr() {
SourceFileAttr sourceFileAttr = get(JadxAttrType.SOURCE_FILE);
if (sourceFileAttr == null) {
return true;
}
String fileName = sourceFileAttr.getFileName();
if (fileName.endsWith(".java")) {
fileName = fileName.substring(0, fileName.length() - 5);
}
if (fileName.isEmpty() || fileName.equals("SourceFile")) {
return;
return false;
}
if (clsInfo != null) {
String name = clsInfo.getShortName();
if (fileName.equals(name)) {
return;
return false;
}
ClassInfo parentCls = clsInfo.getParentClass();
while (parentCls != null) {
String parentName = parentCls.getShortName();
if (parentName.equals(fileName) || parentName.startsWith(fileName + '$')) {
return false;
}
parentCls = parentCls.getParentClass();
}
if (fileName.contains("$") && fileName.endsWith('$' + name)) {
return;
return false;
}
if (name.contains("$") && name.startsWith(fileName)) {
return false;
}
}
this.addAttr(new SourceFileAttr(fileName));
return true;
}
public void ensureProcessed() {
......@@ -570,30 +594,30 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return clsInfo.getAliasPkg();
}
public String getSmali() {
public String getDisassembledCode() {
if (smali == null) {
StringBuilder sb = new StringBuilder();
getSmali(sb);
sb.append(System.lineSeparator());
getDisassembledCode(sb);
sb.append(ICodeWriter.NL);
Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
getInnerAndInlinedClassesRecursive(allInlinedClasses);
for (ClassNode innerClass : allInlinedClasses) {
innerClass.getSmali(sb);
sb.append(System.lineSeparator());
innerClass.getDisassembledCode(sb);
sb.append(ICodeWriter.NL);
}
smali = sb.toString();
}
return smali;
}
protected void getSmali(StringBuilder sb) {
if (this.clsData == null) {
protected void getDisassembledCode(StringBuilder sb) {
if (clsData == null) {
sb.append(String.format("###### Class %s is created by jadx", getFullName()));
return;
}
sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName()));
sb.append(System.lineSeparator());
sb.append(this.clsData.getDisassembledCode());
sb.append(ICodeWriter.NL);
sb.append(clsData.getDisassembledCode());
}
public IClassData getClsData() {
......
......@@ -4,7 +4,6 @@ import java.util.Collections;
import java.util.List;
import jadx.api.plugins.input.data.IFieldData;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
......@@ -22,9 +21,9 @@ public class FieldNode extends LineAttrNode implements ICodeNode {
private List<MethodNode> useIn = Collections.emptyList();
public static FieldNode build(ClassNode cls, IFieldData fieldData) {
FieldInfo fieldInfo = FieldInfo.fromData(cls.root(), fieldData);
FieldInfo fieldInfo = FieldInfo.fromRef(cls.root(), fieldData);
FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags());
AnnotationsList.attach(fieldNode, fieldData.getAnnotations());
fieldNode.addAttrs(fieldData.getAttributes());
return fieldNode;
}
......
......@@ -2,13 +2,13 @@ package jadx.core.dex.nodes;
import java.util.List;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.Utils;
public interface IMethodDetails extends IAttribute {
public interface IMethodDetails extends IJadxAttribute {
MethodInfo getMethodInfo();
......@@ -25,7 +25,7 @@ public interface IMethodDetails extends IAttribute {
int getRawAccessFlags();
@Override
default AType<IMethodDetails> getType() {
default AType<IMethodDetails> getAttrType() {
return AType.METHOD_DETAILS;
}
......
......@@ -5,12 +5,14 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
......@@ -290,6 +292,18 @@ public class InsnNode extends LineAttrNode {
return false;
}
/**
* Visit this instruction and all inner (wrapped) instructions
*/
public void visitInsns(Consumer<InsnNode> visitor) {
visitor.accept(this);
for (InsnArg arg : this.getArguments()) {
if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().visitInsns(visitor);
}
}
}
/**
* 'Soft' equals, don't compare arguments, only instruction specific parameters.
*/
......@@ -346,6 +360,11 @@ public class InsnNode extends LineAttrNode {
return copy;
}
public void copyAttributesFrom(InsnNode attrNode) {
super.copyAttributesFrom(attrNode);
this.addSourceLineFrom(attrNode);
}
/**
* Make copy of InsnNode object.
* <p>
......@@ -451,6 +470,21 @@ public class InsnNode extends LineAttrNode {
}
}
public void inheritMetadata(InsnNode sourceInsn) {
if (insnType == InsnType.RETURN) {
this.copyLines(sourceInsn);
if (this.contains(AFlag.SYNTHETIC)) {
this.setOffset(sourceInsn.getOffset());
this.rewriteAttributeFrom(sourceInsn, AType.CODE_COMMENTS);
} else {
this.copyAttributeFrom(sourceInsn, AType.CODE_COMMENTS);
}
} else {
this.copyAttributeFrom(sourceInsn, AType.CODE_COMMENTS);
this.addSourceLineFrom(sourceInsn);
}
}
/**
* Compare instruction only by identity.
*/
......
......@@ -13,13 +13,10 @@ import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IDebugInfo;
import jadx.api.plugins.input.data.IMethodData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.Consts;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr;
import jadx.core.codegen.NameGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.info.AccessInfo;
......@@ -52,11 +49,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
private AccessInfo accFlags;
private final ICodeReader codeReader;
private final boolean methodIsVirtual;
private final int insnsCount;
private boolean noCode;
private int regsCount;
private int argsStartReg;
private boolean loaded;
......@@ -82,8 +79,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
public static MethodNode build(ClassNode classNode, IMethodData methodData) {
MethodNode methodNode = new MethodNode(classNode, methodData);
AnnotationsList.attach(methodNode, methodData.getAnnotations());
MethodParameters.attach(methodNode, methodData.getParamsAnnotations());
methodNode.addAttrs(methodData.getAttributes());
return methodNode;
}
......@@ -91,7 +87,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
this.mthInfo = MethodInfo.fromRef(classNode.root(), mthData.getMethodRef());
this.parentClass = classNode;
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
this.methodIsVirtual = !mthData.isDirect();
ICodeReader codeReader = mthData.getCodeReader();
this.noCode = codeReader == null;
if (noCode) {
......@@ -99,7 +94,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
this.insnsCount = 0;
} else {
this.codeReader = codeReader.copy();
this.insnsCount = codeReader.getInsnsCount();
this.insnsCount = codeReader.getUnitsCount();
}
this.retType = mthInfo.getReturnType();
......@@ -194,6 +189,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
}
this.regsCount = codeReader.getRegistersCount();
this.argsStartReg = codeReader.getArgsStartReg();
initArguments(this.argTypes);
InsnDecoder decoder = new InsnDecoder(this);
this.instructions = decoder.process(codeReader);
......@@ -205,7 +201,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
load();
noCode = false;
}
throw new DecodeException(this, "Load method exception: " + e.getMessage(), e);
throw new DecodeException(this, "Load method exception: "
+ e.getClass().getSimpleName() + ": " + e.getMessage(), e);
}
}
......@@ -240,21 +237,13 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
}
private void initArguments(List<ArgType> args) {
int pos;
if (noCode) {
pos = 1;
} else {
pos = regsCount;
for (ArgType arg : args) {
pos -= arg.getRegCount();
}
}
int pos = getArgsStartPos(args);
TypeUtils typeUtils = root().getTypeUtils();
if (accFlags.isStatic()) {
thisArg = null;
} else {
ArgType thisClsType = typeUtils.expandTypeVariables(this, parentClass.getType());
RegisterArg arg = InsnArg.reg(pos - 1, thisClsType);
RegisterArg arg = InsnArg.reg(pos++, thisClsType);
arg.add(AFlag.THIS);
arg.add(AFlag.IMMUTABLE_TYPE);
thisArg = arg;
......@@ -274,6 +263,23 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
}
}
private int getArgsStartPos(List<ArgType> args) {
if (noCode) {
return 0;
}
if (argsStartReg != -1) {
return argsStartReg;
}
int pos = regsCount;
for (ArgType arg : args) {
pos -= arg.getRegCount();
}
if (!accFlags.isStatic()) {
pos--;
}
return pos;
}
@Override
@NotNull
public List<ArgType> getArgTypes() {
......@@ -480,14 +486,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
}
@Override
@SuppressWarnings("unchecked")
public List<ArgType> getThrows() {
IAnnotation an = getAnnotation(Consts.DALVIK_THROWS);
if (an == null) {
ExceptionsAttr exceptionsAttr = get(JadxAttrType.EXCEPTIONS);
if (exceptionsAttr == null) {
return Collections.emptyList();
}
List<EncodedValue> types = (List<EncodedValue>) an.getDefaultValue().getValue();
return Utils.collectionMap(types, ev -> ArgType.object((String) ev.getValue()));
return Utils.collectionMap(exceptionsAttr.getList(), ArgType::object);
}
/**
......@@ -528,10 +532,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return false;
}
public boolean isVirtual() {
return methodIsVirtual;
}
public int getRegsCount() {
return regsCount;
}
......
......@@ -243,8 +243,9 @@ public class RootNode {
}
public void runPreDecompileStage() {
boolean debugEnabled = LOG.isDebugEnabled();
for (IDexTreeVisitor pass : preDecompilePasses) {
long start = System.currentTimeMillis();
long start = debugEnabled ? System.currentTimeMillis() : 0;
try {
pass.init(this);
} catch (Exception e) {
......@@ -253,7 +254,7 @@ public class RootNode {
for (ClassNode cls : classes) {
DepthTraversal.visit(pass, cls);
}
if (LOG.isDebugEnabled()) {
if (debugEnabled) {
LOG.debug("{} time: {}ms", pass.getClass().getSimpleName(), System.currentTimeMillis() - start);
}
}
......
......@@ -9,12 +9,10 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.Consts;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SignatureAttr;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SignatureParser {
......@@ -43,16 +41,13 @@ public class SignatureParser {
return new SignatureParser(signature);
}
@SuppressWarnings("unchecked")
@Nullable
public static String getSignature(IAttributeNode node) {
IAnnotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null) {
SignatureAttr attr = node.get(JadxAttrType.SIGNATURE);
if (attr == null) {
return null;
}
List<EncodedValue> values = (List<EncodedValue>) a.getDefaultValue().getValue();
List<String> strings = Utils.collectionMap(values, ev -> ((String) ev.getValue()));
return mergeSignature(strings);
return attr.getSignature();
}
private char next() {
......
......@@ -4,9 +4,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
......@@ -253,7 +253,7 @@ public final class IfCondition extends AttrNode {
}
public List<RegisterArg> getRegisterArgs() {
List<RegisterArg> list = new LinkedList<>();
List<RegisterArg> list = new ArrayList<>();
if (mode == Mode.COMPARE) {
compare.getInsn().getRegisterArgs(list);
} else {
......@@ -264,6 +264,14 @@ public final class IfCondition extends AttrNode {
return list;
}
public void visitInsns(Consumer<InsnNode> visitor) {
if (mode == Mode.COMPARE) {
compare.getInsn().visitInsns(visitor);
} else {
args.forEach(arg -> arg.visitInsns(visitor));
}
}
@Nullable
public InsnNode getFirstInsn() {
if (mode == Mode.COMPARE) {
......
package jadx.core.dex.trycatch;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class CatchAttr implements IAttribute {
public class CatchAttr implements IJadxAttribute {
private final TryCatchBlock tryBlock;
......@@ -12,7 +12,7 @@ public class CatchAttr implements IAttribute {
}
@Override
public AType<CatchAttr> getType() {
public AType<CatchAttr> getAttrType() {
return AType.CATCH_BLOCK;
}
......
package jadx.core.dex.trycatch;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class ExcHandlerAttr implements IAttribute {
public class ExcHandlerAttr implements IJadxAttribute {
private final TryCatchBlock tryBlock;
private final ExceptionHandler handler;
......@@ -14,7 +14,7 @@ public class ExcHandlerAttr implements IAttribute {
}
@Override
public AType<ExcHandlerAttr> getType() {
public AType<ExcHandlerAttr> getAttrType() {
return AType.EXC_HANDLER;
}
......
package jadx.core.dex.trycatch;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.BlockNode;
public class SplitterBlockAttr implements IAttribute {
public class SplitterBlockAttr implements IJadxAttribute {
private final BlockNode block;
......@@ -17,7 +17,7 @@ public class SplitterBlockAttr implements IAttribute {
}
@Override
public AType<SplitterBlockAttr> getType() {
public AType<SplitterBlockAttr> getAttrType() {
return AType.SPLITTER_BLOCK;
}
......
......@@ -86,12 +86,11 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
markTryBounds(insnByOffset, tryData, catchBlock);
}
}
}
private static void markTryBounds(InsnNode[] insnByOffset, ITry aTry, TryCatchBlock catchBlock) {
int offset = aTry.getStartAddress();
int end = offset + aTry.getInstructionCount() - 1;
int end = aTry.getEndAddress();
boolean tryBlockStarted = false;
InsnNode insn = null;
......
......@@ -303,15 +303,17 @@ public class ClassModifier extends AbstractVisitor {
return true;
}
/**
* Remove public empty constructors (static or default)
*/
private static void removeEmptyMethods(MethodNode mth) {
AccessInfo af = mth.getAccessFlags();
// remove public empty constructors (static or default)
if (af.isConstructor()
&& (af.isPublic() || af.isStatic())
&& mth.getArgRegs().isEmpty()) {
boolean publicConstructor = af.isConstructor() && af.isPublic();
boolean clsInit = mth.getMethodInfo().isClassInit() && af.isStatic();
if ((publicConstructor || clsInit) && mth.getArgRegs().isEmpty()) {
List<BlockNode> bb = mth.getBasicBlocks();
if (bb == null || bb.isEmpty() || BlockUtils.isAllBlocksEmpty(bb)) {
if (af.isStatic() && mth.getMethodInfo().isClassInit()) {
if (clsInit) {
mth.add(AFlag.DONT_GENERATE);
} else {
// don't remove default constructor if other constructors exists
......
......@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.ConstStringNode;
......@@ -157,7 +156,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
List<RegisterArg> useList = new ArrayList<>(ssaVar.getUseList());
int replaceCount = 0;
for (RegisterArg arg : useList) {
if (canInline(arg) && replaceArg(mth, arg, constArg, constInsn, toRemove)) {
if (canInline(arg) && replaceArg(mth, arg, constArg, constInsn)) {
replaceCount++;
}
}
......@@ -180,7 +179,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
return true;
}
private static boolean replaceArg(MethodNode mth, RegisterArg arg, InsnArg constArg, InsnNode constInsn, List<InsnNode> toRemove) {
private static boolean replaceArg(MethodNode mth, RegisterArg arg, InsnArg constArg, InsnNode constInsn) {
InsnNode useInsn = arg.getParentInsn();
if (useInsn == null) {
return false;
......@@ -224,15 +223,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
return false;
}
}
if (insnType == InsnType.RETURN) {
useInsn.setSourceLine(constInsn.getSourceLine());
if (useInsn.contains(AFlag.SYNTHETIC)) {
useInsn.setOffset(constInsn.getOffset());
useInsn.rewriteAttributeFrom(constInsn, AType.CODE_COMMENTS);
} else {
useInsn.copyAttributeFrom(constInsn, AType.CODE_COMMENTS);
}
}
useInsn.inheritMetadata(constInsn);
return true;
}
......
......@@ -64,6 +64,8 @@ public class ConstructorVisitor extends AbstractVisitor {
remover.addAndUnbind(inv);
return;
}
co.inheritMetadata(inv);
RegisterArg instanceArg = ((RegisterArg) inv.getArg(0));
InsnNode newInstInsn = null;
if (co.isNewInstance()) {
......@@ -97,6 +99,7 @@ public class ConstructorVisitor extends AbstractVisitor {
parentInsn.replaceArg(useArg, resultArg.duplicate());
}
}
co.inheritMetadata(newInstInsn);
}
}
ConstructorInsn replace = processConstructor(mth, co);
......@@ -154,6 +157,7 @@ public class ConstructorVisitor extends AbstractVisitor {
}
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType());
newInsn.setResult(co.getResult().duplicate());
newInsn.inheritMetadata(co);
return newInsn;
}
......
package jadx.core.dex.visitors;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
......@@ -86,13 +87,30 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add(escape(mth.getParentClass() + "." + mth.getMethodInfo().getShortId()));
dot.add("\" {");
BlockNode enterBlock = mth.getEnterBlock();
if (useRegions) {
if (mth.getRegion() == null) {
return;
}
processMethodRegion(mth);
} else {
for (BlockNode block : mth.getBasicBlocks()) {
List<BlockNode> blocks = mth.getBasicBlocks();
if (blocks == null) {
InsnNode[] insnArr = mth.getInstructions();
if (insnArr == null) {
return;
}
BlockNode block = new BlockNode(0, 0);
List<InsnNode> insnList = block.getInstructions();
for (InsnNode insn : insnArr) {
if (insn != null) {
insnList.add(insn);
}
}
enterBlock = block;
blocks = Collections.singletonList(block);
}
for (BlockNode block : blocks) {
processBlock(mth, block, false);
}
}
......@@ -109,7 +127,7 @@ public class DotGraphVisitor extends AbstractVisitor {
}
dot.add("}\"];");
dot.startLine("MethodNode -> ").add(makeName(mth.getEnterBlock())).add(';');
dot.startLine("MethodNode -> ").add(makeName(enterBlock)).add(';');
dot.add(conn.toString());
......@@ -269,6 +287,9 @@ public class DotGraphVisitor extends AbstractVisitor {
StringBuilder str = new StringBuilder();
for (InsnNode insn : block.getInstructions()) {
str.append(escape(insn + " " + insn.getAttributesString()));
if (insn.getSourceLine() != 0) {
str.append(" (LINE:").append(insn.getSourceLine()).append(')');
}
str.append(NL);
}
return str.toString();
......
......@@ -6,9 +6,10 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
......@@ -71,7 +72,7 @@ public class ExtractFieldInit extends AbstractVisitor {
if (field.getDeclClass().equals(cls.getClassInfo())) {
FieldNode fn = cls.searchField(field);
if (fn != null && fn.getAccessFlags().isFinal()) {
fn.remove(AType.FIELD_INIT);
fn.remove(JadxAttrType.CONSTANT_VALUE);
}
}
}
......@@ -90,7 +91,7 @@ public class ExtractFieldInit extends AbstractVisitor {
private static boolean processFields(ClassNode cls, MethodNode classInitMth) {
boolean changed = false;
for (FieldNode field : cls.getFields()) {
if (field.contains(AFlag.DONT_GENERATE) || field.contains(AType.FIELD_INIT)) {
if (field.contains(AFlag.DONT_GENERATE) || field.contains(AType.FIELD_INIT_INSN)) {
continue;
}
if (field.getAccessFlags().isStatic()) {
......@@ -277,6 +278,6 @@ public class ExtractFieldInit extends AbstractVisitor {
private static void addFieldInitAttr(MethodNode classInitMth, FieldNode field, InsnNode insn) {
InsnNode assignInsn = InsnNode.wrapArg(insn.getArg(0));
field.addAttr(FieldInitAttr.insnValue(classInitMth, assignInsn));
field.addAttr(new FieldInitInsnAttr(classInitMth, assignInsn));
}
}
package jadx.core.dex.visitors;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException;
......@@ -37,7 +40,7 @@ public class FixAccessModifiers extends AbstractVisitor {
@Override
public void visit(MethodNode mth) {
if (respectAccessModifiers) {
if (respectAccessModifiers || mth.contains(AFlag.DONT_GENERATE)) {
return;
}
int newVisFlag = fixMethodVisibility(mth);
......@@ -93,27 +96,30 @@ public class FixAccessModifiers extends AbstractVisitor {
}
private static int fixMethodVisibility(MethodNode mth) {
if (mth.isVirtual()) {
// make virtual methods public
return AccessFlags.PUBLIC;
} else {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isAbstract()) {
// make abstract methods public
return AccessFlags.PUBLIC;
}
// enum constructor can't be public
if (accessFlags.isConstructor()
&& accessFlags.isPublic()
&& mth.getParentClass().isEnum()) {
return 0;
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isPublic()) {
return -1;
}
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr != null && !overrideAttr.getOverrideList().isEmpty()) {
// visibility can't be weaker
IMethodDetails parentMD = overrideAttr.getOverrideList().get(0);
AccessInfo parentAccInfo = new AccessInfo(parentMD.getRawAccessFlags(), AccessInfo.AFType.METHOD);
if (accessFlags.isVisibilityWeakerThan(parentAccInfo)) {
return parentAccInfo.getVisibility().rawValue();
}
if (accessFlags.isConstructor() || accessFlags.isStatic()) {
// TODO: make public if used outside
return -1;
}
if (mth.getUseIn().isEmpty()) {
return -1;
}
ClassNode thisTopParentCls = mth.getParentClass().getTopParentClass();
for (MethodNode useMth : mth.getUseIn()) {
ClassNode useInTPCls = useMth.getParentClass().getTopParentClass();
if (!useInTPCls.equals(thisTopParentCls)) {
return AccessFlags.PUBLIC;
}
// make other direct methods private
return AccessFlags.PRIVATE;
}
return -1;
}
}
......@@ -63,19 +63,11 @@ public class MethodInvokeVisitor extends AbstractVisitor {
if (insn.contains(AFlag.DONT_GENERATE)) {
continue;
}
processInsn(mth, insn);
}
}
}
private void processInsn(MethodNode mth, InsnNode insn) {
if (insn instanceof BaseInvokeNode) {
processInvoke(mth, ((BaseInvokeNode) insn));
}
for (InsnArg insnArg : insn.getArguments()) {
if (insnArg instanceof InsnWrapArg) {
InsnNode wrapInsn = ((InsnWrapArg) insnArg).getWrapInsn();
processInsn(mth, wrapInsn);
insn.visitInsns(in -> {
if (in instanceof BaseInvokeNode) {
processInvoke(mth, ((BaseInvokeNode) in));
}
});
}
}
}
......@@ -92,7 +84,6 @@ public class MethodInvokeVisitor extends AbstractVisitor {
}
processUnknown(invokeInsn);
} else {
// parentMth.addComment("JADX DEBUG: got method details: " + mthDetails);
if (mthDetails.isVarArg()) {
ArgType last = Utils.last(mthDetails.getArgTypes());
if (last != null && last.isArray()) {
......
......@@ -14,10 +14,11 @@ import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
......@@ -276,7 +277,7 @@ public class ModVisitor extends AbstractVisitor {
}
private void replaceConstsInAnnotationForAttrNode(ClassNode parentCls, AttrNode attrNode) {
AnnotationsList annotationsList = attrNode.get(AType.ANNOTATION_LIST);
AnnotationsAttr annotationsList = attrNode.get(JadxAttrType.ANNOTATION_LIST);
if (annotationsList == null) {
return;
}
......
......@@ -80,6 +80,7 @@ public class MoveInlineVisitor extends AbstractVisitor {
} else {
replaceArg = moveArg.duplicate();
}
useInsn.inheritMetadata(move);
replaceArg.copyAttributesFrom(useArg);
if (debugInfo != null) {
replaceArg.addAttr(debugInfo);
......
......@@ -49,17 +49,12 @@ public class ProcessInstructionsVisitor extends AbstractVisitor {
switch (insn.getType()) {
case SWITCH:
SwitchInsn sw = (SwitchInsn) insn;
// default case
int nextInsnOffset = getNextInsnOffset(insnByOffset, offset);
if (nextInsnOffset != -1) {
addJump(mth, insnByOffset, offset, nextInsnOffset);
if (sw.needData()) {
attachSwitchData(insnByOffset, offset, sw);
}
int dataTarget = sw.getDataTarget();
InsnNode switchDataInsn = getInsnAtOffset(insnByOffset, dataTarget);
if (switchDataInsn != null && switchDataInsn.getType() == InsnType.SWITCH_DATA) {
sw.attachSwitchData((SwitchData) switchDataInsn, nextInsnOffset);
} else {
throw new JadxRuntimeException("Payload for fill-array not found at " + InsnUtils.formatOffset(dataTarget));
int defCaseOffset = sw.getDefaultCaseOffset();
if (defCaseOffset != -1) {
addJump(mth, insnByOffset, offset, defCaseOffset);
}
for (int target : sw.getTargets()) {
addJump(mth, insnByOffset, offset, target);
......@@ -79,8 +74,10 @@ public class ProcessInstructionsVisitor extends AbstractVisitor {
break;
case INVOKE:
ArgType retType = ((BaseInvokeNode) insn).getCallMth().getReturnType();
mergeMoveResult(insnByOffset, offset, insn, retType);
if (insn.getResult() == null) {
ArgType retType = ((BaseInvokeNode) insn).getCallMth().getReturnType();
mergeMoveResult(insnByOffset, offset, insn, retType);
}
break;
case FILLED_NEW_ARRAY:
......@@ -105,6 +102,19 @@ public class ProcessInstructionsVisitor extends AbstractVisitor {
}
}
private static void attachSwitchData(InsnNode[] insnByOffset, int offset, SwitchInsn sw) {
int nextInsnOffset = getNextInsnOffset(insnByOffset, offset);
int dataTarget = sw.getDataTarget();
InsnNode switchDataInsn = getInsnAtOffset(insnByOffset, dataTarget);
if (switchDataInsn != null && switchDataInsn.getType() == InsnType.SWITCH_DATA) {
SwitchData data = (SwitchData) switchDataInsn;
data.fixTargets(offset);
sw.attachSwitchData(data, nextInsnOffset);
} else {
throw new JadxRuntimeException("Payload for switch not found at " + InsnUtils.formatOffset(dataTarget));
}
}
private static void mergeMoveResult(InsnNode[] insnByOffset, int offset, InsnNode insn, ArgType resType) {
int nextInsnOffset = getNextInsnOffset(insnByOffset, offset);
if (nextInsnOffset == -1) {
......
package jadx.core.dex.visitors;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
......@@ -21,7 +24,6 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
......@@ -53,19 +55,28 @@ public class ReSugarCode extends AbstractVisitor {
if (mth.isNoCode()) {
return;
}
boolean changed = false;
InsnRemover remover = new InsnRemover(mth);
for (BlockNode block : mth.getBasicBlocks()) {
remover.setBlock(block);
List<InsnNode> instructions = block.getInstructions();
int size = instructions.size();
for (int i = 0; i < size; i++) {
changed |= process(mth, instructions, i, remover);
int k = 0;
while (true) {
boolean changed = false;
InsnRemover remover = new InsnRemover(mth);
for (BlockNode block : mth.getBasicBlocks()) {
remover.setBlock(block);
List<InsnNode> instructions = block.getInstructions();
int size = instructions.size();
for (int i = 0; i < size; i++) {
changed |= process(mth, instructions, i, remover);
}
remover.perform();
}
if (changed) {
CodeShrinkVisitor.shrinkMethod(mth);
} else {
break;
}
if (k++ > 100) {
mth.addWarnComment("Reached limit for ReSugarCode iterations");
break;
}
remover.perform();
}
if (changed) {
CodeShrinkVisitor.shrinkMethod(mth);
}
}
......@@ -76,7 +87,7 @@ public class ReSugarCode extends AbstractVisitor {
}
switch (insn.getType()) {
case NEW_ARRAY:
return processNewArray(mth, (NewArrayNode) insn, instructions, remover);
return processNewArray(mth, (NewArrayNode) insn, instructions, i, remover);
case SWITCH:
return processEnumSwitch(mth, (SwitchInsn) insn);
......@@ -90,7 +101,7 @@ public class ReSugarCode extends AbstractVisitor {
* Replace new-array and sequence of array-put to new filled-array instruction.
*/
private static boolean processNewArray(MethodNode mth, NewArrayNode newArrayInsn,
List<InsnNode> instructions, InsnRemover remover) {
List<InsnNode> instructions, int i, InsnRemover remover) {
Object arrayLenConst = InsnUtils.getConstValueByArg(mth.root(), newArrayInsn.getArg(0));
if (!(arrayLenConst instanceof LiteralArg)) {
return false;
......@@ -100,30 +111,33 @@ public class ReSugarCode extends AbstractVisitor {
return false;
}
RegisterArg arrArg = newArrayInsn.getResult();
SSAVar ssaVar = arrArg.getSVar();
List<RegisterArg> useList = ssaVar.getUseList();
List<RegisterArg> useList = arrArg.getSVar().getUseList();
if (useList.size() < len) {
return false;
}
// check sequential array put with increasing index
int putIndex = 0;
for (RegisterArg useArg : useList) {
InsnNode insn = useArg.getParentInsn();
if (checkPutInsn(mth, insn, arrArg, putIndex)) {
putIndex++;
} else {
break;
}
List<InsnNode> arrPuts = useList.stream()
.map(InsnArg::getParentInsn)
.filter(Objects::nonNull)
.filter(insn -> insn.getType() == InsnType.APUT)
.sorted(Comparator.comparingLong(insn -> {
Object constVal = InsnUtils.getConstValueByArg(mth.root(), insn.getArg(1));
if (constVal instanceof LiteralArg) {
return ((LiteralArg) constVal).getLiteral();
}
return -1; // bad value, put at top to fail fast next check
}))
.collect(Collectors.toList());
if (arrPuts.size() != len) {
return false;
}
if (putIndex != len) {
// expect all puts to be in same block
if (!new HashSet<>(instructions).containsAll(arrPuts)) {
return false;
}
List<InsnNode> arrPuts = useList.subList(0, len).stream().map(InsnArg::getParentInsn).collect(Collectors.toList());
// check that all puts in current block
for (InsnNode arrPut : arrPuts) {
int index = InsnList.getIndex(instructions, arrPut);
if (index == -1) {
mth.addDebugComment("Can't convert new array creation: APUT found in different block: " + arrPut);
for (int j = 0; j < len; j++) {
InsnNode insn = arrPuts.get(j);
if (!checkPutInsn(mth, insn, arrArg, j)) {
return false;
}
}
......@@ -134,7 +148,7 @@ public class ReSugarCode extends AbstractVisitor {
filledArr.setResult(arrArg.duplicate());
for (InsnNode put : arrPuts) {
filledArr.addArg(put.getArg(2).duplicate());
filledArr.addArg(replaceConstInArg(mth, put.getArg(2)));
remover.addAndUnbind(put);
}
remover.addAndUnbind(newArrayInsn);
......@@ -145,6 +159,17 @@ public class ReSugarCode extends AbstractVisitor {
return true;
}
private static InsnArg replaceConstInArg(MethodNode mth, InsnArg valueArg) {
if (valueArg.isLiteral()) {
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
return InsnArg.wrapArg(fGet);
}
}
return valueArg.duplicate();
}
private static boolean checkPutInsn(MethodNode mth, InsnNode insn, RegisterArg arrArg, int putIndex) {
if (insn == null || insn.getType() != InsnType.APUT) {
return false;
......
......@@ -406,7 +406,7 @@ public class SimplifyVisitor extends AbstractVisitor {
checkResult(mth, concatInsn);
return concatInsn;
} catch (Exception e) {
LOG.warn("Can't convert string concatenation: {} insn: {}", mth, toStrInsn, e);
mth.addWarnComment("String concatenation convert failed", e);
}
return null;
}
......
......@@ -15,6 +15,7 @@ import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.TargetInsnNode;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
......@@ -52,6 +53,7 @@ public class BlockSplitter extends AbstractVisitor {
splitBasicBlocks(mth);
initBlocksInTargetNodes(mth);
expandMoveMulti(mth);
removeJumpAttr(mth);
removeInsns(mth);
removeEmptyDetachedBlocks(mth);
......@@ -308,6 +310,34 @@ public class BlockSplitter extends AbstractVisitor {
return block;
}
private static void expandMoveMulti(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
List<InsnNode> insnsList = block.getInstructions();
int len = insnsList.size();
for (int i = 0; i < len; i++) {
InsnNode insn = insnsList.get(i);
if (insn.getType() == InsnType.MOVE_MULTI) {
int mvCount = insn.getArgsCount() / 2;
for (int j = 0; j < mvCount; j++) {
InsnNode mv = new InsnNode(InsnType.MOVE, 1);
int startArg = j * 2;
mv.setResult((RegisterArg) insn.getArg(startArg));
mv.addArg(insn.getArg(startArg + 1));
mv.copyAttributesFrom(insn);
if (j == 0) {
mv.setOffset(insn.getOffset());
insnsList.set(i, mv);
} else {
insnsList.add(i + j, mv);
}
}
i += mvCount - 1;
len = insnsList.size();
}
}
}
}
private static void removeJumpAttr(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
......@@ -403,7 +433,6 @@ public class BlockSplitter extends AbstractVisitor {
}
}
}
}
}
......
......@@ -53,18 +53,24 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
private void processDebugInfo(MethodNode mth, IDebugInfo debugInfo) {
InsnNode[] insnArr = mth.getInstructions();
attachSourceLines(debugInfo.getSourceLineMapping(), insnArr);
attachSourceLines(mth, debugInfo.getSourceLineMapping(), insnArr);
attachDebugInfo(mth, debugInfo.getLocalVars(), insnArr);
setMethodSourceLine(mth, insnArr);
}
private void attachSourceLines(Map<Integer, Integer> lineMapping, InsnNode[] insnArr) {
for (InsnNode insn : insnArr) {
if (insn != null) {
Integer sourceLine = lineMapping.get(insn.getOffset());
if (sourceLine != null) {
insn.setSourceLine(sourceLine);
private void attachSourceLines(MethodNode mth, Map<Integer, Integer> lineMapping, InsnNode[] insnArr) {
if (lineMapping.isEmpty()) {
return;
}
for (Map.Entry<Integer, Integer> entry : lineMapping.entrySet()) {
try {
Integer offset = entry.getKey();
InsnNode insn = insnArr[offset];
if (insn != null) {
insn.setSourceLine(entry.getValue());
}
} catch (Exception e) {
mth.addWarnComment("Error attach source line", e);
}
}
}
......@@ -80,7 +86,7 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
ArgType type = getVarType(mth, var);
RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(type, var.getName());
if (start < 0) {
if (start <= 0) {
// attach to method arguments
RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) {
......
......@@ -171,13 +171,19 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
}
SSAVar sVar = ((RegisterArg) condArg).getSVar();
List<RegisterArg> args = sVar.getUseList();
if (args.size() != 3 || args.get(2) != condArg) {
if (args.size() != 3) {
return null;
}
condArg = InsnUtils.getRegFromInsn(args, InsnType.IF);
if (condArg == null) {
return null;
}
RegisterArg arrIndex = InsnUtils.getRegFromInsn(args, InsnType.AGET);
if (arrIndex == null) {
return null;
}
condArg = args.get(0);
RegisterArg arrIndex = args.get(1);
InsnNode arrGetInsn = arrIndex.getParentInsn();
if (arrGetInsn == null || arrGetInsn.getType() != InsnType.AGET || arrGetInsn.containsWrappedInsn()) {
if (arrGetInsn == null || arrGetInsn.containsWrappedInsn()) {
return null;
}
if (!condition.isCompare()) {
......
......@@ -106,7 +106,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
InsnArg thenArg = InsnArg.wrapInsnIntoArg(thenInsn);
InsnArg elseArg = InsnArg.wrapInsnIntoArg(elseInsn);
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), resArg, thenArg, elseArg);
ternInsn.setSourceLine(thenInsn.getSourceLine());
InsnRemover.unbindResult(mth, elseInsn);
......@@ -141,7 +140,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
eb.remove(AFlag.RETURN);
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), null, thenArg, elseArg);
ternInsn.setSourceLine(thenInsn.getSourceLine());
InsnNode retInsn = new InsnNode(InsnType.RETURN, 1);
InsnArg arg = InsnArg.wrapInsnIntoArg(ternInsn);
arg.setType(thenArg.getType());
......@@ -281,7 +279,6 @@ public class TernaryMod implements IRegionIterativeVisitor {
InsnList.remove(block, insn);
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), otherArg);
ternInsn.setSourceLine(insn.getSourceLine());
InsnRemover.unbindAllArgs(mth, phiInsn);
header.getInstructions().clear();
......
......@@ -6,10 +6,7 @@ import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
......@@ -152,27 +149,15 @@ public class CodeShrinkVisitor extends AbstractVisitor {
InsnArg wrappedArg = arg.wrapInstruction(mth, insn, false);
boolean replaced = wrappedArg != null;
if (replaced) {
processCodeComment(insn, arg.getParentInsn());
InsnNode parentInsn = arg.getParentInsn();
if (parentInsn != null) {
parentInsn.inheritMetadata(insn);
}
InsnRemover.removeWithoutUnbind(mth, block, insn);
}
return replaced;
}
private static void processCodeComment(InsnNode insn, @Nullable InsnNode parentInsn) {
if (parentInsn == null) {
return;
}
if (parentInsn.getType() == InsnType.RETURN) {
parentInsn.setSourceLine(insn.getSourceLine());
if (parentInsn.contains(AFlag.SYNTHETIC)) {
parentInsn.setOffset(insn.getOffset());
parentInsn.rewriteAttributeFrom(insn, AType.CODE_COMMENTS);
return;
}
}
parentInsn.copyAttributeFrom(insn, AType.CODE_COMMENTS);
}
private static boolean canMoveBetweenBlocks(MethodNode mth, InsnNode assignInsn, BlockNode assignBlock,
BlockNode useBlock, InsnNode useInsn) {
if (!BlockUtils.isPathExists(assignBlock, useBlock)) {
......@@ -238,6 +223,7 @@ public class CodeShrinkVisitor extends AbstractVisitor {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
wrapInsn.setResult(insn.getResult());
wrapInsn.copyAttributesFrom(insn);
wrapInsn.addSourceLineFrom(insn);
wrapInsn.setOffset(insn.getOffset());
wrapInsn.remove(AFlag.WRAPPED);
block.getInstructions().set(i, wrapInsn);
......
......@@ -73,6 +73,7 @@ public class SSATransform extends AbstractVisitor {
} while (repeatFix);
hidePhiInsns(mth);
removeUnusedInvokeResults(mth);
}
private static void placePhi(MethodNode mth, int regNum, LiveVarAnalysis la) {
......@@ -449,4 +450,18 @@ public class SSATransform extends AbstractVisitor {
}
mth.getSVars().clear();
}
private static void removeUnusedInvokeResults(MethodNode mth) {
Iterator<SSAVar> it = mth.getSVars().iterator();
while (it.hasNext()) {
SSAVar ssaVar = it.next();
if (ssaVar.getUseCount() == 0) {
InsnNode parentInsn = ssaVar.getAssign().getParentInsn();
if (parentInsn != null && parentInsn.getType() == InsnType.INVOKE) {
parentInsn.setResult(null);
it.remove();
}
}
}
}
}
package jadx.core.dex.visitors.usage;
import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.Opcode;
import jadx.api.plugins.input.insns.custom.ICustomPayload;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
......@@ -92,19 +96,45 @@ public class UsageInfoVisitor extends AbstractVisitor {
case FIELD_REF:
insnData.decode();
FieldNode fieldNode = root.resolveField(FieldInfo.fromData(root, insnData.getIndexAsField()));
FieldNode fieldNode = root.resolveField(FieldInfo.fromRef(root, insnData.getIndexAsField()));
if (fieldNode != null) {
usageInfo.fieldUse(mth, fieldNode);
}
break;
case METHOD_REF:
case METHOD_REF: {
insnData.decode();
MethodNode methodNode = root.resolveMethod(MethodInfo.fromRef(root, insnData.getIndexAsMethod()));
IMethodRef mthRef;
ICustomPayload payload = insnData.getPayload();
if (payload != null) {
mthRef = ((IMethodRef) payload);
} else {
mthRef = insnData.getIndexAsMethod();
}
MethodNode methodNode = root.resolveMethod(MethodInfo.fromRef(root, mthRef));
if (methodNode != null) {
usageInfo.methodUse(mth, methodNode);
}
break;
}
case CALL_SITE: {
insnData.decode();
ICallSite callSite;
ICustomPayload payload = insnData.getPayload();
if (payload != null) {
callSite = ((ICallSite) payload);
} else {
callSite = insnData.getIndexAsCallSite();
}
IMethodHandle methodHandle = (IMethodHandle) callSite.getValues().get(4).getValue();
IMethodRef mthRef = methodHandle.getMethodRef();
MethodNode mthNode = root.resolveMethod(MethodInfo.fromRef(root, mthRef));
if (mthNode != null) {
usageInfo.methodUse(mth, mthNode);
}
break;
}
}
}
}
......@@ -641,7 +641,7 @@ public class BlockUtils {
public static void replaceInsn(MethodNode mth, BlockNode block, int i, InsnNode insn) {
InsnNode prevInsn = block.getInstructions().get(i);
insn.copyAttributesFrom(prevInsn);
insn.setSourceLine(prevInsn.getSourceLine());
insn.inheritMetadata(prevInsn);
insn.setOffset(prevInsn.getOffset());
block.getInstructions().set(i, insn);
......
......@@ -6,11 +6,12 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.CodePosition;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
......@@ -88,7 +89,7 @@ public class CodeGenUtils {
}
public static void addSourceFileInfo(ICodeWriter code, ClassNode node) {
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
SourceFileAttr sourceFileAttr = node.get(JadxAttrType.SOURCE_FILE);
if (sourceFileAttr != null) {
String fileName = sourceFileAttr.getFileName();
String topClsName = node.getTopParentClass().getClassInfo().getShortName();
......
......@@ -6,7 +6,6 @@ import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.RootNode;
public class EncodedValueUtils {
......@@ -16,7 +15,7 @@ public class EncodedValueUtils {
* @return LiteralArg, String, ArgType or null
*/
@Nullable
public static Object convertToConstValue(RootNode root, EncodedValue encodedValue) {
public static Object convertToConstValue(EncodedValue encodedValue) {
if (encodedValue == null) {
return null;
}
......
package jadx.core.utils;
import java.util.List;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
......@@ -100,9 +101,9 @@ public class InsnUtils {
LOG.warn("Field {} not found", f);
return null;
}
FieldInitAttr attr = fieldNode.get(AType.FIELD_INIT);
if (attr != null && attr.isConst()) {
return EncodedValueUtils.convertToConstValue(root, attr.getEncodedValue());
EncodedValue constVal = fieldNode.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null) {
return EncodedValueUtils.convertToConstValue(constVal);
}
return null;
......@@ -139,6 +140,17 @@ public class InsnUtils {
return null;
}
@Nullable
public static RegisterArg getRegFromInsn(List<RegisterArg> regs, InsnType insnType) {
for (RegisterArg reg : regs) {
InsnNode parentInsn = reg.getParentInsn();
if (parentInsn != null && parentInsn.getType() == insnType) {
return reg;
}
}
return null;
}
private static InsnNode recursiveInsnCheck(InsnNode insn, InsnType insnType, Predicate<InsnNode> test) {
if (insn.getType() == insnType && test.test(insn)) {
return insn;
......
......@@ -17,7 +17,6 @@ import jadx.core.codegen.ClassGen;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
......@@ -77,10 +76,9 @@ public class AndroidResourcesUtils {
/**
* Force hex format for Android resources ids
*/
@SuppressWarnings("RedundantCast")
public static boolean handleResourceFieldValue(ClassNode cls, ICodeWriter code, EncodedValue encodedValue) {
if (encodedValue.getType() == EncodedType.ENCODED_INT && isResourceClass(cls)) {
code.add(String.format("0x%08x", ((Integer) encodedValue.getValue())));
public static boolean handleResourceFieldValue(ClassNode cls, ICodeWriter code, long lit, ArgType type) {
if (type.equals(ArgType.INT) && isResourceClass(cls)) {
code.add(String.format("0x%08x", lit));
return true;
}
return false;
......@@ -121,8 +119,7 @@ public class AndroidResourcesUtils {
if (rField == null) {
FieldInfo rFieldInfo = FieldInfo.from(typeCls.root(), typeCls.getClassInfo(), resName, ArgType.INT);
rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
EncodedValue value = new EncodedValue(EncodedType.ENCODED_INT, resource.getId());
rField.addAttr(FieldInitAttr.constValue(value));
rField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId()));
typeCls.getFields().add(rField);
if (rClsExists) {
rField.addAttr(AType.COMMENTS, "added by JADX");
......
......@@ -151,7 +151,7 @@ public class FileUtils {
public static Path createTempFileNoDelete(String suffix) {
try {
return Files.createTempFile(TEMP_ROOT_DIR, JADX_TMP_PREFIX, suffix);
return Files.createTempFile(Files.createTempDirectory("jadx-persist"), "jadx-", suffix);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to create temp file with suffix: " + suffix, e);
}
......
package jadx.tests.api;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -18,18 +16,21 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.jar.JarOutputStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
......@@ -47,7 +48,6 @@ import jadx.tests.api.compiler.DynamicCompiler;
import jadx.tests.api.compiler.StaticCompiler;
import jadx.tests.api.utils.TestUtils;
import static jadx.core.utils.files.FileUtils.addFileToJar;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.apache.commons.lang3.StringUtils.rightPad;
import static org.hamcrest.MatcherAssert.assertThat;
......@@ -63,11 +63,18 @@ import static org.junit.jupiter.api.Assertions.fail;
public abstract class IntegrationTest extends TestUtils {
private static final Logger LOG = LoggerFactory.getLogger(IntegrationTest.class);
private static final String TEST_DIRECTORY = "src/test/java";
private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY;
private static final String OUT_DIR = "test-out-tmp";
private static final String DEFAULT_INPUT_PLUGIN = "dx";
/**
* Set 'TEST_INPUT_PLUGIN' env variable to use 'java' or 'dx' input in tests
*/
private static final boolean USE_JAVA_INPUT = Utils.getOrElse(System.getenv("TEST_INPUT_PLUGIN"), DEFAULT_INPUT_PLUGIN).equals("java");
/**
* Run auto check method if defined:
*
......@@ -80,26 +87,20 @@ public abstract class IntegrationTest extends TestUtils {
protected JadxArgs args;
protected boolean deleteTmpFiles;
protected boolean withDebugInfo;
protected boolean unloadCls;
protected boolean compile;
protected boolean useEclipseCompiler;
protected Map<Integer, String> resMap = Collections.emptyMap();
private boolean allowWarnInCode;
private boolean printLineNumbers;
private boolean printSmali;
private boolean printOffsets;
private boolean printDisassemble;
private Boolean useJavaInput = null;
private DynamicCompiler dynamicCompiler;
static {
// needed for post decompile check
AType.SKIP_ON_UNLOAD.addAll(Arrays.asList(
AType.JADX_ERROR,
AType.JADX_WARN,
AType.COMMENTS));
// enable debug checks
DebugChecks.checksEnabled = true;
}
......@@ -108,8 +109,6 @@ public abstract class IntegrationTest extends TestUtils {
@BeforeEach
public void init() {
this.deleteTmpFiles = true;
this.unloadCls = true;
this.withDebugInfo = true;
this.compile = true;
this.useEclipseCompiler = false;
......@@ -141,8 +140,9 @@ public abstract class IntegrationTest extends TestUtils {
public ClassNode getClassNode(Class<?> clazz) {
try {
File jar = getJarForClass(clazz);
return getClassNodeFromFiles(Collections.singletonList(jar), clazz.getName());
List<File> files = compileClass(clazz);
assertThat("File list is empty", files, not(empty()));
return getClassNodeFromFiles(files, clazz.getName());
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
......@@ -181,6 +181,13 @@ public abstract class IntegrationTest extends TestUtils {
protected JadxDecompiler loadFiles(List<File> inputFiles) {
args.setInputFiles(inputFiles);
JadxDecompiler d = new JadxDecompiler(args);
if (isJavaInput()) {
d.getPluginManager().unload("java-convert");
LOG.info("Using java input");
} else {
d.getPluginManager().unload("java-input");
LOG.info("Using dex input");
}
try {
d.load();
} catch (Exception e) {
......@@ -199,25 +206,24 @@ public abstract class IntegrationTest extends TestUtils {
}
protected void decompileAndCheck(List<ClassNode> clsList) {
if (!unloadCls) {
clsList.forEach(cls -> cls.add(AFlag.DONT_UNLOAD_CLASS));
}
clsList.forEach(cls -> cls.add(AFlag.DONT_UNLOAD_CLASS)); // keep error and warning attributes
clsList.forEach(ClassNode::decompile);
for (ClassNode cls : clsList) {
System.out.println("-----------------------------------------------------------");
ICodeInfo code = cls.getCode();
if (printLineNumbers) {
printCodeWithLineNumbers(cls.getCode());
printCodeWithLineNumbers(code);
} else if (printOffsets) {
printCodeWithOffsets(code);
} else {
System.out.println(cls.getCode());
System.out.println(code);
}
}
System.out.println("-----------------------------------------------------------");
if (printSmali) {
if (printDisassemble) {
clsList.forEach(this::printSmali);
}
runChecks(clsList);
}
......@@ -233,7 +239,7 @@ public abstract class IntegrationTest extends TestUtils {
private void printSmali(ClassNode cls) {
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
System.out.println(cls.getSmali());
System.out.println(cls.getDisassembledCode());
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}
......@@ -253,6 +259,23 @@ public abstract class IntegrationTest extends TestUtils {
}
}
private void printCodeWithOffsets(ICodeInfo code) {
String codeStr = code.getCodeStr();
Map<CodePosition, Object> annotations = code.getAnnotations();
String[] lines = codeStr.split(ICodeWriter.NL);
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
int curLine = i + 1;
Object ann = annotations.get(new CodePosition(curLine, 0));
String offsetStr = "";
if (ann instanceof InsnCodeOffset) {
int offset = ((InsnCodeOffset) ann).getOffset();
offsetStr = "/* " + leftPad(String.valueOf(offset), 5) + " */";
}
System.out.println(rightPad(offsetStr, 12) + line);
}
}
private void insertResources(RootNode root) {
if (resMap.isEmpty()) {
return;
......@@ -275,7 +298,10 @@ public abstract class IntegrationTest extends TestUtils {
+ "\n " + Utils.listToString(mthNode.getAttributesStringsList(), "\n "));
}
}
assertThat(cls.getCode().toString(), not(containsString("inconsistent")));
String code = cls.getCode().getCodeStr();
assertThat(code, not(containsString("inconsistent")));
assertThat(code, not(containsString("JADX ERROR")));
}
private boolean hasErrors(IAttributeNode node) {
......@@ -407,36 +433,6 @@ public abstract class IntegrationTest extends TestUtils {
return dynamicCompiler.invoke(cls, methodName, types, args);
}
private File getJarForClass(Class<?> cls) throws IOException {
List<File> files = compileClass(cls);
assertThat("File list is empty", files, not(empty()));
String path = cls.getPackage().getName().replace('.', '/');
File temp = createTempFile(".jar");
try (JarOutputStream jo = new JarOutputStream(new FileOutputStream(temp))) {
for (File file : files) {
addFileToJar(jo, file, path + '/' + file.getName());
}
}
return temp;
}
protected File createTempFile(String suffix) {
try {
Path temp;
if (deleteTmpFiles) {
temp = FileUtils.createTempFile(suffix);
} else {
// don't delete on exit
temp = FileUtils.createTempFileNoDelete(suffix);
System.out.println("Temporary file saved: " + temp.toAbsolutePath());
}
return temp.toFile();
} catch (Exception e) {
throw new AssertionError(e.getMessage());
}
}
private List<File> compileClass(Class<?> cls) throws IOException {
String clsFullName = cls.getName();
String rootClsName;
......@@ -499,10 +495,6 @@ public abstract class IntegrationTest extends TestUtils {
this.compile = false;
}
protected void dontUnloadClass() {
this.unloadCls = false;
}
protected void enableDeobfuscation() {
args.setDeobfuscationOn(true);
args.setDeobfuscationForceSave(true);
......@@ -518,6 +510,22 @@ public abstract class IntegrationTest extends TestUtils {
printLineNumbers = true;
}
protected void printOffsets() {
printOffsets = true;
}
protected void useJavaInput() {
this.useJavaInput = true;
}
protected void useDexInput() {
this.useJavaInput = false;
}
protected boolean isJavaInput() {
return Utils.getOrElse(useJavaInput, USE_JAVA_INPUT);
}
// Use only for debug purpose
@Deprecated
protected void outputCFG() {
......@@ -527,8 +535,8 @@ public abstract class IntegrationTest extends TestUtils {
// Use only for debug purpose
@Deprecated
protected void printSmali() {
this.printSmali = true;
protected void printDisassemble() {
this.printDisassemble = true;
}
// Use only for debug purpose
......@@ -536,10 +544,4 @@ public abstract class IntegrationTest extends TestUtils {
protected void outputRawCFG() {
this.args.setRawCFGOutput(true);
}
// Use only for debug purpose
@Deprecated
protected void notDeleteTmpJar() {
this.deleteTmpFiles = false;
}
}
......@@ -7,6 +7,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import jadx.api.JadxInternalAccess;
import jadx.core.dex.nodes.ClassNode;
......@@ -21,6 +22,12 @@ public abstract class SmaliTest extends IntegrationTest {
private static final String SMALI_TESTS_DIR = "src/test/smali";
private static final String SMALI_TESTS_EXT = ".smali";
@BeforeEach
public void init() {
super.init();
this.useDexInput();
}
protected ClassNode getClassNodeFromSmali(String file, String clsName) {
File smaliFile = getSmaliFile(file);
return getClassNodeFromFiles(Collections.singletonList(smaliFile), clsName);
......
package jadx.tests.api.utils.assertj;
import java.util.Arrays;
import org.assertj.core.api.AbstractStringAssert;
import jadx.api.ICodeWriter;
......@@ -72,4 +74,15 @@ public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions>
System.out.println("-----------------------------------------------------------");
return this;
}
public JadxCodeAssertions containsOneOf(String... substringArr) {
int matches = 0;
for (String substring : substringArr) {
matches += TestUtils.count(actual, substring);
}
if (matches != 1) {
failWithMessage("Expected a only one match from <%s> but was <%d>", Arrays.toString(substringArr), matches);
}
return this;
}
}
......@@ -3,9 +3,9 @@ package jadx.tests.functional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttributeStorage;
import jadx.core.dex.attributes.IAttribute;
import static jadx.core.dex.attributes.AFlag.SYNTHETIC;
import static org.hamcrest.MatcherAssert.assertThat;
......@@ -35,9 +35,9 @@ public class AttributeStorageTest {
public static final AType<TestAttr> TEST = new AType<>();
public static class TestAttr implements IAttribute {
public static class TestAttr implements IJadxAttribute {
@Override
public AType<TestAttr> getType() {
public AType<TestAttr> getAttrType() {
return TEST;
}
}
......
......@@ -6,8 +6,8 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.tests.api.IntegrationTest;
......@@ -56,8 +56,8 @@ public class TestRFieldRestore extends IntegrationTest {
// check 'Button' field
FieldNode buttonField = idCls.searchFieldByName("Button");
assertThat(buttonField, notNullValue());
FieldInitAttr fieldInitAttr = buttonField.get(AType.FIELD_INIT);
Integer buttonValue = (Integer) fieldInitAttr.getEncodedValue().getValue();
EncodedValue constVal = buttonField.get(JadxAttrType.CONSTANT_VALUE);
Integer buttonValue = (Integer) constVal.getValue();
assertThat(buttonValue, is(buttonConstValue));
}
}
......@@ -100,6 +100,7 @@ public class TestAnnotationsMix extends IntegrationTest {
@Test
public void test() {
// useDexInput();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
......
......@@ -13,6 +13,8 @@ public class TestArith extends IntegrationTest {
public static class TestCls {
public static final int F = 7;
public int test(int a) {
a += 2;
use(a);
......
......@@ -25,13 +25,15 @@ public class TestArith4 extends IntegrationTest {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("int k = b & 7;")
.containsOne("return (1 - k) & (k + 1);");
.containsOne("& 255")
.containsOneOf("return (1 - k) & (1 + k);", "return (1 - k) & (k + 1);");
}
@Test
public void testNoDebug() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code();
.code()
.containsOne("& 255");
}
}
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册