diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f3ac276..4001458d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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' diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 14c190b1..5f73db16 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 diff --git a/jadx-cli/build.gradle b/jadx-cli/build.gradle index bbc53325..9c68fe9f 100644 --- a/jadx-cli/build.gradle +++ b/jadx-cli/build.gradle @@ -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' diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index c4691112..17741b20 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -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 { diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 279fd9cd..bea1469d 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -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(); diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 98025579..e6355a66 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -63,7 +63,7 @@ public final class JavaClass implements JavaNode { } public synchronized String getSmali() { - return cls.getSmali(); + return cls.getDisassembledCode(); } /** diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java index c417f009..779f851d 100644 --- a/jadx-core/src/main/java/jadx/core/Consts.java +++ b/jadx-core/src/main/java/jadx/core/Consts.java @@ -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"; diff --git a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java index 1a6571fd..5053311d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java @@ -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 paramList = paramsAnnotations.getParamList(); + public void addForParameter(ICodeWriter code, MethodParamsAttr paramsAnnotations, int n) { + List 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 { diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index aed7a334..c4d62c60 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -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(';'); diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 282ff259..81278fdb 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -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()); } diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 0d743d16..5e91a83c 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -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 args) { - MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS); + MethodParamsAttr paramsAnnotation = mth.get(JadxAttrType.ANNOTATION_MTH_PARAMETERS); int i = 0; Iterator 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 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(); diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java index 8e956b43..b0f21d41 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java index 9b617b11..95dd57e6 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index f0486ef9..1001e2e3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -1,12 +1,7 @@ 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 attribute class implementation */ -@SuppressWarnings("InstantiationOfUtilityClass") -public class AType { +public final class AType implements IJadxAttrType { // class, method, field, insn public static final AType> CODE_COMMENTS = new AType<>(); // class, method, field - public static final AType ANNOTATION_LIST = new AType<>(); public static final AType RENAME_REASON = new AType<>(); // class, method @@ -56,19 +48,17 @@ public class AType { public static final AType> COMMENTS = new AType<>(); // any additional info about decompilation // class - public static final AType SOURCE_FILE = new AType<>(); public static final AType ENUM_CLASS = new AType<>(); public static final AType ENUM_MAP = new AType<>(); public static final AType CLASS_TYPE_VARS = new AType<>(); // field - public static final AType FIELD_INIT = new AType<>(); + public static final AType FIELD_INIT_INSN = new AType<>(); public static final AType FIELD_REPLACE = new AType<>(); // method public static final AType LOCAL_VARS_DEBUG_INFO = new AType<>(); public static final AType METHOD_INLINE = new AType<>(); - public static final AType ANNOTATION_MTH_PARAMETERS = new AType<>(); public static final AType SKIP_MTH_ARGS = new AType<>(); public static final AType METHOD_OVERRIDE = new AType<>(); public static final AType METHOD_TYPE_VARS = new AType<>(); @@ -96,14 +86,4 @@ public class AType { // register public static final AType REG_DEBUG_INFO = new AType<>(); - - public static final Set> 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)); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrList.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrList.java index 4c982ac8..df5f1bca 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrList.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrList.java @@ -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 implements IAttribute { +public class AttrList implements IJadxAttribute { - private final AType> type; + private final IJadxAttrType> type; private final List list = new ArrayList<>(); - public AttrList(AType> type) { + public AttrList(IJadxAttrType> type) { this.type = type; } @@ -20,7 +22,7 @@ public class AttrList implements IAttribute { } @Override - public AType> getType() { + public IJadxAttrType> getAttrType() { return type; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java index 949133e2..268c28e6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java @@ -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 void addAttr(AType> type, T obj) { + public void addAttrs(List list) { + initStorage().add(list); + } + + @Override + public void addAttr(IJadxAttrType> type, T obj) { initStorage().add(type, obj); } @@ -34,8 +41,8 @@ public abstract class AttrNode implements IAttributeNode { } @Override - public void copyAttributeFrom(AttrNode attrNode, AType attrType) { - IAttribute attr = attrNode.get(attrType); + public void copyAttributeFrom(AttrNode attrNode, AType 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 void rewriteAttributeFrom(AttrNode attrNode, AType attrType) { + public void rewriteAttributeFrom(AttrNode attrNode, AType attrType) { remove(attrType); copyAttributeFrom(attrNode, attrType); } @@ -71,12 +78,12 @@ public abstract class AttrNode implements IAttributeNode { } @Override - public boolean contains(AType type) { + public boolean contains(IJadxAttrType type) { return storage.contains(type); } @Override - public T get(AType type) { + public T get(IJadxAttrType type) { return storage.get(type); } @@ -86,7 +93,7 @@ public abstract class AttrNode implements IAttributeNode { } @Override - public List getAll(AType> type) { + public List getAll(IJadxAttrType> type) { return storage.getAll(type); } @@ -97,13 +104,13 @@ public abstract class AttrNode implements IAttributeNode { } @Override - public void remove(AType type) { + public void remove(IJadxAttrType 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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java index adcf6c63..6076b76d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttributeStorage.java @@ -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 flags; - private Map, IAttribute> attributes; + private Map, IJadxAttribute> attributes; public AttributeStorage() { flags = EnumSet.noneOf(AFlag.class); attributes = Collections.emptyMap(); } + public AttributeStorage(List 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(AType> type, T obj) { + public void add(List list) { + Map, IJadxAttribute> map = writeAttributes(); + for (IJadxAttribute attr : list) { + map.put(attr.getAttrType(), attr); + } + } + + public void add(IJadxAttrType> type, T obj) { AttrList list = get(type); if (list == null) { list = new AttrList<>(type); @@ -61,21 +76,21 @@ public class AttributeStorage { return flags.contains(flag); } - public boolean contains(AType type) { + public boolean contains(IJadxAttrType type) { return attributes.containsKey(type); } @SuppressWarnings("unchecked") - public T get(AType type) { + public T get(IJadxAttrType 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 List getAll(AType> type) { + public List getAll(IJadxAttrType> type) { AttrList attrList = get(type); if (attrList == null) { return Collections.emptyList(); @@ -87,23 +102,23 @@ public class AttributeStorage { flags.remove(flag); } - public void remove(AType type) { + public void remove(IJadxAttrType type) { if (!attributes.isEmpty()) { attributes.remove(type); } } - public void remove(IAttribute attr) { + public void remove(IJadxAttribute attr) { if (!attributes.isEmpty()) { - AType type = attr.getType(); - IAttribute a = attributes.get(type); + IJadxAttrType type = attr.getAttrType(); + IJadxAttribute a = attributes.get(type); if (a == attr) { attributes.remove(type); } } } - private Map, IAttribute> writeAttributes() { + private Map, IJadxAttribute> writeAttributes() { if (attributes.isEmpty()) { attributes = new IdentityHashMap<>(5); } @@ -121,8 +136,7 @@ public class AttributeStorage { if (attributes.isEmpty()) { return; } - Set> skipOnUnload = AType.SKIP_ON_UNLOAD; - attributes.keySet().removeIf(attrType -> !skipOnUnload.contains(attrType)); + attributes.entrySet().removeIf(entry -> !entry.getValue().keepLoaded()); } public List 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; diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/EmptyAttrStorage.java b/jadx-core/src/main/java/jadx/core/dex/attributes/EmptyAttrStorage.java index e449bb49..54676aa6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/EmptyAttrStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/EmptyAttrStorage.java @@ -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 boolean contains(AType type) { + public boolean contains(IJadxAttrType type) { return false; } @Override - public T get(AType type) { + public T get(IJadxAttrType type) { return null; } @@ -28,7 +30,7 @@ public final class EmptyAttrStorage extends AttributeStorage { } @Override - public List getAll(AType> type) { + public List getAll(IJadxAttrType> type) { return Collections.emptyList(); } @@ -43,12 +45,12 @@ public final class EmptyAttrStorage extends AttributeStorage { } @Override - public void remove(AType type) { + public void remove(IJadxAttrType type) { // ignore } @Override - public void remove(IAttribute attr) { + public void remove(IJadxAttribute attr) { // ignore } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitInsnAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitInsnAttr.java similarity index 50% rename from jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitInsnAttr.java rename to jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitInsnAttr.java index b6cb95a4..5f378f2b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitInsnAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitInsnAttr.java @@ -1,32 +1,33 @@ -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 getAttrType() { + return AType.FIELD_INIT_INSN; } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttribute.java b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttribute.java deleted file mode 100644 index 9eb94d8e..00000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttribute.java +++ /dev/null @@ -1,9 +0,0 @@ -package jadx.core.dex.attributes; - -public interface IAttribute { - AType getType(); - - default String toAttrString() { - return this.toString(); - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java index df3985ac..ccb4e135 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/IAttributeNode.java @@ -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); - void addAttr(AType> type, T obj); + void addAttrs(List list); + + void addAttr(IJadxAttrType> type, T obj); void copyAttributesFrom(AttrNode attrNode); - void copyAttributeFrom(AttrNode attrNode, AType attrType); + void copyAttributeFrom(AttrNode attrNode, AType attrType); - void rewriteAttributeFrom(AttrNode attrNode, AType attrType); + void rewriteAttributeFrom(AttrNode attrNode, AType attrType); boolean contains(AFlag flag); - boolean contains(AType type); + boolean contains(IJadxAttrType type); - T get(AType type); + T get(IJadxAttrType type); IAnnotation getAnnotation(String cls); - List getAll(AType> type); + List getAll(IJadxAttrType> type); void remove(AFlag flag); - void remove(AType type); + void remove(IJadxAttrType type); - void removeAttr(IAttribute attr); + void removeAttr(IJadxAttribute attr); void clearAttributes(); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/AnnotationsList.java b/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/AnnotationsList.java deleted file mode 100644 index 3562d2d3..00000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/AnnotationsList.java +++ /dev/null @@ -1,68 +0,0 @@ -package jadx.core.dex.attributes.annotations; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.jetbrains.annotations.Nullable; - -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 AnnotationsList implements IAttribute { - - public static void attach(ICodeNode node, List annotationList) { - AnnotationsList attrList = pack(annotationList); - if (attrList != null) { - node.addAttr(attrList); - } - } - - @Nullable - public static AnnotationsList pack(List annotationList) { - if (annotationList.isEmpty()) { - return null; - } - Map annMap = new HashMap<>(annotationList.size()); - for (IAnnotation ann : annotationList) { - annMap.put(ann.getAnnotationClass(), ann); - } - return new AnnotationsList(annMap); - } - - private final Map map; - - public AnnotationsList(Map map) { - this.map = map; - } - - public IAnnotation get(String className) { - return map.get(className); - } - - public Collection getAll() { - return map.values(); - } - - public int size() { - return map.size(); - } - - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public AType getType() { - return AType.ANNOTATION_LIST; - } - - @Override - public String toString() { - return Utils.listToString(map.values()); - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/MethodParameters.java b/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/MethodParameters.java deleted file mode 100644 index 42d7e84f..00000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/MethodParameters.java +++ /dev/null @@ -1,44 +0,0 @@ -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> annotationRefList) { - if (annotationRefList.isEmpty()) { - return; - } - List list = new ArrayList<>(annotationRefList.size()); - for (List annList : annotationRefList) { - list.add(AnnotationsList.pack(annList)); - } - node.addAttr(new MethodParameters(list)); - } - - private final List paramList; - - public MethodParameters(List paramsList) { - this.paramList = paramsList; - } - - public List getParamList() { - return paramList; - } - - @Override - public AType getType() { - return AType.ANNOTATION_MTH_PARAMETERS; - } - - @Override - public String toString() { - return Utils.listToString(paramList); - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitAttr.java deleted file mode 100644 index 43bc9108..00000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitAttr.java +++ /dev/null @@ -1,49 +0,0 @@ -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 getType() { - return AType.FIELD_INIT; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitConstAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitConstAttr.java deleted file mode 100644 index 48232109..00000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/fldinit/FieldInitConstAttr.java +++ /dev/null @@ -1,30 +0,0 @@ -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 + '}'; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java index f6cbe808..7cff77d0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ClassTypeVarsAttr.java @@ -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 getType() { + public AType getAttrType() { return AType.CLASS_TYPE_VARS; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/DeclareVariablesAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/DeclareVariablesAttr.java index c0d988dd..dada8886 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/DeclareVariablesAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/DeclareVariablesAttr.java @@ -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 vars = new ArrayList<>(); @@ -24,7 +24,7 @@ public class DeclareVariablesAttr implements IAttribute { } @Override - public AType getType() { + public AType getAttrType() { return AType.DECLARE_VARIABLES; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EdgeInsnAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EdgeInsnAttr.java index 41324d9f..f90b66e3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EdgeInsnAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EdgeInsnAttr.java @@ -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> getType() { + public AType> getAttrType() { return AType.EDGE_INSN; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EnumClassAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EnumClassAttr.java index 122d73ca..1ed47812 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EnumClassAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EnumClassAttr.java @@ -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 getType() { + public AType getAttrType() { return AType.ENUM_CLASS; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EnumMapAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EnumMapAttr.java index 5bdd8f1a..dacaba6e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EnumMapAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EnumMapAttr.java @@ -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 map = new HashMap<>(); @@ -51,7 +51,7 @@ public class EnumMapAttr implements IAttribute { } @Override - public AType getType() { + public AType getAttrType() { return AType.ENUM_MAP; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/FieldReplaceAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/FieldReplaceAttr.java index 62c7201b..a031f8c8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/FieldReplaceAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/FieldReplaceAttr.java @@ -1,11 +1,11 @@ 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 getType() { + public AType getAttrType() { return AType.FIELD_REPLACE; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ForceReturnAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ForceReturnAttr.java index a7e59005..652fb762 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ForceReturnAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/ForceReturnAttr.java @@ -1,11 +1,11 @@ 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 getType() { + public AType getAttrType() { return AType.FORCE_RETURN; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/GenericInfoAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/GenericInfoAttr.java index 91e96df4..fcf0d4b4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/GenericInfoAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/GenericInfoAttr.java @@ -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 genericTypes; private boolean explicit; @@ -27,7 +27,7 @@ public class GenericInfoAttr implements IAttribute { } @Override - public AType getType() { + public AType getAttrType() { return AType.GENERIC_INFO; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java index 5d66bccc..9100becc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java @@ -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 blocks = new HashSet<>(3); @@ -21,7 +21,7 @@ public class IgnoreEdgeAttr implements IAttribute { } @Override - public AType getType() { + public AType getAttrType() { return AType.IGNORE_EDGE; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LineAttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LineAttrNode.java index 03efffa6..5cc782e0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LineAttrNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LineAttrNode.java @@ -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()); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java index 60380e2a..1645cfc4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LocalVarsDebugInfoAttr.java @@ -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 localVars; public LocalVarsDebugInfoAttr(List localVars) { @@ -20,7 +20,7 @@ public class LocalVarsDebugInfoAttr implements IAttribute { } @Override - public AType getType() { + public AType getAttrType() { return AType.LOCAL_VARS_DEBUG_INFO; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopLabelAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopLabelAttr.java index 15381f39..e6a75c9d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopLabelAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopLabelAttr.java @@ -1,9 +1,9 @@ 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 getType() { + public AType getAttrType() { return AType.LOOP_LABEL; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java index 27b6e428..89fe1102 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java @@ -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 getType() { + public AType getAttrType() { return AType.METHOD_INLINE; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java index daceb9c0..a6dee964 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java @@ -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 getType() { + public AType getAttrType() { return AType.METHOD_OVERRIDE; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodTypeVarsAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodTypeVarsAttr.java index ddc8eddc..01a281f8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodTypeVarsAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodTypeVarsAttr.java @@ -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 typeVars) { @@ -33,7 +33,7 @@ public class MethodTypeVarsAttr implements IAttribute { } @Override - public AType getType() { + public AType getAttrType() { return AType.METHOD_TYPE_VARS; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/PhiListAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/PhiListAttr.java index 43e59cf5..a0a9055b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/PhiListAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/PhiListAttr.java @@ -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 list = new LinkedList<>(); @Override - public AType getType() { + public AType getAttrType() { return AType.PHI_LIST; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java index 3a3d8fe6..78915710 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RegDebugInfoAttr.java @@ -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 getType() { + public AType getAttrType() { return AType.REG_DEBUG_INFO; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java index 02818229..9d8be13d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java @@ -1,10 +1,10 @@ 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 getType() { + public AType getAttrType() { return AType.RENAME_REASON; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java index a5d483cd..c7c743ba 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java @@ -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 getType() { + public AType getAttrType() { return AType.SKIP_MTH_ARGS; } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SourceFileAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SourceFileAttr.java deleted file mode 100644 index 4cc8c853..00000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/SourceFileAttr.java +++ /dev/null @@ -1,27 +0,0 @@ -package jadx.core.dex.attributes.nodes; - -import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.IAttribute; - -public class SourceFileAttr implements IAttribute { - - private final String fileName; - - public SourceFileAttr(String fileName) { - this.fileName = fileName; - } - - public String getFileName() { - return fileName; - } - - @Override - public AType getType() { - return AType.SOURCE_FILE; - } - - @Override - public String toString() { - return "SOURCE:" + fileName; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java index bb3f82ef..6c37680e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index e2eb4250..e9b400df 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -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()); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java index 5d8bb84f..8e28ebb0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java index bed5046d..9706f864 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java @@ -1,12 +1,16 @@ 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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java index 448ba16d..88461799 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnType.java @@ -11,6 +11,7 @@ public enum InsnType { NOT, MOVE, + MOVE_MULTI, CAST, RETURN, diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java index a8f95eee..bc28f80e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java @@ -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 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); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java index b0061b27..5c4dc1aa 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/NewArrayNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/NewArrayNode.java index a52cc9aa..cad0bb2f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/NewArrayNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/NewArrayNode.java @@ -1,24 +1,14 @@ 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 diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java index fab77a83..b96c76af 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java index f2823927..423ff4ad 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java @@ -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 diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index 67136ef2..3f43e7d4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java index f80f2ccb..36e0c703 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/TernaryInsn.java @@ -1,6 +1,7 @@ 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 visitor) { + super.visitInsns(visitor); + condition.visitInsns(visitor); + } + @Override public boolean isSame(InsnNode obj) { if (this == obj) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 9c2dfb95..b42c2b5e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -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 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()); + } + } } - this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS); + + // 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(); + } + } + 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 fields) { + private void initStaticValues(List fields) { if (fields.isEmpty()) { return; } List 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 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 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() { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index 1c30a1fa..e2fde72f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -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 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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java index 8324506d..db94e354 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IMethodDetails.java @@ -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 getType() { + default AType getAttrType() { return AType.METHOD_DETAILS; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java index 185d459c..9d1a20c4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java @@ -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 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. *

@@ -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. */ diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 85d94fa6..e5b4d93e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -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 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 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 getArgTypes() { @@ -480,14 +486,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, } @Override - @SuppressWarnings("unchecked") public List getThrows() { - IAnnotation an = getAnnotation(Consts.DALVIK_THROWS); - if (an == null) { + ExceptionsAttr exceptionsAttr = get(JadxAttrType.EXCEPTIONS); + if (exceptionsAttr == null) { return Collections.emptyList(); } - List types = (List) 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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index fbf3151f..bcfe013f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java index 6e5d82e6..cf80617e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java @@ -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 values = (List) a.getDefaultValue().getValue(); - List strings = Utils.collectionMap(values, ev -> ((String) ev.getValue())); - return mergeSignature(strings); + return attr.getSignature(); } private char next() { diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfCondition.java b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfCondition.java index 31faa8bf..1d5d49ac 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfCondition.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfCondition.java @@ -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 getRegisterArgs() { - List list = new LinkedList<>(); + List 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 visitor) { + if (mode == Mode.COMPARE) { + compare.getInsn().visitInsns(visitor); + } else { + args.forEach(arg -> arg.visitInsns(visitor)); + } + } + @Nullable public InsnNode getFirstInsn() { if (mode == Mode.COMPARE) { diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/CatchAttr.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/CatchAttr.java index 0187293f..49a58e43 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/CatchAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/CatchAttr.java @@ -1,9 +1,9 @@ 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 getType() { + public AType getAttrType() { return AType.CATCH_BLOCK; } diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/ExcHandlerAttr.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/ExcHandlerAttr.java index 2ccb232b..081a6fbd 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/ExcHandlerAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/ExcHandlerAttr.java @@ -1,9 +1,9 @@ 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 getType() { + public AType getAttrType() { return AType.EXC_HANDLER; } diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/SplitterBlockAttr.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/SplitterBlockAttr.java index 561091c6..16ed8f14 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/SplitterBlockAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/SplitterBlockAttr.java @@ -1,10 +1,10 @@ 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 getType() { + public AType getAttrType() { return AType.SPLITTER_BLOCK; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java index 943d795f..fca03fcd 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index d0b63467..859d8299 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -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 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 diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java index 4f76ecb8..e568c5db 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java @@ -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 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 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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java index b20a1645..c1c8a72c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java index 9fcdf769..0637301e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java @@ -1,6 +1,7 @@ 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 blocks = mth.getBasicBlocks(); + if (blocks == null) { + InsnNode[] insnArr = mth.getInstructions(); + if (insnArr == null) { + return; + } + BlockNode block = new BlockNode(0, 0); + List 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(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java index 2dc7d16a..0b1ab60f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java @@ -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)); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java b/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java index 44fabb97..8ccb4b7e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java @@ -1,10 +1,13 @@ 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 + 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 (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; } - // enum constructor can't be public - if (accessFlags.isConstructor() - && accessFlags.isPublic() - && mth.getParentClass().isEnum()) { - return 0; - } - if (accessFlags.isConstructor() || accessFlags.isStatic()) { - // TODO: make public if used outside - return -1; - } - // make other direct methods private - return AccessFlags.PRIVATE; } + return -1; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java index a7485d06..744d092c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java @@ -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()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index a2d0fcc9..2f8c1215 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java index d0170545..41e52a7a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java index e48fa12a..d0a62a6d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java index eaeab330..2c7305a7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ReSugarCode.java @@ -1,6 +1,9 @@ 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 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 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 instructions, InsnRemover remover) { + List 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 useList = ssaVar.getUseList(); + List 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; - } - } - if (putIndex != len) { + List 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; } - List 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); + // expect all puts to be in same block + if (!new HashSet<>(instructions).containsAll(arrPuts)) { + return false; + } + + 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; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java index e263429c..c50065f2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java index c73b6555..f9498f02 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java @@ -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 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 { } } } - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java index 910003f6..523cbb76 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java @@ -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 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 lineMapping, InsnNode[] insnArr) { + if (lineMapping.isEmpty()) { + return; + } + for (Map.Entry 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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java index c22a89e0..c6c771da 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java @@ -171,13 +171,19 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor } SSAVar sVar = ((RegisterArg) condArg).getSVar(); List 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()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java index 36b0042f..cbfbc1c4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java @@ -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(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java index 04ccea06..916dfb61 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java index 6cc68894..142e4710 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java @@ -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 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(); + } + } + } + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java index 8539dc56..f8c45460 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java @@ -1,8 +1,12 @@ 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; + } } } } diff --git a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java index 122587a9..9de1f368 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java b/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java index 01c37494..89846258 100644 --- a/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java @@ -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(); diff --git a/jadx-core/src/main/java/jadx/core/utils/EncodedValueUtils.java b/jadx-core/src/main/java/jadx/core/utils/EncodedValueUtils.java index 26e66537..05f53163 100644 --- a/jadx-core/src/main/java/jadx/core/utils/EncodedValueUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/EncodedValueUtils.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java index 3edde3c4..78c6c012 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -1,14 +1,15 @@ 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 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 test) { if (insn.getType() == insnType && test.test(insn)) { return insn; diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java index ea0395b1..99777145 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java @@ -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"); diff --git a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java index 7a40139f..4d83857b 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java @@ -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); } diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index a6ff5989..2a6e2765 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -1,13 +1,11 @@ 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 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 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 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 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 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 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 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; - } } diff --git a/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java b/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java index 78a9c3cd..f6e9dd30 100644 --- a/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java index b0872b4c..bc244e7c 100644 --- a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java @@ -1,5 +1,7 @@ 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 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; + } } diff --git a/jadx-core/src/test/java/jadx/tests/functional/AttributeStorageTest.java b/jadx-core/src/test/java/jadx/tests/functional/AttributeStorageTest.java index 2cfe06c7..a44d9d1e 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/AttributeStorageTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/AttributeStorageTest.java @@ -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 TEST = new AType<>(); - public static class TestAttr implements IAttribute { + public static class TestAttr implements IJadxAttribute { @Override - public AType getType() { + public AType getAttrType() { return TEST; } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java index 7edf8956..e7f3460d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java @@ -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)); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/annotations/TestAnnotationsMix.java b/jadx-core/src/test/java/jadx/tests/integration/annotations/TestAnnotationsMix.java index 57e41156..66e7b09d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/annotations/TestAnnotationsMix.java +++ b/jadx-core/src/test/java/jadx/tests/integration/annotations/TestAnnotationsMix.java @@ -100,6 +100,7 @@ public class TestAnnotationsMix extends IntegrationTest { @Test public void test() { + // useDexInput(); ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith.java b/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith.java index ccd2b2c4..f52cf6d8 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith.java +++ b/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith.java @@ -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); diff --git a/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith4.java b/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith4.java index 430abccb..ed00691b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith4.java +++ b/jadx-core/src/test/java/jadx/tests/integration/arith/TestArith4.java @@ -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"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayFillConstReplace.java b/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayFillConstReplace.java index cad78ca2..d5f555c4 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayFillConstReplace.java +++ b/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayFillConstReplace.java @@ -2,11 +2,9 @@ package jadx.tests.integration.arrays; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestArrayFillConstReplace extends IntegrationTest { @@ -20,9 +18,9 @@ public class TestArrayFillConstReplace extends IntegrationTest { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("return new int[]{127, 129, CONST_INT};")); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne(" int CONST_INT = 65535;") + .containsOne("return new int[]{127, 129, CONST_INT};"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestElseIf.java b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestElseIf.java index f8931f4d..f3de85e2 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestElseIf.java +++ b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestElseIf.java @@ -10,6 +10,7 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +@SuppressWarnings("IfCanBeSwitch") public class TestElseIf extends IntegrationTest { public static class TestCls { @@ -25,6 +26,7 @@ public class TestElseIf extends IntegrationTest { r = 4; } else { r = -1; + System.out.println(); } r = r * 10; return Math.abs(r); diff --git a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernaryInIf.java b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernaryInIf.java index aa207e63..d21e402c 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernaryInIf.java +++ b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernaryInIf.java @@ -2,13 +2,9 @@ package jadx.tests.integration.conditions; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestTernaryInIf extends IntegrationTest { @@ -24,12 +20,14 @@ public class TestTernaryInIf extends IntegrationTest { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("return a ? b : c;")); - assertThat(code, containsOne("return (a ? b : c) ? 1 : 2;")); - assertThat(code, not(containsString("if"))); - assertThat(code, not(containsString("else"))); + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("if") + .doesNotContain("else") + .containsOne("return a ? b : c;") + .containsOneOf( + "return (a ? b : c) ? 1 : 2;", + "return (a ? !b : !c) ? 2 : 1;" // TODO: simplify this + ); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestLineNumbers2.java b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestLineNumbers2.java index 893cf8f0..8cf551f5 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestLineNumbers2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestLineNumbers2.java @@ -1,7 +1,6 @@ package jadx.tests.integration.debuginfo; import java.lang.ref.WeakReference; -import java.util.Map; import org.junit.jupiter.api.Test; @@ -15,7 +14,7 @@ public class TestLineNumbers2 extends IntegrationTest { public static class TestCls { private WeakReference f; - // keep at line 18 + // keep constructor at line 18 public TestCls(TestCls s) { } @@ -38,8 +37,12 @@ public class TestLineNumbers2 extends IntegrationTest { printLineNumbers(); ClassNode cls = getClassNode(TestCls.class); - Map lineMapping = cls.getCode().getLineMapping(); - assertEquals("{6=17, 9=18, 12=22, 13=23, 14=24, 15=28, 17=25, 18=26, 19=28, 22=31, 23=32}", - lineMapping.toString()); + String linesMapStr = cls.getCode().getLineMapping().toString(); + if (isJavaInput()) { + assertEquals("{6=16, 9=17, 12=21, 13=22, 14=23, 16=25, 18=27, 21=30}", linesMapStr); + } else { + // TODO: invert condition to match source lines + assertEquals("{6=16, 9=17, 12=21, 13=22, 14=23, 15=27, 17=24, 18=25, 19=27, 22=30, 23=31}", linesMapStr); + } } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java index 58b064c8..5f353d99 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java +++ b/jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java @@ -2,7 +2,6 @@ package jadx.tests.integration.debuginfo; import org.junit.jupiter.api.Test; -import jadx.NotYetImplemented; import jadx.api.ICodeInfo; import jadx.api.ICodeWriter; import jadx.core.dex.attributes.nodes.LineAttrNode; @@ -51,28 +50,24 @@ public class TestReturnSourceLine extends IntegrationTest { @Test public void test() { + printLineNumbers(); + ClassNode cls = getClassNode(TestCls.class); ICodeInfo codeInfo = cls.getCode(); - String code = codeInfo.toString(); - String[] lines = code.split(ICodeWriter.NL); + String[] lines = codeInfo.getCodeStr().split(ICodeWriter.NL); MethodNode test1 = cls.searchMethodByShortId("test1(Z)I"); checkLine(lines, codeInfo, test1, 3, "return 1;"); MethodNode test2 = cls.searchMethodByShortId("test2(I)I"); checkLine(lines, codeInfo, test2, 3, "return v - 1;"); - } - - @Test - @NotYetImplemented - public void test2() { - ClassNode cls = getClassNode(TestCls.class); - ICodeInfo codeInfo = cls.getCode(); - String code = codeInfo.toString(); - String[] lines = code.split(ICodeWriter.NL); + checkLine(lines, codeInfo, test2, 6, "return v + 1;"); MethodNode test3 = cls.searchMethodByShortId("test3(I)I"); - checkLine(lines, codeInfo, test3, 3, "return v;"); + if (isJavaInput()) { // dx lost line number for this return + checkLine(lines, codeInfo, test3, 3, "return v;"); + } + checkLine(lines, codeInfo, test3, 6, "return v + 1;"); } private static void checkLine(String[] lines, ICodeInfo cw, LineAttrNode node, int offset, String str) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/fallback/TestFallbackMode.java b/jadx-core/src/test/java/jadx/tests/integration/fallback/TestFallbackMode.java index aff03914..e3313e8a 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/fallback/TestFallbackMode.java +++ b/jadx-core/src/test/java/jadx/tests/integration/fallback/TestFallbackMode.java @@ -24,6 +24,7 @@ public class TestFallbackMode extends IntegrationTest { @Test public void test() { + useDexInput(); setFallback(); disableCompilation(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestInvokeWithWideVars.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestInvokeWithWideVars.java new file mode 100644 index 00000000..8a442b54 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestInvokeWithWideVars.java @@ -0,0 +1,38 @@ +package jadx.tests.integration.invoke; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestInvokeWithWideVars extends IntegrationTest { + + @SuppressWarnings("SameParameterValue") + public static class TestCls { + + public long test1() { + return call(1, 2); + } + + public long test2() { + return rangeCall(1, 2, 3.0d, (byte) 4); + } + + private long call(int a, long b) { + return 0L; + } + + private long rangeCall(long a, int b, double c, byte d) { + return 0L; + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("return call(1, 2);") + .containsOne("return rangeCall(1, 2, 3.0d, (byte) 4);"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaConstructor.java b/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaConstructor.java index 0a7b7b0c..7871cf87 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaConstructor.java +++ b/jadx-core/src/test/java/jadx/tests/integration/java8/TestLambdaConstructor.java @@ -33,6 +33,7 @@ public class TestLambdaConstructor extends IntegrationTest { setFallback(); assertThat(getClassNode(TestCls.class)) .code() - .containsOne("RuntimeException::new"); + .containsOne("r0 = java.lang.RuntimeException::new") + .containsOne("return r0"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEach.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEach.java index 7a880749..ce364781 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEach.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEach.java @@ -29,7 +29,7 @@ public class TestArrayForEach extends IntegrationTest { assertThat(code, containsLines(2, "int sum = 0;", "for (int n : a) {", - indent(1) + "sum += n;", + indent() + "sum += n;", "}", "return sum;")); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments.java index 2ade28cc..d6bc4f34 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments.java @@ -32,13 +32,15 @@ public class TestCodeComments extends IntegrationTest { @Test public void test() { + int insnOffset = isJavaInput() ? 13 : 11; + String baseClsId = TestCls.class.getName(); ICodeComment clsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId), "class comment"); ICodeComment innerClsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId + ".A"), "inner class comment"); ICodeComment fldComment = new JadxCodeComment(new JadxNodeRef(RefType.FIELD, baseClsId, "intField:I"), "field comment"); JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test()I"); ICodeComment mthComment = new JadxCodeComment(mthRef, "method comment"); - ICodeComment insnComment = new JadxCodeComment(mthRef, "insn comment", 11); + ICodeComment insnComment = new JadxCodeComment(mthRef, "insn comment", insnOffset); JadxCodeData codeData = new JadxCodeData(); getArgs().setCodeData(codeData); @@ -60,7 +62,7 @@ public class TestCodeComments extends IntegrationTest { .reloadCode(this) .isEqualTo(code); - ICodeComment updInsnComment = new JadxCodeComment(mthRef, "updated insn comment", 11); + ICodeComment updInsnComment = new JadxCodeComment(mthRef, "updated insn comment", insnOffset); codeData.setComments(Collections.singletonList(updInsnComment)); assertThat(cls) .reloadCode(this) diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments2.java index 063c338d..cecd3c1b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments2.java @@ -27,10 +27,12 @@ public class TestCodeComments2 extends IntegrationTest { @Test public void test() { + printOffsets(); + String baseClsId = TestCls.class.getName(); JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I"); - ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", 10); - ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", 11); + ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", isJavaInput() ? 13 : 10); + ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", isJavaInput() ? 15 : 11); JadxCodeData codeData = new JadxCodeData(); codeData.setComments(Arrays.asList(insnComment, insnComment2)); @@ -40,7 +42,7 @@ public class TestCodeComments2 extends IntegrationTest { .decompile() .checkCodeOffsets() .code() - .containsOne("// " + insnComment.getComment()) - .containsOne("// " + insnComment2.getComment()); + .containsOne("return 1; // " + insnComment.getComment()) + .containsOne("return 3; // " + insnComment2.getComment()); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments2a.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments2a.java index 01fbe28f..af20d807 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments2a.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeComments2a.java @@ -30,10 +30,12 @@ public class TestCodeComments2a extends IntegrationTest { @Test public void test() { + printOffsets(); + String baseClsId = TestCls.class.getName(); JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I"); - ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", 18); - ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", 19); + ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", isJavaInput() ? 22 : 18); + ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", isJavaInput() ? 27 : 19); JadxCodeData codeData = new JadxCodeData(); codeData.setComments(Arrays.asList(insnComment, insnComment2)); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeCommentsMultiline.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeCommentsMultiline.java index 3cf158cd..2b1bfa4e 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeCommentsMultiline.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestCodeCommentsMultiline.java @@ -27,9 +27,11 @@ public class TestCodeCommentsMultiline extends IntegrationTest { @Test public void test() { + printOffsets(); + String baseClsId = TestCls.class.getName(); JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I"); - ICodeComment insnComment = new JadxCodeComment(mthRef, "multi\nline\ncomment", 11); + ICodeComment insnComment = new JadxCodeComment(mthRef, "multi\nline\ncomment", isJavaInput() ? 15 : 11); JadxCodeData codeData = new JadxCodeData(); codeData.setComments(Collections.singletonList(insnComment)); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestDuplicateCast.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestDuplicateCast.java index 22bf801c..805261b5 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestDuplicateCast.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestDuplicateCast.java @@ -32,7 +32,6 @@ public class TestDuplicateCast extends IntegrationTest { @Test public void test() { - dontUnloadClass(); ClassNode cls = getClassNode(TestCls.class); MethodNode mth = getMethod(cls, "method"); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit.java index 2fb06e82..98406e78 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit.java @@ -6,13 +6,9 @@ import java.util.Random; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestFieldInit extends IntegrationTest { @@ -35,15 +31,16 @@ public class TestFieldInit extends IntegrationTest { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("List s = new ArrayList")); - assertThat(code, containsOne("A a = new A();")); - assertThat(code, containsOne("int i = (Random.class.getSimpleName().length() + 1);")); - assertThat(code, containsOne("int n = 0;")); - assertThat(code, not(containsString("static {"))); - assertThat(code, containsOne("this.n = z;")); - assertThat(code, containsOne("this.n = 0;")); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("List s = new ArrayList") + .containsOne("A a = new A();") + .containsOneOf( + "int i = (Random.class.getSimpleName().length() + 1);", + "int i = (1 + Random.class.getSimpleName().length());") + .containsOne("int n = 0;") + .doesNotContain("static {") + .containsOne("this.n = z;") + .containsOne("this.n = 0;"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java index 18823f28..85fc31bf 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java @@ -34,6 +34,7 @@ public class TestFieldInit2 extends IntegrationTest { @Test public void test() { + printDisassemble(); ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestFloatValue.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestFloatValue.java index 4d0de4a3..6c747f65 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestFloatValue.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestFloatValue.java @@ -2,35 +2,31 @@ package jadx.tests.integration.others; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; public class TestFloatValue extends IntegrationTest { public static class TestCls { - public float[] method() { + public float[] test() { float[] fa = { 0.55f }; fa[0] /= 2; return fa; } public void check() { - assertEquals(0.275f, method()[0], 0.0001f); + assertEquals(0.275f, test()[0], 0.0001f); } } @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, not(containsString("1073741824"))); - assertThat(code, containsString("0.55f")); - assertThat(code, containsString("fa[0] = fa[0] / 2.0f;")); + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("1073741824") + .containsOne("0.55f") + .containsOne("fa[0] = fa[0] / 2.0f;"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestJavaDupInsn.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestJavaDupInsn.java new file mode 100644 index 00000000..1fc006eb --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestJavaDupInsn.java @@ -0,0 +1,36 @@ +package jadx.tests.integration.others; + +import org.junit.jupiter.api.Test; + +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.MethodNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestJavaDupInsn extends IntegrationTest { + + public static class TestCls { + private MethodNode mth; + private BlockNode block; + private SSAVar[] vars; + private int[] versions; + + public SSAVar test(RegisterArg regArg) { + int regNum = regArg.getRegNum(); + int version = versions[regNum]++; + SSAVar ssaVar = mth.makeNewSVar(regNum, version, regArg); + vars[regNum] = ssaVar; + return ssaVar; + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code(); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestOverridePackagePrivateMethod.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestOverridePackagePrivateMethod.java index 3b392e1c..96616a8f 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestOverridePackagePrivateMethod.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestOverridePackagePrivateMethod.java @@ -4,7 +4,6 @@ import java.util.List; import org.junit.jupiter.api.Test; -import jadx.NotYetImplemented; import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.SmaliTest; @@ -42,7 +41,6 @@ public class TestOverridePackagePrivateMethod extends SmaliTest { */ // @formatter:on - @NotYetImplemented("Don't change access modifiers if not needed") @Test public void test() { commonChecks(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitch.java b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitch.java index b5d3cbcc..03f28b10 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitch.java +++ b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitch.java @@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class TestSwitch extends IntegrationTest { public static class TestCls { - public String escape(String str) { + public String test(String str) { int len = str.length(); StringBuilder sb = new StringBuilder(len); for (int i = 0; i < len; i++) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized2.java b/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized2.java index b599d61a..8691e769 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized2.java @@ -31,6 +31,8 @@ public class TestSynchronized2 extends IntegrationTest { @Test @NotYetImplemented public void test2() { + useDexInput(); // java bytecode don't add exception handlers + ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java index 5a710cbf..92a82092 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java @@ -33,6 +33,7 @@ public class TestTryCatch7 extends IntegrationTest { @Test public void testNoDebug() { + // useDexInput(); noDebugInfo(); ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchMultiException.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchMultiException.java index 57febf7a..3d89d00d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchMultiException.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchMultiException.java @@ -25,6 +25,8 @@ public class TestTryCatchMultiException extends IntegrationTest { @Test public void test() { + // printDisassemble(); + // setFallback(); noDebugInfo(); ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics5.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics5.java index 2621cc19..c32f078d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics5.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics5.java @@ -14,7 +14,7 @@ public class TestGenerics5 extends IntegrationTest { public static class TestCls { private InheritableThreadLocal> inheritableThreadLocal; - public void put(String key, String val) { + public void test(String key, String val) { if (key == null) { throw new IllegalArgumentException("key cannot be null"); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/usethis/TestDontInlineThis.java b/jadx-core/src/test/java/jadx/tests/integration/usethis/TestDontInlineThis.java index 3f41b2dc..399702f4 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/usethis/TestDontInlineThis.java +++ b/jadx-core/src/test/java/jadx/tests/integration/usethis/TestDontInlineThis.java @@ -19,6 +19,7 @@ public class TestDontInlineThis extends IntegrationTest { TestCls res; if (field == 7) { res = this; + System.out.println(); } else { res = new TestCls(); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariables5.java b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariables5.java index 06213eb2..314db281 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariables5.java +++ b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariables5.java @@ -5,10 +5,7 @@ import org.junit.jupiter.api.Test; import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; public class TestVariables5 extends IntegrationTest { @@ -47,12 +44,10 @@ public class TestVariables5 extends IntegrationTest { public void test() { noDebugInfo(); ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, not(containsString("int i2++;"))); - assertThat(code, containsOne("int i = 0;")); - assertThat(code, containsOne("&& (i = i + 1) == 2")); - // assertThat(code, containsOne("i++;")); - // assertThat(code, containsOne("if (i == 2) {")); + assertThat(cls) + .code() + .doesNotContain("int i2++;") + .containsOne("int i = 0;") + .containsOneOf("i++;", "&& (i = i + 1) == 2"); } } diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java index f308886a..80f71a27 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java @@ -2,6 +2,7 @@ package jadx.gui.device.debugger.smali; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -27,11 +28,14 @@ import jadx.api.plugins.input.data.ITry; import jadx.api.plugins.input.data.annotations.AnnotationVisibility; 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.api.plugins.input.insns.InsnData; import jadx.api.plugins.input.insns.InsnIndexType; import jadx.api.plugins.input.insns.Opcode; import jadx.api.plugins.input.insns.custom.ISwitchPayload; import jadx.core.codegen.TypeGen; +import jadx.core.dex.attributes.AttributeStorage; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnDecoder; import jadx.core.dex.instructions.InsnType; @@ -169,17 +173,23 @@ public class Smali { smali.startLine(String.format("###### Class %s is created by jadx", cls.getFullName())); return; } + AttributeStorage attributes = new AttributeStorage(); + attributes.add(clsData.getAttributes()); + smali.startLine("Class: " + clsData.getType()) .startLine("AccessFlags: " + AccessFlags.format(clsData.getAccessFlags(), AccessFlagsScope.CLASS)) .startLine("SuperType: " + clsData.getSuperType()) .startLine("Interfaces: " + clsData.getInterfacesTypes()) - .startLine("SourceFile: " + clsData.getSourceFile()); + .startLine("SourceFile: " + attributes.get(JadxAttrType.SOURCE_FILE)); - List annos = clsData.getAnnotations(); - if (annos.size() > 0) { - smali.startLine(String.format("# %d annotations", annos.size())); - writeAnnotations(smali, annos); - smali.startLine(); + AnnotationsAttr annotationsAttr = attributes.get(JadxAttrType.ANNOTATION_LIST); + if (annotationsAttr != null) { + Collection annos = annotationsAttr.getList(); + if (!annos.isEmpty()) { + smali.startLine(String.format("# %d annotations", annos.size())); + writeAnnotations(smali, new ArrayList<>(annos)); + smali.startLine(); + } } List fields = new ArrayList<>(); @@ -216,7 +226,6 @@ public class Smali { private void writeFields(SmaliWriter smali, IClassData classData, List fields, int[] colWidths) { int staticIdx = 0; - List staticFieldInitValues = classData.getStaticFieldInitValues(); smali.startLine().startLine("# fields"); String whites = new String(new byte[Math.max(colWidths[0], colWidths[1])]).replace("\0", " "); for (RawField fld : fields) { @@ -232,15 +241,19 @@ public class Smali { } smali.add(fld.name).add(" "); smali.add(": ").add(fld.type); - if (fld.isStatic) { // static field - if (staticIdx < staticFieldInitValues.size()) { + if (fld.isStatic) { + EncodedValue constVal = fld.attributes.get(JadxAttrType.CONSTANT_VALUE); + if (constVal != null) { smali.add(" # init val = "); - writeEncodedValue(smali, staticFieldInitValues.get(staticIdx++), false); + writeEncodedValue(smali, constVal, false); } } - smali.incIndent(); - writeAnnotations(smali, fld.annoList); - smali.decIndent(); + AnnotationsAttr annotationsAttr = fld.attributes.get(JadxAttrType.ANNOTATION_LIST); + if (annotationsAttr != null) { + smali.incIndent(); + writeAnnotations(smali, annotationsAttr.getList()); + smali.decIndent(); + } } smali.startLine(); } @@ -249,17 +262,15 @@ public class Smali { if (insnDecoder == null) { insnDecoder = new SmaliInsnDecoder(methodNode); } - smali.startLine() - .startLine(mth.isDirect() ? "# direct method" : " # virtual method") - .startLine(".method "); + smali.startLine().startLine(".method "); writeMethodDef(smali, mth, line); ICodeReader codeReader = mth.getCodeReader(); if (codeReader != null) { line.smaliMthNode.setParamRegStart(getParamStartRegNum(mth)); line.smaliMthNode.setRegCount(codeReader.getRegistersCount()); - Map nodes = new HashMap<>(codeReader.getInsnsCount() / 2); - line.smaliMthNode.setInsnNodes(nodes, codeReader.getInsnsCount()); - line.smaliMthNode.initRegInfoList(codeReader.getRegistersCount(), codeReader.getInsnsCount()); + Map nodes = new HashMap<>(codeReader.getUnitsCount() / 2); + line.smaliMthNode.setInsnNodes(nodes, codeReader.getUnitsCount()); + line.smaliMthNode.initRegInfoList(codeReader.getRegistersCount(), codeReader.getUnitsCount()); smali.incIndent(); smali.startLine(".registers ") @@ -288,7 +299,7 @@ public class Smali { private void writeTries(ICodeReader codeReader, LineInfo line) { List tries = codeReader.getTries(); for (ITry aTry : tries) { - int end = aTry.getStartAddress() + aTry.getInstructionCount(); + int end = aTry.getEndAddress(); String tryEndTip = String.format(FMT_TRY_END_TAG, end); String tryStartTip = String.format(FMT_TRY_TAG, aTry.getStartAddress()); String tryStartTipExtra = " # :" + tryStartTip.substring(0, tryStartTip.length() - 1); @@ -403,10 +414,11 @@ public class Smali { methodRef.getArgTypes().forEach(smali::add); smali.add(')'); smali.add(methodRef.getReturnType()); - List annos = mth.getAnnotations(); - if (annos.size() > 0) { + + AnnotationsAttr annotationsAttr = new AttributeStorage(mth.getAttributes()).get(JadxAttrType.ANNOTATION_LIST); + if (annotationsAttr != null && !annotationsAttr.isEmpty()) { smali.incIndent(); - writeAnnotations(smali, annos); + writeAnnotations(smali, annotationsAttr.getList()); smali.decIndent(); smali.startLine(); } @@ -414,7 +426,7 @@ public class Smali { private boolean formatMthParamInfo(IMethodData mth, SmaliWriter smali, ICodeReader codeReader, LineInfo line) { List types = mth.getMethodRef().getArgTypes(); - if (types.size() == 0) { + if (types.isEmpty()) { return false; } int paramCount = 0; @@ -1002,7 +1014,7 @@ public class Smali { String accessFlag; String name; String type; - List annoList; + AttributeStorage attributes; private static RawField make(IFieldData f) { RawField field = new RawField(); @@ -1010,7 +1022,7 @@ public class Smali { field.accessFlag = AccessFlags.format(f.getAccessFlags(), FIELD); field.name = f.getName(); field.type = f.getType(); - field.annoList = f.getAnnotations(); + field.attributes = new AttributeStorage(f.getAttributes()); return field; } } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java index b1b8aaae..87bdb1d9 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java @@ -2,7 +2,11 @@ package jadx.plugins.input.dex.insns; import org.jetbrains.annotations.Nullable; -import jadx.api.plugins.input.data.*; +import jadx.api.plugins.input.data.ICallSite; +import jadx.api.plugins.input.data.IFieldRef; +import jadx.api.plugins.input.data.IMethodHandle; +import jadx.api.plugins.input.data.IMethodProto; +import jadx.api.plugins.input.data.IMethodRef; import jadx.api.plugins.input.insns.InsnData; import jadx.api.plugins.input.insns.InsnIndexType; import jadx.api.plugins.input.insns.Opcode; @@ -82,6 +86,11 @@ public class DexInsnData implements InsnData { return argsReg[argNum]; } + @Override + public int getResultReg() { + return -1; + } + @Override public long getLiteral() { return literal; @@ -113,8 +122,8 @@ public class DexInsnData implements InsnData { } @Override - public IFieldData getIndexAsField() { - return externalReader.getFieldData(index); + public IFieldRef getIndexAsField() { + return externalReader.getFieldRef(index); } @Override diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java index d13c0be6..ade8d7e1 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java @@ -1,8 +1,8 @@ package jadx.plugins.input.dex.insns; +import jadx.api.plugins.input.insns.custom.impl.SwitchPayload; import jadx.plugins.input.dex.DexException; import jadx.plugins.input.dex.insns.payloads.DexArrayPayload; -import jadx.plugins.input.dex.insns.payloads.DexSwitchPayload; import jadx.plugins.input.dex.sections.SectionReader; public abstract class DexInsnFormat { @@ -158,6 +158,7 @@ public abstract class DexInsnFormat { regs[0] = nibble2(opcodeUnit); regs[1] = nibble3(opcodeUnit); insn.setIndex(in.readUShort()); + insn.setLiteral(0L); } }; @@ -262,7 +263,7 @@ public abstract class DexInsnFormat { targets[i] = in.readInt(); keys[i] = firstKey + i; } - insn.setPayload(new DexSwitchPayload(size, keys, targets)); + insn.setPayload(new SwitchPayload(size, keys, targets)); insn.setLength(size * 2 + 4); } @@ -286,7 +287,7 @@ public abstract class DexInsnFormat { for (int i = 0; i < size; i++) { targets[i] = in.readInt(); } - insn.setPayload(new DexSwitchPayload(size, keys, targets)); + insn.setPayload(new SwitchPayload(size, keys, targets)); insn.setLength(size * 4 + 2); } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java index 75dbee4e..52cae896 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java @@ -282,8 +282,8 @@ public class DexInsnInfo { register(arr, DexOpcodes.INVOKE_POLYMORPHIC, Opcode.INVOKE_POLYMORPHIC, DexInsnFormat.FORMAT_45CC); register(arr, DexOpcodes.INVOKE_POLYMORPHIC_RANGE, Opcode.INVOKE_POLYMORPHIC_RANGE, DexInsnFormat.FORMAT_4RCC); - register(arr, DexOpcodes.INVOKE_CUSTOM, Opcode.INVOKE_CUSTOM, DexInsnFormat.FORMAT_35C); - register(arr, DexOpcodes.INVOKE_CUSTOM_RANGE, Opcode.INVOKE_CUSTOM_RANGE, DexInsnFormat.FORMAT_3RC); + register(arr, DexOpcodes.INVOKE_CUSTOM, Opcode.INVOKE_CUSTOM, DexInsnFormat.FORMAT_35C, InsnIndexType.CALL_SITE); + register(arr, DexOpcodes.INVOKE_CUSTOM_RANGE, Opcode.INVOKE_CUSTOM_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.CALL_SITE); register(arr, DexOpcodes.CONST_METHOD_HANDLE, Opcode.CONST_METHOD_HANDLE, DexInsnFormat.FORMAT_21C); register(arr, DexOpcodes.CONST_METHOD_TYPE, Opcode.CONST_METHOD_TYPE, DexInsnFormat.FORMAT_21C); diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexAnnotationsConvert.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexAnnotationsConvert.java new file mode 100644 index 00000000..379e4e78 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexAnnotationsConvert.java @@ -0,0 +1,103 @@ +package jadx.plugins.input.dex.sections; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.IJadxAttribute; +import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultClassAttr; +import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr; +import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr; +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.SignatureAttr; +import jadx.api.plugins.utils.Utils; + +public class DexAnnotationsConvert { + private static final Logger LOG = LoggerFactory.getLogger(DexAnnotationsConvert.class); + + public static void forClass(String cls, List list, List annotationList) { + appendAnnotations(cls, list, annotationList); + } + + public static void forMethod(List list, List annotationList) { + appendAnnotations(null, list, annotationList); + } + + public static void forField(List list, List annotationList) { + appendAnnotations(null, list, annotationList); + } + + private static void appendAnnotations(String cls, List attributes, List annotations) { + if (annotations.isEmpty()) { + return; + } + for (IAnnotation annotation : annotations) { + if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) { + convertSystemAnnotations(cls, attributes, annotation); + } + } + Utils.addToList(attributes, AnnotationsAttr.pack(annotations)); + } + + @SuppressWarnings("unchecked") + private static void convertSystemAnnotations(String cls, List attributes, IAnnotation annotation) { + switch (annotation.getAnnotationClass()) { + case "Ldalvik/annotation/Signature;": + attributes.add(new SignatureAttr(extractSignature(annotation))); + break; + + case "Ldalvik/annotation/InnerClass;": + Map values = annotation.getValues(); + String name = (String) values.get("name").getValue(); + int accFlags = (Integer) values.get("accessFlags").getValue(); + Map map = Collections.singletonMap(cls, new InnerClsInfo(cls, null, name, accFlags)); + attributes.add(new InnerClassesAttr(map)); + break; + + case "Ldalvik/annotation/AnnotationDefault;": + EncodedValue annValue = annotation.getDefaultValue(); + if (annValue != null && annValue.getType() == EncodedType.ENCODED_ANNOTATION) { + IAnnotation defAnnotation = (IAnnotation) annValue.getValue(); + attributes.add(new AnnotationDefaultClassAttr(defAnnotation.getValues())); + } + break; + + case "Ldalvik/annotation/Throws;": + try { + EncodedValue defaultValue = annotation.getDefaultValue(); + if (defaultValue != null) { + List excs = ((List) defaultValue.getValue()) + .stream() + .map(ev -> ((String) ev.getValue())) + .collect(Collectors.toList()); + attributes.add(new ExceptionsAttr(excs)); + } + } catch (Exception e) { + LOG.warn("Failed to convert dalvik throws annotation", e); + } + break; + } + } + + @SuppressWarnings({ "unchecked", "ConstantConditions" }) + private static String extractSignature(IAnnotation annotation) { + List values = (List) annotation.getDefaultValue().getValue(); + if (values.size() == 1) { + return (String) values.get(0).getValue(); + } + StringBuilder sb = new StringBuilder(); + for (EncodedValue part : values) { + sb.append((String) part.getValue()); + } + return sb.toString(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java index 93b90926..74b566b5 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java @@ -1,21 +1,27 @@ package jadx.plugins.input.dex.sections; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import jadx.api.plugins.input.data.IClassData; import jadx.api.plugins.input.data.IFieldData; 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.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; import jadx.plugins.input.dex.utils.SmaliUtils; public class DexClassData implements IClassData { + private static final Logger LOG = LoggerFactory.getLogger(DexClassData.class); public static final int SIZE = 8 * 4; private final SectionReader in; @@ -63,8 +69,7 @@ public class DexClassData implements IClassData { } @Nullable - @Override - public String getSourceFile() { + private String getSourceFile() { int strIdx = in.pos(4 * 4).readInt(); return in.getString(strIdx); } @@ -107,12 +112,13 @@ public class DexClassData implements IClassData { Map annotationOffsetMap = annotationsParser.readFieldsAnnotationOffsetMap(); DexFieldData fieldData = new DexFieldData(annotationsParser); fieldData.setParentClassType(getType()); - readFields(fieldConsumer, data, fieldData, staticFieldsCount, annotationOffsetMap); - readFields(fieldConsumer, data, fieldData, instanceFieldsCount, annotationOffsetMap); + readFields(fieldConsumer, data, fieldData, staticFieldsCount, annotationOffsetMap, true); + readFields(fieldConsumer, data, fieldData, instanceFieldsCount, annotationOffsetMap, false); } private void readFields(Consumer fieldConsumer, SectionReader data, DexFieldData fieldData, int count, - Map annOffsetMap) { + Map annOffsetMap, boolean staticFields) { + List constValues = staticFields ? getStaticFieldInitValues(data.copy()) : null; int fieldId = 0; for (int i = 0; i < count; i++) { fieldId += data.readUleb128(); @@ -120,6 +126,7 @@ public class DexClassData implements IClassData { in.fillFieldData(fieldData, fieldId); fieldData.setAccessFlags(accFlags); fieldData.setAnnotationsOffset(getOffsetFromMap(fieldId, annOffsetMap)); + fieldData.setConstValue(staticFields && i < constValues.size() ? constValues.get(i) : null); fieldConsumer.accept(fieldData); } } @@ -130,9 +137,7 @@ public class DexClassData implements IClassData { Map annotationOffsetMap = annotationsParser.readMethodsAnnotationOffsetMap(); Map paramsAnnOffsetMap = annotationsParser.readMethodParamsAnnRefOffsetMap(); - methodData.setDirect(true); readMethods(mthConsumer, data, methodData, directMthCount, annotationOffsetMap, paramsAnnOffsetMap); - methodData.setDirect(false); readMethods(mthConsumer, data, methodData, virtualMthCount, annotationOffsetMap, paramsAnnOffsetMap); } @@ -167,20 +172,29 @@ public class DexClassData implements IClassData { return offset != null ? offset : 0; } - @Override - public List getStaticFieldInitValues() { + private List getStaticFieldInitValues(SectionReader reader) { int staticValuesOff = getStaticValuesOff(); if (staticValuesOff == 0) { return Collections.emptyList(); } - in.absPos(staticValuesOff); - return annotationsParser.parseEncodedArray(in); + reader.absPos(staticValuesOff); + return annotationsParser.parseEncodedArray(reader); + } + + private List getAnnotations() { + annotationsParser.setOffset(getAnnotationsOff()); + return annotationsParser.readClassAnnotations(); } @Override - public List getAnnotations() { - annotationsParser.setOffset(getAnnotationsOff()); - return annotationsParser.readClassAnnotations(); + public List getAttributes() { + List list = new ArrayList<>(); + String sourceFile = getSourceFile(); + if (sourceFile != null && !sourceFile.isEmpty()) { + list.add(new SourceFileAttr(sourceFile)); + } + DexAnnotationsConvert.forClass(getType(), list, getAnnotations()); + return list; } public int getClassDefOffset() { diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java index e0d04629..53b36ae2 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java @@ -13,14 +13,14 @@ import jadx.api.plugins.input.data.ICatch; import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.IDebugInfo; import jadx.api.plugins.input.data.ITry; +import jadx.api.plugins.input.data.impl.CatchData; +import jadx.api.plugins.input.data.impl.TryData; import jadx.api.plugins.input.insns.InsnData; import jadx.plugins.input.dex.DexException; import jadx.plugins.input.dex.insns.DexInsnData; import jadx.plugins.input.dex.insns.DexInsnFormat; import jadx.plugins.input.dex.insns.DexInsnInfo; import jadx.plugins.input.dex.sections.debuginfo.DebugInfoParser; -import jadx.plugins.input.dex.sections.trycatch.DexCatch; -import jadx.plugins.input.dex.sections.trycatch.DexTryData; public class DexCodeReader implements ICodeReader { @@ -48,7 +48,12 @@ public class DexCodeReader implements ICodeReader { } @Override - public int getInsnsCount() { + public int getArgsStartReg() { + return -1; + } + + @Override + public int getUnitsCount() { return in.pos(12).readInt(); } @@ -108,7 +113,7 @@ public class DexCodeReader implements ICodeReader { return null; } int regsCount = getRegistersCount(); - DebugInfoParser debugInfoParser = new DebugInfoParser(in, regsCount, getInsnsCount()); + DebugInfoParser debugInfoParser = new DebugInfoParser(in, regsCount, getUnitsCount()); debugInfoParser.initMthArgs(regsCount, in.getMethodParamTypes(mthId)); return debugInfoParser.process(debugOff); } @@ -122,7 +127,7 @@ public class DexCodeReader implements ICodeReader { if (triesCount == 0) { return -1; } - int insnsCount = getInsnsCount(); + int insnsCount = getUnitsCount(); int padding = insnsCount % 2 == 1 ? 2 : 0; return 4 * 4 + insnsCount * 2 + padding; } @@ -145,7 +150,7 @@ public class DexCodeReader implements ICodeReader { if (catchHandler == null) { throw new DexException("Catch handler not found by byte offset: " + handlerOff); } - triesList.add(new DexTryData(catchHandler, startAddr, insnsCount)); + triesList.add(new TryData(startAddr, startAddr + insnsCount - 1, catchHandler)); } return triesList; } @@ -171,7 +176,7 @@ public class DexCodeReader implements ICodeReader { } else { catchAllAddr = -1; } - map.put(byteIndex, new DexCatch(addr, types, catchAllAddr)); + map.put(byteIndex, new CatchData(addr, types, catchAllAddr)); } return map; } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java index ce482d46..380bcfc2 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java @@ -1,11 +1,15 @@ package jadx.plugins.input.dex.sections; +import java.util.ArrayList; import java.util.List; import org.jetbrains.annotations.Nullable; import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.api.plugins.utils.Utils; import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; public class DexFieldData implements IFieldData { @@ -17,6 +21,7 @@ public class DexFieldData implements IFieldData { private String name; private int accessFlags; private int annotationsOffset; + private EncodedValue constValue; public DexFieldData(@Nullable AnnotationsParser parser) { this.annotationsParser = parser; @@ -62,14 +67,25 @@ public class DexFieldData implements IFieldData { this.annotationsOffset = annotationsOffset; } - @Override - public List getAnnotations() { + public void setConstValue(EncodedValue constValue) { + this.constValue = constValue; + } + + private List getAnnotations() { if (annotationsParser == null) { throw new NullPointerException("Annotation parser not initialized"); } return annotationsParser.readAnnotationList(annotationsOffset); } + @Override + public List getAttributes() { + List list = new ArrayList<>(2); + Utils.addToList(list, constValue); + DexAnnotationsConvert.forField(list, getAnnotations()); + return list; + } + @Override public String toString() { return getParentClassType() + "->" + getName() + ":" + getType(); diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java index 1c775abd..a6940ce3 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java @@ -1,5 +1,6 @@ package jadx.plugins.input.dex.sections; +import java.util.ArrayList; import java.util.List; import org.jetbrains.annotations.Nullable; @@ -7,6 +8,9 @@ import org.jetbrains.annotations.Nullable; import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.IMethodData; import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr; +import jadx.api.plugins.utils.Utils; import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; import jadx.plugins.input.dex.smali.SmaliPrinter; @@ -17,7 +21,6 @@ public class DexMethodData implements IMethodData { private DexMethodRef methodRef; private int accessFlags; - private boolean isDirect; private int annotationsOffset; private int paramAnnotationsOffset; @@ -46,15 +49,6 @@ public class DexMethodData implements IMethodData { this.accessFlags = accessFlags; } - @Override - public boolean isDirect() { - return isDirect; - } - - public void setDirect(boolean direct) { - isDirect = direct; - } - @Nullable @Override public ICodeReader getCodeReader() { @@ -78,16 +72,22 @@ public class DexMethodData implements IMethodData { this.paramAnnotationsOffset = paramAnnotationsOffset; } - @Override - public List getAnnotations() { + private List getAnnotations() { return getAnnotationsParser().readAnnotationList(annotationsOffset); } - @Override - public List> getParamsAnnotations() { + private List> getParamsAnnotations() { return getAnnotationsParser().readAnnotationRefList(paramAnnotationsOffset); } + @Override + public List getAttributes() { + List list = new ArrayList<>(); + DexAnnotationsConvert.forMethod(list, getAnnotations()); + Utils.addToList(list, MethodParamsAttr.pack(getParamsAnnotations())); + return list; + } + private AnnotationsParser getAnnotationsParser() { if (annotationsParser == null) { throw new NullPointerException("Annotation parser not initialized"); diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodProto.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodProto.java index 7a2ad9ba..a297b8d8 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodProto.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodProto.java @@ -3,7 +3,7 @@ package jadx.plugins.input.dex.sections; import java.util.List; import jadx.api.plugins.input.data.IMethodProto; -import jadx.plugins.input.dex.utils.Utils; +import jadx.api.plugins.utils.Utils; public class DexMethodProto implements IMethodProto { private final List argTypes; diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodRef.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodRef.java index 7377db23..7b9c5516 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodRef.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodRef.java @@ -3,8 +3,8 @@ package jadx.plugins.input.dex.sections; import java.util.List; import jadx.api.plugins.input.data.IMethodRef; +import jadx.api.plugins.utils.Utils; import jadx.plugins.input.dex.DexReader; -import jadx.plugins.input.dex.utils.Utils; public class DexMethodRef implements IMethodRef { diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java index aecf782e..b9421013 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java @@ -10,9 +10,10 @@ import java.util.List; import org.jetbrains.annotations.Nullable; import jadx.api.plugins.input.data.ICallSite; -import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IFieldRef; import jadx.api.plugins.input.data.IMethodHandle; import jadx.api.plugins.input.data.MethodHandleType; +import jadx.api.plugins.input.data.impl.CallSite; import jadx.api.plugins.input.data.impl.FieldRefHandle; import jadx.api.plugins.input.data.impl.MethodRefHandle; import jadx.plugins.input.dex.DexReader; @@ -177,7 +178,7 @@ public class SectionReader { return MUtf8.decode(this); } - public IFieldData getFieldData(int idx) { + public IFieldRef getFieldRef(int idx) { DexFieldData fieldData = new DexFieldData(null); int clsTypeIdx = fillFieldData(fieldData, idx); fieldData.setParentClassType(getType(clsTypeIdx)); @@ -205,7 +206,7 @@ public class SectionReader { int callSiteOff = dexReader.getHeader().getCallSiteOff(); absPos(callSiteOff + idx * 4); absPos(readInt()); - return new DexCallSite(EncodedValueParser.parseEncodedArray(this, ext)); + return new CallSite(EncodedValueParser.parseEncodedArray(this, ext)); } public IMethodHandle getMethodHandle(int idx) { @@ -215,7 +216,7 @@ public class SectionReader { skip(2); int refId = readUShort(); if (handleType.isField()) { - return new FieldRefHandle(handleType, getFieldData(refId)); + return new FieldRefHandle(handleType, getFieldRef(refId)); } return new MethodRefHandle(handleType, getMethodRef(refId)); } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java index 18106b2d..662ece53 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java @@ -10,6 +10,7 @@ import java.util.Map; import jadx.api.plugins.input.data.annotations.AnnotationVisibility; import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.annotations.JadxAnnotation; import jadx.plugins.input.dex.DexException; import jadx.plugins.input.dex.sections.SectionReader; @@ -149,7 +150,7 @@ public class AnnotationsParser { values.put(name, EncodedValueParser.parseValue(in, ext)); } String type = ext.getType(typeIndex); - return new DexAnnotation(visibility, type, values); + return new JadxAnnotation(visibility, type, values); } private static AnnotationVisibility getVisibilityValue(int value) { diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java index 82704e9c..c75198ec 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java @@ -66,7 +66,7 @@ public class EncodedValueParser { case ENCODED_FIELD: case ENCODED_ENUM: - return new EncodedValue(EncodedType.ENCODED_FIELD, ext.getFieldData(parseUnsignedInt(in, size))); + return new EncodedValue(EncodedType.ENCODED_FIELD, ext.getFieldRef(parseUnsignedInt(in, size))); case ENCODED_ARRAY: return new EncodedValue(EncodedType.ENCODED_ARRAY, parseEncodedArray(in, ext)); diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java index 5a48d54e..28e827d9 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java @@ -9,6 +9,7 @@ import java.util.Map; import org.jetbrains.annotations.Nullable; import jadx.api.plugins.input.data.ILocalVar; +import jadx.api.plugins.input.data.impl.DebugInfo; import jadx.plugins.input.dex.sections.DexConsts; import jadx.plugins.input.dex.sections.SectionReader; @@ -34,7 +35,7 @@ public class DebugInfoParser { private final SectionReader in; private final SectionReader ext; - private final LocalVar[] locals; + private final DexLocalVar[] locals; private final int codeSize; private List resultList; @@ -48,7 +49,7 @@ public class DebugInfoParser { public DebugInfoParser(SectionReader in, int regsCount, int codeSize) { this.in = in; this.ext = in.copy(); - this.locals = new LocalVar[regsCount]; + this.locals = new DexLocalVar[regsCount]; this.codeSize = codeSize; } @@ -96,7 +97,7 @@ public class DebugInfoParser { String name = ext.getString(nameId); if (name != null && i < argsCount) { int regNum = argRegs[i]; - startVar(new LocalVar(regNum, name, argTypes.get(i)), -1); + startVar(new DexLocalVar(regNum, name, argTypes.get(i)), -1); varsInfoFound = true; } } @@ -123,7 +124,7 @@ public class DebugInfoParser { int regNum = in.readUleb128(); int nameId = in.readUleb128() - 1; int type = in.readUleb128() - 1; - LocalVar var = new LocalVar(ext, regNum, nameId, type, DexConsts.NO_INDEX); + DexLocalVar var = new DexLocalVar(ext, regNum, nameId, type, DexConsts.NO_INDEX); startVar(var, addr); varsInfoFound = true; break; @@ -133,7 +134,7 @@ public class DebugInfoParser { int nameId = in.readUleb128p1(); int type = in.readUleb128p1(); int sign = in.readUleb128p1(); - LocalVar var = new LocalVar(ext, regNum, nameId, type, sign); + DexLocalVar var = new DexLocalVar(ext, regNum, nameId, type, sign); startVar(var, addr); varsInfoFound = true; break; @@ -146,7 +147,7 @@ public class DebugInfoParser { } case DBG_END_LOCAL: { int regNum = in.readUleb128(); - LocalVar var = locals[regNum]; + DexLocalVar var = locals[regNum]; if (var != null) { endVar(var, addr); } @@ -178,7 +179,7 @@ public class DebugInfoParser { } if (varsInfoFound) { - for (LocalVar var : locals) { + for (DexLocalVar var : locals) { if (var != null && !var.isEnd()) { endVar(var, codeSize - 1); } @@ -208,17 +209,17 @@ public class DebugInfoParser { } private void restartVar(int regNum, int addr) { - LocalVar prev = locals[regNum]; + DexLocalVar prev = locals[regNum]; if (prev != null) { endVar(prev, addr); - LocalVar newVar = new LocalVar(regNum, prev.getName(), prev.getType(), prev.getSignature()); + DexLocalVar newVar = new DexLocalVar(regNum, prev.getName(), prev.getType(), prev.getSignature()); startVar(newVar, addr); } } - private void startVar(LocalVar newVar, int addr) { + private void startVar(DexLocalVar newVar, int addr) { int regNum = newVar.getRegNum(); - LocalVar prev = locals[regNum]; + DexLocalVar prev = locals[regNum]; if (prev != null) { endVar(prev, addr); } @@ -226,7 +227,7 @@ public class DebugInfoParser { locals[regNum] = newVar; } - private void endVar(LocalVar var, int addr) { + private void endVar(DexLocalVar var, int addr) { if (var.end(addr)) { resultList.add(var); } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/LocalVar.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DexLocalVar.java similarity index 81% rename from jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/LocalVar.java rename to jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DexLocalVar.java index 4e8930b1..4458fc24 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/LocalVar.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DexLocalVar.java @@ -3,10 +3,10 @@ package jadx.plugins.input.dex.sections.debuginfo; import org.jetbrains.annotations.Nullable; import jadx.api.plugins.input.data.ILocalVar; +import jadx.api.plugins.utils.Utils; import jadx.plugins.input.dex.sections.SectionReader; -import jadx.plugins.input.dex.utils.Utils; -public class LocalVar implements ILocalVar { +public class DexLocalVar implements ILocalVar { private final int regNum; private final String name; private final String type; @@ -17,15 +17,15 @@ public class LocalVar implements ILocalVar { private int startOffset; private int endOffset; - public LocalVar(SectionReader dex, int regNum, int nameId, int typeId, int signId) { + public DexLocalVar(SectionReader dex, int regNum, int nameId, int typeId, int signId) { this(regNum, dex.getString(nameId), dex.getType(typeId), dex.getString(signId)); } - public LocalVar(int regNum, String name, String type) { + public DexLocalVar(int regNum, String name, String type) { this(regNum, name, type, null); } - public LocalVar(int regNum, String name, String type, @Nullable String sign) { + public DexLocalVar(int regNum, String name, String type, @Nullable String sign) { this.regNum = regNum; this.name = name; this.type = type; @@ -99,7 +99,7 @@ public class LocalVar implements ILocalVar { @Override public String toString() { - return Utils.formatOffset(startOffset) + return (startOffset == -1 ? "-1 " : Utils.formatOffset(startOffset)) + '-' + (isEnd ? Utils.formatOffset(endOffset) : " ") + ": r" + regNum + " '" + name + "' " + type + (sign != null ? ", signature: " + sign : ""); diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexCatch.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexCatch.java deleted file mode 100644 index c9ebc0ee..00000000 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexCatch.java +++ /dev/null @@ -1,30 +0,0 @@ -package jadx.plugins.input.dex.sections.trycatch; - -import jadx.api.plugins.input.data.ICatch; - -public class DexCatch implements ICatch { - private final int[] addr; - private final String[] types; - private final int allAddr; - - public DexCatch(int[] addr, String[] types, int allAddr) { - this.addr = addr; - this.types = types; - this.allAddr = allAddr; - } - - @Override - public int[] getAddresses() { - return addr; - } - - @Override - public String[] getTypes() { - return types; - } - - @Override - public int getCatchAllAddress() { - return allAddr; - } -} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java index 2975c727..b70aeb1c 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java @@ -1,8 +1,7 @@ package jadx.plugins.input.dex.smali; -import java.util.*; - -import jadx.api.plugins.input.data.*; +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.ICodeReader; import jadx.plugins.input.dex.sections.DexMethodData; import jadx.plugins.input.dex.sections.DexMethodRef; diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Utils.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Utils.java deleted file mode 100644 index 216e0d94..00000000 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Utils.java +++ /dev/null @@ -1,29 +0,0 @@ -package jadx.plugins.input.dex.utils; - -import java.util.Iterator; -import java.util.List; - -public class Utils { - - public static String listToStr(List collection) { - if (collection == null) { - return "null"; - } - if (collection.isEmpty()) { - return ""; - } - StringBuilder sb = new StringBuilder(); - Iterator it = collection.iterator(); - if (it.hasNext()) { - sb.append(it.next()); - } - while (it.hasNext()) { - sb.append(", ").append(it.next()); - } - return sb.toString(); - } - - public static String formatOffset(int offset) { - return String.format("0x%04x", offset); - } -} diff --git a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java index 3ce64ba7..48d0776b 100644 --- a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java +++ b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java @@ -46,7 +46,7 @@ class DexInputPluginTest { System.out.println("AccessFlags: " + AccessFlags.format(cls.getAccessFlags(), AccessFlagsScope.CLASS)); System.out.println("SuperType: " + cls.getSuperType()); System.out.println("Interfaces: " + cls.getInterfacesTypes()); - System.out.println("SourceFile: " + cls.getSourceFile()); + System.out.println("Attributes: " + cls.getAttributes()); count.getAndIncrement(); cls.visitFieldsAndMethods( diff --git a/jadx-plugins/jadx-java-input/build.gradle b/jadx-plugins/jadx-java-input/build.gradle new file mode 100644 index 00000000..fa1c146e --- /dev/null +++ b/jadx-plugins/jadx-java-input/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'java-library' +} + +dependencies { + api(project(":jadx-plugins:jadx-plugins-api")) +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaClassReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaClassReader.java new file mode 100644 index 00000000..b1512e76 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaClassReader.java @@ -0,0 +1,37 @@ +package jadx.plugins.input.java; + +import jadx.api.plugins.input.data.IClassData; +import jadx.plugins.input.java.data.JavaClassData; + +public class JavaClassReader { + private final int id; + private final String fileName; + private final byte[] data; + + public JavaClassReader(int id, String fileName, byte[] data) { + this.id = id; + this.fileName = fileName; + this.data = data; + } + + public IClassData loadClassData() { + return new JavaClassData(this); + } + + public int getId() { + return id; + } + + public String getFileName() { + return fileName; + } + + public byte[] getData() { + return data; + } + + @Override + public String toString() { + return fileName; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java new file mode 100644 index 00000000..a343b977 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java @@ -0,0 +1,119 @@ +package jadx.plugins.input.java; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.utils.ZipSecurity; + +public class JavaFileLoader { + private static final Logger LOG = LoggerFactory.getLogger(JavaFileLoader.class); + + public static final int MAX_MAGIC_SIZE = 4; + public static final byte[] JAVA_CLASS_FILE_MAGIC = { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE }; + public static final byte[] ZIP_FILE_MAGIC = { 0x50, 0x4B, 0x03, 0x04 }; + + private static int classUniqId = 1; + + public static List collectFiles(List inputFiles) { + return inputFiles.stream() + .map(Path::toFile) + .map(JavaFileLoader::loadFromFile) + .filter(list -> !list.isEmpty()) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + private static List loadFromFile(File file) { + try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { + return loadReader(file, inputStream, file.getAbsolutePath()); + } catch (Exception e) { + LOG.error("File open error: {}", file.getAbsolutePath(), e); + return Collections.emptyList(); + } + } + + private static List loadReader(File file, InputStream in, String inputFileName) throws IOException { + byte[] magic = new byte[MAX_MAGIC_SIZE]; + if (in.read(magic) != magic.length) { + return Collections.emptyList(); + } + if (isStartWithBytes(magic, JAVA_CLASS_FILE_MAGIC)) { + byte[] data = loadBytes(magic, in); + JavaClassReader reader = new JavaClassReader(getNextUniqId(), inputFileName, data); + return Collections.singletonList(reader); + } + if (file != null && isStartWithBytes(magic, ZIP_FILE_MAGIC)) { + return collectFromZip(file); + } + return Collections.emptyList(); + } + + private static List collectFromZip(File file) { + List result = new ArrayList<>(); + try { + ZipSecurity.readZipEntries(file, (entry, in) -> { + try { + result.addAll(loadReader(null, in, entry.getName())); + } catch (Exception e) { + LOG.error("Failed to read zip entry: {}", entry, e); + } + }); + } catch (Exception e) { + LOG.error("Failed to process zip file: {}", file.getAbsolutePath(), e); + } + return result; + } + + public static boolean isStartWithBytes(byte[] fileMagic, byte[] expectedBytes) { + int len = expectedBytes.length; + if (fileMagic.length < len) { + return false; + } + for (int i = 0; i < len; i++) { + if (fileMagic[i] != expectedBytes[i]) { + return false; + } + } + return true; + } + + public static byte[] loadBytes(byte[] prefix, InputStream in) throws IOException { + int estimateSize = prefix.length + in.available(); + ByteArrayOutputStream out = new ByteArrayOutputStream(estimateSize); + out.write(prefix); + byte[] buffer = new byte[0xFFFF]; + while (true) { + int len = in.read(buffer); + if (len == -1) { + break; + } + out.write(buffer, 0, len); + } + return out.toByteArray(); + } + + private static int getNextUniqId() { + classUniqId++; + if (classUniqId >= 0xFFFF) { + resetDexUniqId(); + } + return classUniqId; + } + + public static void resetDexUniqId() { + classUniqId = 1; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java new file mode 100644 index 00000000..764a8ee4 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaInputPlugin.java @@ -0,0 +1,31 @@ +package jadx.plugins.input.java; + +import java.nio.file.Path; +import java.util.List; + +import jadx.api.plugins.JadxPluginInfo; +import jadx.api.plugins.input.JadxInputPlugin; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.impl.EmptyLoadResult; + +public class JavaInputPlugin implements JadxInputPlugin { + + public static final JadxPluginInfo PLUGIN_INFO = new JadxPluginInfo( + "java-input", + "JavaInput", + "Load .class and .jar files"); + + @Override + public JadxPluginInfo getPluginInfo() { + return PLUGIN_INFO; + } + + @Override + public ILoadResult loadFiles(List inputFiles) { + List readers = JavaFileLoader.collectFiles(inputFiles); + if (readers.isEmpty()) { + return EmptyLoadResult.INSTANCE; + } + return new JavaLoadResult(readers); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java new file mode 100644 index 00000000..95800055 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaLoadResult.java @@ -0,0 +1,46 @@ +package jadx.plugins.input.java; + +import java.util.List; +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.IResourceData; + +public class JavaLoadResult implements ILoadResult { + private static final Logger LOG = LoggerFactory.getLogger(JavaLoadResult.class); + + private final List readers; + + public JavaLoadResult(List readers) { + this.readers = readers; + } + + @Override + public void visitClasses(Consumer consumer) { + for (JavaClassReader reader : readers) { + try { + consumer.accept(reader.loadClassData()); + } catch (Exception e) { + LOG.error("Failed to load class data for file: " + reader.getFileName(), e); + } + } + } + + @Override + public void visitResources(Consumer consumer) { + } + + @Override + public boolean isEmpty() { + return readers.isEmpty(); + } + + @Override + public void close() { + readers.clear(); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ClassOffsets.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ClassOffsets.java new file mode 100644 index 00000000..085f24f1 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ClassOffsets.java @@ -0,0 +1,99 @@ +package jadx.plugins.input.java.data; + +public class ClassOffsets { + + private final int[] constPoolOffsets; + private final int constPoolEnd; + private final int interfacesEnd; + private final int attributesOffset; + + public ClassOffsets(DataReader data) { + this.constPoolOffsets = readConstPool(data); + this.constPoolEnd = data.getOffset(); + int interfacesCount = data.absPos(constPoolEnd + 6).readU2(); + data.skip(interfacesCount * 2); + this.interfacesEnd = data.getOffset(); + skipFields(data); + skipMethods(data); + this.attributesOffset = data.getOffset(); + } + + private static int[] readConstPool(DataReader data) { + int cpSize = data.absPos(8).readU2(); + int[] cpOffsets = new int[cpSize + 1]; + for (int i = 1; i < cpSize; i++) { + int tag = data.readU1(); + cpOffsets[i] = data.getOffset(); + ConstantType constType = ConstantType.getTypeByTag(tag); + switch (constType) { + case UTF8: + data.skip(data.readU2()); + break; + + case LONG: + case DOUBLE: + data.skip(8); + i++; + break; + + default: + data.skip(constType.getDataSize()); + break; + } + } + return cpOffsets; + } + + private void skipFields(DataReader data) { + int fieldsCount = data.readU2(); + for (int i = 0; i < fieldsCount; i++) { + data.skip(6); + skipAttributes(data); + } + } + + private void skipMethods(DataReader data) { + int methodsCount = data.readU2(); + for (int i = 0; i < methodsCount; i++) { + data.skip(6); + skipAttributes(data); + } + } + + private void skipAttributes(DataReader data) { + int attrCount = data.readU2(); + for (int i = 0; i < attrCount; i++) { + data.skip(2); + int len = data.readU4(); + data.skip(len); + } + } + + public int getOffsetOfConstEntry(int num) { + return constPoolOffsets[num]; + } + + public int getAccessFlagsOffset() { + return constPoolEnd; + } + + public int getClsTypeOffset() { + return constPoolEnd + 2; + } + + public int getSuperTypeOffset() { + return constPoolEnd + 4; + } + + public int getInterfacesOffset() { + return constPoolEnd + 6; + } + + public int getFieldsOffset() { + return interfacesEnd; + } + + public int getAttributesOffset() { + return attributesOffset; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ConstPoolReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ConstPoolReader.java new file mode 100644 index 00000000..0b5fc181 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ConstPoolReader.java @@ -0,0 +1,259 @@ +package jadx.plugins.input.java.data; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ICallSite; +import jadx.api.plugins.input.data.IFieldRef; +import jadx.api.plugins.input.data.IMethodHandle; +import jadx.api.plugins.input.data.IMethodRef; +import jadx.api.plugins.input.data.MethodHandleType; +import jadx.api.plugins.input.data.annotations.EncodedType; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.impl.CallSite; +import jadx.api.plugins.input.data.impl.FieldRefHandle; +import jadx.api.plugins.input.data.impl.MethodRefHandle; +import jadx.plugins.input.java.JavaClassReader; +import jadx.plugins.input.java.data.attributes.JavaAttrType; +import jadx.plugins.input.java.data.attributes.types.JavaBootstrapMethodsAttr; +import jadx.plugins.input.java.data.attributes.types.data.RawBootstrapMethod; +import jadx.plugins.input.java.utils.DescriptorParser; +import jadx.plugins.input.java.utils.JavaClassParseException; + +public class ConstPoolReader { + private final JavaClassReader clsReader; + private final JavaClassData clsData; + private final DataReader data; + private final ClassOffsets offsets; + + public ConstPoolReader(JavaClassReader clsReader, JavaClassData javaClassData, DataReader data, ClassOffsets offsets) { + this.clsReader = clsReader; + this.clsData = javaClassData; + this.data = data; + this.offsets = offsets; + } + + @Nullable + public String getClass(int idx) { + jumpToData(idx); + int nameIdx = data.readU2(); + return fixType(getUtf8(nameIdx)); + } + + public IFieldRef getFieldRef(int idx) { + jumpToData(idx); + int clsIdx = data.readU2(); + int nameTypeIdx = data.readU2(); + jumpToData(nameTypeIdx); + int nameIdx = data.readU2(); + int typeIdx = data.readU2(); + + JavaFieldData fieldData = new JavaFieldData(); + fieldData.setParentClassType(getClass(clsIdx)); + fieldData.setName(getUtf8(nameIdx)); + fieldData.setType(getUtf8(typeIdx)); + return fieldData; + } + + public String getFieldType(int idx) { + jumpToData(idx); + data.skip(2); + int nameTypeIdx = data.readU2(); + jumpToData(nameTypeIdx); + data.skip(2); + int typeIdx = data.readU2(); + return getUtf8(typeIdx); + } + + public IMethodRef getMethodRef(int idx) { + jumpToData(idx); + int clsIdx = data.readU2(); + int nameTypeIdx = data.readU2(); + jumpToData(nameTypeIdx); + int nameIdx = data.readU2(); + int descIdx = data.readU2(); + + JavaMethodRef mthRef = new JavaMethodRef(); + mthRef.initUniqId(clsReader, clsIdx, nameIdx, descIdx); + mthRef.setParentClassType(getClass(clsIdx)); + mthRef.setName(getUtf8(nameIdx)); + mthRef.setDescr(getUtf8(descIdx)); + return mthRef; + } + + public ICallSite getCallSite(int idx) { + ConstantType constType = jumpToConst(idx); + switch (constType) { + case INVOKE_DYNAMIC: + int bootstrapMthIdx = data.readU2(); + int nameAndTypeIdx = data.readU2(); + jumpToData(nameAndTypeIdx); + int nameIdx = data.readU2(); + int descIdx = data.readU2(); + return resolveMethodCallSite(bootstrapMthIdx, nameIdx, descIdx); + case DYNAMIC: + throw new JavaClassParseException("Field call site not yet implemented"); + default: + throw new JavaClassParseException("Unexpected tag type for call site: " + constType); + } + } + + private CallSite resolveMethodCallSite(int bootstrapMthIdx, int nameIdx, int descIdx) { + JavaBootstrapMethodsAttr bootstrapMethodsAttr = clsData.loadAttribute(data, JavaAttrType.BOOTSTRAP_METHODS); + if (bootstrapMethodsAttr == null) { + throw new JavaClassParseException("Unexpected missing BootstrapMethods attribute"); + } + RawBootstrapMethod rawBootstrapMethod = bootstrapMethodsAttr.getList().get(bootstrapMthIdx); + + List values = new ArrayList<>(6); + values.add(new EncodedValue(EncodedType.ENCODED_METHOD_HANDLE, getMethodHandle(rawBootstrapMethod.getMethodHandleIdx()))); + values.add(new EncodedValue(EncodedType.ENCODED_STRING, getUtf8(nameIdx))); + values.add(new EncodedValue(EncodedType.ENCODED_METHOD_TYPE, DescriptorParser.parseToMethodProto(getUtf8(descIdx)))); + for (int argConstIdx : rawBootstrapMethod.getArgs()) { + values.add(readAsEncodedValue(argConstIdx)); + } + return new CallSite(values); + } + + private IMethodHandle getMethodHandle(int idx) { + jumpToData(idx); + int kind = data.readU1(); + int refIdx = data.readU2(); + MethodHandleType handleType = convertMethodHandleKind(kind); + if (handleType.isField()) { + return new FieldRefHandle(handleType, getFieldRef(refIdx)); + } + return new MethodRefHandle(handleType, getMethodRef(refIdx)); + } + + private MethodHandleType convertMethodHandleKind(int kind) { + switch (kind) { + case 1: + return MethodHandleType.STATIC_PUT; + case 2: + return MethodHandleType.STATIC_GET; + case 3: + return MethodHandleType.INSTANCE_PUT; + case 4: + return MethodHandleType.INSTANCE_GET; + case 5: + return MethodHandleType.INVOKE_INSTANCE; + case 6: + return MethodHandleType.INVOKE_STATIC; + case 7: + return MethodHandleType.INVOKE_DIRECT; + case 8: + return MethodHandleType.INVOKE_CONSTRUCTOR; + case 9: + return MethodHandleType.INVOKE_INTERFACE; + default: + throw new IllegalArgumentException("Unknown method handle type: " + kind); + } + } + + public String getUtf8(int idx) { + if (idx == 0) { + return null; + } + jumpToData(idx); + return readString(); + } + + public ConstantType jumpToConst(int idx) { + jumpToTag(idx); + return ConstantType.getTypeByTag(data.readU1()); + } + + public String readString() { + int len = data.readU2(); + byte[] bytes = data.readBytes(len); + return parseString(bytes); + } + + public int readU2() { + return data.readU2(); + } + + public int readU4() { + return data.readU4(); + } + + public long readU8() { + return data.readU8(); + } + + public int getInt(int idx) { + jumpToData(idx); + return data.readS4(); + } + + public long getLong(int idx) { + jumpToData(idx); + return data.readS8(); + } + + public double getDouble(int idx) { + jumpToData(idx); + return Double.longBitsToDouble(data.readU8()); + } + + public float getFloat(int idx) { + jumpToData(idx); + return Float.intBitsToFloat(data.readU4()); + } + + public EncodedValue readAsEncodedValue(int idx) { + ConstantType constantType = jumpToConst(idx); + switch (constantType) { + case UTF8: + return new EncodedValue(EncodedType.ENCODED_STRING, readString()); + case STRING: + return new EncodedValue(EncodedType.ENCODED_STRING, getUtf8(readU2())); + case INTEGER: + return new EncodedValue(EncodedType.ENCODED_INT, data.readS4()); + case FLOAT: + return new EncodedValue(EncodedType.ENCODED_FLOAT, Float.intBitsToFloat(data.readU4())); + case LONG: + return new EncodedValue(EncodedType.ENCODED_LONG, data.readS8()); + case DOUBLE: + return new EncodedValue(EncodedType.ENCODED_DOUBLE, Double.longBitsToDouble(data.readU8())); + case METHOD_TYPE: + return new EncodedValue(EncodedType.ENCODED_METHOD_TYPE, DescriptorParser.parseToMethodProto(getUtf8(readU2()))); + case METHOD_HANDLE: + return new EncodedValue(EncodedType.ENCODED_METHOD_HANDLE, getMethodHandle(idx)); + + default: + throw new JavaClassParseException("Can't encode constant " + constantType + " as encoded value"); + } + } + + @NotNull + private String parseString(byte[] bytes) { + // TODO: parse modified UTF-8 + return new String(bytes, StandardCharsets.UTF_8); + } + + private String fixType(String clsName) { + switch (clsName.charAt(0)) { + case 'L': + case 'T': + case '[': + return clsName; + + default: + return 'L' + clsName + ';'; + } + } + + private void jumpToData(int idx) { + data.absPos(offsets.getOffsetOfConstEntry(idx)); + } + + private void jumpToTag(int idx) { + data.absPos(offsets.getOffsetOfConstEntry(idx) - 1); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ConstantType.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ConstantType.java new file mode 100644 index 00000000..103208dd --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/ConstantType.java @@ -0,0 +1,65 @@ +package jadx.plugins.input.java.data; + +import java.util.stream.Stream; + +import jadx.plugins.input.java.utils.JavaClassParseException; + +public enum ConstantType { + UTF8(1, -1), + INTEGER(3, 4), + FLOAT(4, 4), + LONG(5, 8), + DOUBLE(6, 8), + CLASS(7, 2), + STRING(8, 2), + FIELD_REF(9, 4), + METHOD_REF(10, 4), + INTERFACE_METHOD_REF(11, 4), + NAME_AND_TYPE(12, 4), + METHOD_HANDLE(15, 3), + METHOD_TYPE(16, 2), + DYNAMIC(17, 4), + INVOKE_DYNAMIC(18, 4), + MODULE(19, 2), + PACKAGE(20, 2); + + private static final ConstantType[] TAG_MAP; + + static { + ConstantType[] values = ConstantType.values(); + int maxVal = Stream.of(values) + .mapToInt(ConstantType::getTag) + .max() + .orElseThrow(() -> new IllegalArgumentException("Empty ConstantType enum")); + + ConstantType[] map = new ConstantType[maxVal + 1]; + for (ConstantType value : values) { + map[value.getTag()] = value; + } + TAG_MAP = map; + } + + public static ConstantType getTypeByTag(int tag) { + ConstantType type = TAG_MAP[tag]; + if (type == null) { + throw new JavaClassParseException("Unknown constant pool tag: " + tag); + } + return type; + } + + private final byte tag; + private final int dataSize; + + ConstantType(int tag, int dataSize) { + this.tag = (byte) tag; + this.dataSize = dataSize; + } + + public byte getTag() { + return tag; + } + + public int getDataSize() { + return dataSize; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/DataReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/DataReader.java new file mode 100644 index 00000000..2ba40330 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/DataReader.java @@ -0,0 +1,125 @@ +package jadx.plugins.input.java.data; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class DataReader { + private final byte[] data; + private int offset; + + public DataReader(byte[] data) { + this(data, 0); + } + + public DataReader(byte[] data, int offset) { + this.data = data; + this.offset = offset; + } + + public DataReader copy() { + return new DataReader(data, offset); + } + + public DataReader absPos(int offset) { + this.offset = offset; + return this; + } + + public int getOffset() { + return offset; + } + + public void skip(int size) { + this.offset += size; + } + + public int readS1() { + int pos = this.offset; + byte b1 = this.data[pos]; + this.offset = pos + 1; + return b1; + } + + public int readU1() { + int pos = this.offset; + byte b1 = this.data[pos]; + this.offset = pos + 1; + return b1 & 0xFF; + } + + public int readS2() { + int pos = this.offset; + byte[] d = this.data; + byte b1 = d[pos++]; + byte b2 = d[pos++]; + this.offset = pos; + return b1 << 8 | (b2 & 0xFF); + } + + public int readU2() { + int pos = this.offset; + byte[] d = this.data; + byte b1 = d[pos++]; + byte b2 = d[pos++]; + this.offset = pos; + return (b1 & 0xFF) << 8 | (b2 & 0xFF); + } + + public int readS4() { + int pos = this.offset; + byte[] d = this.data; + byte b1 = d[pos++]; + byte b2 = d[pos++]; + byte b3 = d[pos++]; + byte b4 = d[pos++]; + this.offset = pos; + return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); + } + + public int readU4() { + int pos = this.offset; + byte[] d = this.data; + byte b1 = d[pos++]; + byte b2 = d[pos++]; + byte b3 = d[pos++]; + byte b4 = d[pos++]; + this.offset = pos; + return (b1 & 0xFF) << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); + } + + public long readS8() { + long high = readS4(); + long low = readU4() & 0xFFFF_FFFFL; + return high << 32 | low; + } + + public long readU8() { + long high = readU4() & 0xFFFF_FFFFL; + long low = readU4() & 0xFFFF_FFFFL; + return high << 32 | low; + } + + public byte[] readBytes(int len) { + int pos = this.offset; + this.offset = pos + len; + return Arrays.copyOfRange(data, pos, pos + len); + } + + public List readClassesList(ConstPoolReader constPool) { + int len = readU2(); + if (len == 0) { + return Collections.emptyList(); + } + List list = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + list.add(constPool.getClass(readU2())); + } + return list; + } + + public byte[] getBytes() { + return data; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaClassData.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaClassData.java new file mode 100644 index 00000000..a89cb5fb --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaClassData.java @@ -0,0 +1,184 @@ +package jadx.plugins.input.java.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.api.plugins.utils.Utils; +import jadx.plugins.input.java.JavaClassReader; +import jadx.plugins.input.java.data.attributes.AttributesReader; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.JavaAttrStorage; +import jadx.plugins.input.java.data.attributes.JavaAttrType; +import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr; +import jadx.plugins.input.java.utils.DisasmUtils; + +public class JavaClassData implements IClassData { + private final JavaClassReader clsReader; + private final DataReader data; + private final ClassOffsets offsets; + private final ConstPoolReader constPoolReader; + private final AttributesReader attributesReader; + + public JavaClassData(JavaClassReader clsReader) { + this.clsReader = clsReader; + this.data = new DataReader(clsReader.getData()); + this.offsets = new ClassOffsets(this.data); + this.constPoolReader = new ConstPoolReader(clsReader, this, this.data.copy(), this.offsets); + this.attributesReader = new AttributesReader(this, this.constPoolReader); + } + + @Override + public IClassData copy() { + return this; + } + + @Override + public int getAccessFlags() { + return data.absPos(offsets.getAccessFlagsOffset()).readU2(); + } + + @Override + public String getType() { + int idx = data.absPos(offsets.getClsTypeOffset()).readU2(); + return constPoolReader.getClass(idx); + } + + @Override + @Nullable + public String getSuperType() { + int idx = data.absPos(offsets.getSuperTypeOffset()).readU2(); + if (idx == 0) { + return null; + } + return constPoolReader.getClass(idx); + } + + @Override + public List getInterfacesTypes() { + data.absPos(offsets.getInterfacesOffset()); + return data.readClassesList(constPoolReader); + } + + @Override + public String getInputFileName() { + return this.clsReader.getFileName(); + } + + @Override + public void visitFieldsAndMethods(Consumer fieldsConsumer, Consumer mthConsumer) { + int clsIdx = data.absPos(offsets.getClsTypeOffset()).readU2(); + String classType = constPoolReader.getClass(clsIdx); + DataReader reader = data.absPos(offsets.getFieldsOffset()).copy(); + int fieldsCount = reader.readU2(); + if (fieldsCount != 0) { + JavaFieldData field = new JavaFieldData(); + field.setParentClassType(classType); + for (int i = 0; i < fieldsCount; i++) { + parseField(reader, field); + fieldsConsumer.accept(field); + } + } + + int methodsCount = reader.readU2(); + if (methodsCount != 0) { + JavaMethodRef methodRef = new JavaMethodRef(); + methodRef.setParentClassType(classType); + JavaMethodData method = new JavaMethodData(this, methodRef); + for (int i = 0; i < methodsCount; i++) { + parseMethod(reader, method, clsIdx); + mthConsumer.accept(method); + } + } + } + + private void parseField(DataReader reader, JavaFieldData field) { + int accessFlags = reader.readU2(); + int nameIdx = reader.readU2(); + int typeIdx = reader.readU2(); + JavaAttrStorage attributes = attributesReader.load(reader); + + field.setAccessFlags(accessFlags); + field.setName(constPoolReader.getUtf8(nameIdx)); + field.setType(constPoolReader.getUtf8(typeIdx)); + field.setAttributes(attributes); + } + + private void parseMethod(DataReader reader, JavaMethodData method, int clsIdx) { + int accessFlags = reader.readU2(); + int nameIdx = reader.readU2(); + int descriptorIdx = reader.readU2(); + JavaAttrStorage attributes = attributesReader.load(reader); + + JavaMethodRef methodRef = method.getMethodRef(); + methodRef.reset(); + methodRef.initUniqId(clsReader, clsIdx, nameIdx, descriptorIdx); + methodRef.setName(constPoolReader.getUtf8(nameIdx)); + methodRef.setDescr(constPoolReader.getUtf8(descriptorIdx)); + + if (methodRef.getName().equals("")) { + accessFlags |= AccessFlags.CONSTRUCTOR; // java bytecode don't use that flag + } + + method.setData(accessFlags, attributes); + } + + public DataReader getData() { + return data; + } + + @Override + public List getAttributes() { + data.absPos(offsets.getAttributesOffset()); + JavaAttrStorage attributes = attributesReader.load(data); + int size = attributes.size(); + if (size == 0) { + return Collections.emptyList(); + } + List list = new ArrayList<>(size); + Utils.addToList(list, JavaAnnotationsAttr.merge(attributes)); + Utils.addToList(list, attributes.get(JavaAttrType.INNER_CLASSES)); + Utils.addToList(list, attributes.get(JavaAttrType.SOURCE_FILE)); + Utils.addToList(list, attributes.get(JavaAttrType.SIGNATURE)); + return list; + } + + public T loadAttribute(DataReader reader, JavaAttrType type) { + reader.absPos(offsets.getAttributesOffset()); + return attributesReader.loadOne(type, reader); + } + + @Override + public String getDisassembledCode() { + return DisasmUtils.get(data.getBytes()); + } + + public JavaClassReader getClsReader() { + return clsReader; + } + + public ClassOffsets getOffsets() { + return offsets; + } + + public ConstPoolReader getConstPoolReader() { + return constPoolReader; + } + + public AttributesReader getAttributesReader() { + return attributesReader; + } + + @Override + public String toString() { + return getInputFileName(); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaFieldData.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaFieldData.java new file mode 100644 index 00000000..1cabb88f --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaFieldData.java @@ -0,0 +1,79 @@ +package jadx.plugins.input.java.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.api.plugins.utils.Utils; +import jadx.plugins.input.java.data.attributes.JavaAttrStorage; +import jadx.plugins.input.java.data.attributes.JavaAttrType; +import jadx.plugins.input.java.data.attributes.types.ConstValueAttr; +import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr; + +public class JavaFieldData implements IFieldData { + private String name; + private String parentClassType; + private String type; + private int accessFlags; + private JavaAttrStorage attributes; + + @Override + public String getParentClassType() { + return parentClassType; + } + + public void setParentClassType(String parentClassType) { + this.parentClassType = parentClassType; + } + + @Override + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int getAccessFlags() { + return accessFlags; + } + + public void setAccessFlags(int accessFlags) { + this.accessFlags = accessFlags; + } + + public void setAttributes(JavaAttrStorage attributes) { + this.attributes = attributes; + } + + @Override + public List getAttributes() { + int size = attributes.size(); + if (size == 0) { + return Collections.emptyList(); + } + List list = new ArrayList<>(size); + Utils.addToList(list, JavaAnnotationsAttr.merge(attributes)); + Utils.addToList(list, attributes.get(JavaAttrType.CONST_VALUE), ConstValueAttr::getValue); + Utils.addToList(list, attributes.get(JavaAttrType.SIGNATURE)); + return list; + } + + @Override + public String toString() { + return parentClassType + "->" + name + ":" + type; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodData.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodData.java new file mode 100644 index 00000000..31ae1f4b --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodData.java @@ -0,0 +1,81 @@ +package jadx.plugins.input.java.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.api.plugins.utils.Utils; +import jadx.plugins.input.java.data.attributes.JavaAttrStorage; +import jadx.plugins.input.java.data.attributes.JavaAttrType; +import jadx.plugins.input.java.data.attributes.types.CodeAttr; +import jadx.plugins.input.java.data.attributes.types.JavaAnnotationDefaultAttr; +import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr; +import jadx.plugins.input.java.data.attributes.types.JavaParamAnnsAttr; +import jadx.plugins.input.java.data.code.JavaCodeReader; + +public class JavaMethodData implements IMethodData { + + private final JavaClassData clsData; + private final JavaMethodRef methodRef; + private int accessFlags; + private JavaAttrStorage attributes; + + public JavaMethodData(JavaClassData clsData, JavaMethodRef methodRef) { + this.clsData = clsData; + this.methodRef = methodRef; + } + + public void setData(int accessFlags, JavaAttrStorage attributes) { + this.accessFlags = accessFlags; + this.attributes = attributes; + } + + @Override + public JavaMethodRef getMethodRef() { + return methodRef; + } + + @Override + public int getAccessFlags() { + return accessFlags; + } + + @Override + public @Nullable ICodeReader getCodeReader() { + CodeAttr codeAttr = attributes.get(JavaAttrType.CODE); + if (codeAttr == null) { + return null; + } + return new JavaCodeReader(clsData, codeAttr.getOffset()); + } + + @Override + public String disassembleMethod() { + return ""; + } + + @Override + public List getAttributes() { + int size = attributes.size(); + if (size == 0) { + return Collections.emptyList(); + } + List list = new ArrayList<>(size); + Utils.addToList(list, JavaAnnotationsAttr.merge(attributes)); + Utils.addToList(list, JavaParamAnnsAttr.merge(attributes)); + Utils.addToList(list, JavaAnnotationDefaultAttr.convert(attributes)); + Utils.addToList(list, attributes.get(JavaAttrType.SIGNATURE)); + Utils.addToList(list, attributes.get(JavaAttrType.EXCEPTIONS)); + return list; + } + + @Override + public String toString() { + return getMethodRef().toString(); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodProto.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodProto.java new file mode 100644 index 00000000..2b2de95f --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodProto.java @@ -0,0 +1,35 @@ +package jadx.plugins.input.java.data; + +import java.util.List; + +import jadx.api.plugins.input.data.IMethodProto; +import jadx.api.plugins.utils.Utils; + +public class JavaMethodProto implements IMethodProto { + + private String returnType; + private List argTypes; + + @Override + public String getReturnType() { + return returnType; + } + + public void setReturnType(String returnType) { + this.returnType = returnType; + } + + @Override + public List getArgTypes() { + return argTypes; + } + + public void setArgTypes(List argTypes) { + this.argTypes = argTypes; + } + + @Override + public String toString() { + return "(" + Utils.listToStr(argTypes) + ")" + returnType; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodRef.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodRef.java new file mode 100644 index 00000000..9a614b4f --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/JavaMethodRef.java @@ -0,0 +1,66 @@ +package jadx.plugins.input.java.data; + +import jadx.api.plugins.input.data.IMethodRef; +import jadx.plugins.input.java.JavaClassReader; +import jadx.plugins.input.java.utils.DescriptorParser; + +public class JavaMethodRef extends JavaMethodProto implements IMethodRef { + + private int uniqId; + private String parentClassType; + private String name; + private String descr; + + @Override + public int getUniqId() { + return uniqId; + } + + public void initUniqId(JavaClassReader clsReader, int clsIdx, int nameIdx, int descIdx) { + // TODO: check for id overlap + this.uniqId = (clsReader.getId() & 0xFFF) << 20 | (clsIdx & 0xFF) << 12 | (nameIdx & 0xFF) << 4 | descIdx & 0xF; + } + + @Override + public String getParentClassType() { + return parentClassType; + } + + public void setParentClassType(String parentClassType) { + this.parentClassType = parentClassType; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescriptor() { + return descr; + } + + public void setDescr(String descr) { + this.descr = descr; + } + + public void reset() { + this.setReturnType(null); + this.setArgTypes(null); + } + + @Override + public void load() { + if (getReturnType() == null) { + DescriptorParser.fillMethodProto(descr, this); + } + } + + @Override + public String toString() { + return parentClassType + "->" + name + descr; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/AttributesReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/AttributesReader.java new file mode 100644 index 00000000..22ae0128 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/AttributesReader.java @@ -0,0 +1,108 @@ +package jadx.plugins.input.java.data.attributes; + +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.JavaClassData; + +public class AttributesReader { + private static final Logger LOG = LoggerFactory.getLogger(AttributesReader.class); + + private final JavaClassData clsData; + private final ConstPoolReader constPool; + private final Map> attrMap = new HashMap<>(JavaAttrType.size()); + + public AttributesReader(JavaClassData clsData, ConstPoolReader constPoolReader) { + this.clsData = clsData; + this.constPool = constPoolReader; + } + + public JavaAttrStorage load(DataReader reader) { + int attributesCount = reader.readU2(); + if (attributesCount == 0) { + return JavaAttrStorage.EMPTY; + } + JavaAttrStorage storage = new JavaAttrStorage(); + for (int i = 0; i < attributesCount; i++) { + readAndAdd(storage, reader); + } + return storage; + } + + private void readAndAdd(JavaAttrStorage storage, DataReader reader) { + int nameIdx = reader.readU2(); + int len = reader.readU4(); + int end = reader.getOffset() + len; + try { + JavaAttrType attrType = resolveAttrReader(nameIdx); + if (attrType == null) { + return; + } + IJavaAttributeReader attrReader = attrType.getReader(); + if (attrReader == null) { + // ignore attribute + return; + } + IJavaAttribute attrValue = attrReader.read(clsData, reader); + if (attrValue != null) { + storage.add(attrType, attrValue); + } + } catch (Exception e) { + LOG.error("Failed to parse attribute: {}", constPool.getUtf8(nameIdx), e); + } finally { + reader.absPos(end); + } + } + + @SuppressWarnings("unchecked") + @Nullable + public T loadOne(JavaAttrType type, DataReader reader) { + int attributesCount = reader.readU2(); + if (attributesCount == 0) { + return null; + } + for (int i = 0; i < attributesCount; i++) { + IJavaAttribute attr = readType(type, reader); + if (attr != null) { + return (T) attr; + } + } + return null; + } + + private IJavaAttribute readType(JavaAttrType type, DataReader reader) { + int nameIdx = reader.readU2(); + int len = reader.readU4(); + int end = reader.getOffset() + len; + try { + JavaAttrType attrType = resolveAttrReader(nameIdx); + if (attrType == null || attrType != type) { + return null; + } + return attrType.getReader().read(clsData, reader); + } catch (Exception e) { + LOG.error("Failed to parse attribute: {}", constPool.getUtf8(nameIdx), e); + return null; + } finally { + reader.absPos(end); + } + } + + private JavaAttrType resolveAttrReader(int nameIdx) { + return attrMap.computeIfAbsent(nameIdx, idx -> { + String attrName = constPool.getUtf8(nameIdx); + JavaAttrType attrType = JavaAttrType.byName(attrName); + if (attrType == null) { + LOG.warn("Unknown java class attribute type: {}", attrName); + return null; + } + return attrType; + }); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/EncodedValueReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/EncodedValueReader.java new file mode 100644 index 00000000..7fc96bee --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/EncodedValueReader.java @@ -0,0 +1,65 @@ +package jadx.plugins.input.java.data.attributes; + +import java.util.ArrayList; +import java.util.List; + +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.impl.JadxFieldRef; +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.JavaClassData; +import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr; +import jadx.plugins.input.java.utils.JavaClassParseException; + +public class EncodedValueReader { + + public static EncodedValue read(JavaClassData clsData, DataReader reader) { + ConstPoolReader constPool = clsData.getConstPoolReader(); + char tag = (char) reader.readU1(); + switch (tag) { + case 'B': + return new EncodedValue(EncodedType.ENCODED_BYTE, (byte) constPool.getInt(reader.readU2())); + case 'C': + return new EncodedValue(EncodedType.ENCODED_CHAR, (char) constPool.getInt(reader.readU2())); + case 'D': + return new EncodedValue(EncodedType.ENCODED_DOUBLE, constPool.getDouble(reader.readU2())); + case 'F': + return new EncodedValue(EncodedType.ENCODED_FLOAT, constPool.getFloat(reader.readU2())); + case 'I': + return new EncodedValue(EncodedType.ENCODED_INT, constPool.getInt(reader.readU2())); + case 'J': + return new EncodedValue(EncodedType.ENCODED_LONG, constPool.getLong(reader.readU2())); + case 'S': + return new EncodedValue(EncodedType.ENCODED_SHORT, (short) constPool.getInt(reader.readU2())); + case 'Z': + return new EncodedValue(EncodedType.ENCODED_BOOLEAN, 1 == constPool.getInt(reader.readU2())); + case 's': + return new EncodedValue(EncodedType.ENCODED_STRING, constPool.getUtf8(reader.readU2())); + + case 'e': + String cls = constPool.getUtf8(reader.readU2()); + String name = constPool.getUtf8(reader.readU2()); + return new EncodedValue(EncodedType.ENCODED_ENUM, new JadxFieldRef(cls, name, cls)); + + case 'c': + return new EncodedValue(EncodedType.ENCODED_TYPE, constPool.getUtf8(reader.readU2())); + + case '@': + return new EncodedValue(EncodedType.ENCODED_ANNOTATION, + JavaAnnotationsAttr.readAnnotation(AnnotationVisibility.RUNTIME, clsData, reader)); + + case '[': + int len = reader.readU2(); + List values = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + values.add(read(clsData, reader)); + } + return new EncodedValue(EncodedType.ENCODED_ARRAY, values); + + default: + throw new JavaClassParseException("Unknown element value tag: " + tag); + } + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/IJavaAttribute.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/IJavaAttribute.java new file mode 100644 index 00000000..e34d77f2 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/IJavaAttribute.java @@ -0,0 +1,4 @@ +package jadx.plugins.input.java.data.attributes; + +public interface IJavaAttribute { +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/IJavaAttributeReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/IJavaAttributeReader.java new file mode 100644 index 00000000..0c059f97 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/IJavaAttributeReader.java @@ -0,0 +1,8 @@ +package jadx.plugins.input.java.data.attributes; + +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.JavaClassData; + +public interface IJavaAttributeReader { + IJavaAttribute read(JavaClassData clsData, DataReader reader); +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/JavaAttrStorage.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/JavaAttrStorage.java new file mode 100644 index 00000000..492f6dc4 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/JavaAttrStorage.java @@ -0,0 +1,34 @@ +package jadx.plugins.input.java.data.attributes; + +import org.jetbrains.annotations.Nullable; + +public class JavaAttrStorage { + public static final JavaAttrStorage EMPTY = new JavaAttrStorage(); + + private final IJavaAttribute[] map = new IJavaAttribute[JavaAttrType.size()]; + + public void add(JavaAttrType type, IJavaAttribute value) { + map[type.getId()] = value; + } + + @SuppressWarnings("unchecked") + @Nullable + public A get(JavaAttrType type) { + return (A) map[type.getId()]; + } + + public int size() { + int size = 0; + for (IJavaAttribute attr : map) { + if (attr != null) { + size++; + } + } + return size; + } + + @Override + public String toString() { + return "AttributesStorage{size=" + size() + '}'; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/JavaAttrType.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/JavaAttrType.java new file mode 100644 index 00000000..69e518d9 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/JavaAttrType.java @@ -0,0 +1,116 @@ +package jadx.plugins.input.java.data.attributes; + +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.annotations.AnnotationVisibility; +import jadx.plugins.input.java.data.attributes.debuginfo.LineNumberTableAttr; +import jadx.plugins.input.java.data.attributes.debuginfo.LocalVarTypesAttr; +import jadx.plugins.input.java.data.attributes.debuginfo.LocalVarsAttr; +import jadx.plugins.input.java.data.attributes.types.CodeAttr; +import jadx.plugins.input.java.data.attributes.types.ConstValueAttr; +import jadx.plugins.input.java.data.attributes.types.IgnoredAttr; +import jadx.plugins.input.java.data.attributes.types.JavaAnnotationDefaultAttr; +import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr; +import jadx.plugins.input.java.data.attributes.types.JavaBootstrapMethodsAttr; +import jadx.plugins.input.java.data.attributes.types.JavaExceptionsAttr; +import jadx.plugins.input.java.data.attributes.types.JavaInnerClsAttr; +import jadx.plugins.input.java.data.attributes.types.JavaParamAnnsAttr; +import jadx.plugins.input.java.data.attributes.types.JavaSignatureAttr; +import jadx.plugins.input.java.data.attributes.types.JavaSourceFileAttr; + +public final class JavaAttrType { + + private static final Map> NAME_TO_TYPE_MAP; + + public static final JavaAttrType INNER_CLASSES; + public static final JavaAttrType BOOTSTRAP_METHODS; + + public static final JavaAttrType CONST_VALUE; + + public static final JavaAttrType CODE; + public static final JavaAttrType LINE_NUMBER_TABLE; + public static final JavaAttrType LOCAL_VAR_TABLE; + public static final JavaAttrType LOCAL_VAR_TYPE_TABLE; + + public static final JavaAttrType RUNTIME_ANNOTATIONS; + public static final JavaAttrType BUILD_ANNOTATIONS; + public static final JavaAttrType RUNTIME_PARAMETER_ANNOTATIONS; + public static final JavaAttrType BUILD_PARAMETER_ANNOTATIONS; + public static final JavaAttrType ANNOTATION_DEFAULT; + + public static final JavaAttrType SOURCE_FILE; + public static final JavaAttrType SIGNATURE; + public static final JavaAttrType EXCEPTIONS; + + public static final JavaAttrType DEPRECATED; + public static final JavaAttrType STACK_MAP_TABLE; + + static { + NAME_TO_TYPE_MAP = new HashMap<>(); + + CONST_VALUE = bind("ConstantValue", ConstValueAttr.reader()); + + CODE = bind("Code", CodeAttr.reader()); + + LINE_NUMBER_TABLE = bind("LineNumberTable", LineNumberTableAttr.reader()); + LOCAL_VAR_TABLE = bind("LocalVariableTable", LocalVarsAttr.reader()); + LOCAL_VAR_TYPE_TABLE = bind("LocalVariableTypeTable", LocalVarTypesAttr.reader()); + + INNER_CLASSES = bind("InnerClasses", JavaInnerClsAttr.reader()); + BOOTSTRAP_METHODS = bind("BootstrapMethods", JavaBootstrapMethodsAttr.reader()); + + RUNTIME_ANNOTATIONS = bind("RuntimeVisibleAnnotations", JavaAnnotationsAttr.reader(AnnotationVisibility.RUNTIME)); + BUILD_ANNOTATIONS = bind("RuntimeInvisibleAnnotations", JavaAnnotationsAttr.reader(AnnotationVisibility.BUILD)); + RUNTIME_PARAMETER_ANNOTATIONS = bind("RuntimeVisibleParameterAnnotations", JavaParamAnnsAttr.reader(AnnotationVisibility.RUNTIME)); + BUILD_PARAMETER_ANNOTATIONS = bind("RuntimeInvisibleParameterAnnotations", JavaParamAnnsAttr.reader(AnnotationVisibility.BUILD)); + ANNOTATION_DEFAULT = bind("AnnotationDefault", JavaAnnotationDefaultAttr.reader()); + + SOURCE_FILE = bind("SourceFile", JavaSourceFileAttr.reader()); + SIGNATURE = bind("Signature", JavaSignatureAttr.reader()); + EXCEPTIONS = bind("Exceptions", JavaExceptionsAttr.reader()); + + // ignored + DEPRECATED = bind("Deprecated", null); // duplicated by annotation + STACK_MAP_TABLE = bind("StackMapTable", null); + } + + private static JavaAttrType bind(String name, IJavaAttributeReader reader) { + JavaAttrType attrType = new JavaAttrType<>(NAME_TO_TYPE_MAP.size(), name, reader); + NAME_TO_TYPE_MAP.put(name, attrType); + return attrType; + } + + @Nullable + public static JavaAttrType byName(String name) { + return NAME_TO_TYPE_MAP.get(name); + } + + public static int size() { + return NAME_TO_TYPE_MAP.size(); + } + + private final int id; + private final String name; + private final IJavaAttributeReader reader; + + private JavaAttrType(int id, String name, IJavaAttributeReader reader) { + this.id = id; + this.name = name; + this.reader = reader; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public IJavaAttributeReader getReader() { + return reader; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/JavaLocalVar.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/JavaLocalVar.java new file mode 100644 index 00000000..6edaf5d3 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/JavaLocalVar.java @@ -0,0 +1,98 @@ +package jadx.plugins.input.java.data.attributes.debuginfo; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ILocalVar; + +public class JavaLocalVar implements ILocalVar { + private int regNum; + private final String name; + private final String type; + @Nullable + private String sign; + + private final int startOffset; + private final int endOffset; + + public JavaLocalVar(int regNum, String name, @Nullable String type, @Nullable String sign, int startOffset, int endOffset) { + this.regNum = regNum; + this.name = name; + this.type = type; + this.sign = sign; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + + public void shiftRegNum(int maxStack) { + this.regNum += maxStack; // convert local var to register + } + + @Override + public String getName() { + return name; + } + + @Override + public int getRegNum() { + return regNum; + } + + @Override + public String getType() { + return type; + } + + @Override + public @Nullable String getSignature() { + return sign; + } + + public void setSignature(String sign) { + this.sign = sign; + } + + @Override + public int getStartOffset() { + return startOffset; + } + + @Override + public int getEndOffset() { + return endOffset; + } + + @Override + public int hashCode() { + int result = regNum; + result = 31 * result + name.hashCode(); + result = 31 * result + startOffset; + result = 31 * result + endOffset; + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof JavaLocalVar)) { + return false; + } + JavaLocalVar other = (JavaLocalVar) o; + return regNum == other.regNum + && startOffset == other.startOffset + && endOffset == other.endOffset + && name.equals(other.name); + } + + private static String formatOffset(int offset) { + return String.format("0x%04x", offset); + } + + @Override + public String toString() { + return formatOffset(startOffset) + '-' + formatOffset(endOffset) + + ": r" + regNum + " '" + name + "' " + type + + (sign != null ? ", signature: " + sign : ""); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LineNumberTableAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LineNumberTableAttr.java new file mode 100644 index 00000000..687f905b --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LineNumberTableAttr.java @@ -0,0 +1,32 @@ +package jadx.plugins.input.java.data.attributes.debuginfo; + +import java.util.HashMap; +import java.util.Map; + +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class LineNumberTableAttr implements IJavaAttribute { + private final Map lineMap; + + public LineNumberTableAttr(Map sourceLineMap) { + this.lineMap = sourceLineMap; + } + + public Map getLineMap() { + return lineMap; + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> { + int len = reader.readU2(); + Map map = new HashMap<>(len); + for (int i = 0; i < len; i++) { + int offset = reader.readU2(); + int line = reader.readU2(); + map.put(offset, line); + } + return new LineNumberTableAttr(map); + }; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LocalVarTypesAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LocalVarTypesAttr.java new file mode 100644 index 00000000..b1c6b356 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LocalVarTypesAttr.java @@ -0,0 +1,42 @@ +package jadx.plugins.input.java.data.attributes.debuginfo; + +import java.util.ArrayList; +import java.util.List; + +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class LocalVarTypesAttr implements IJavaAttribute { + private final List vars; + + public LocalVarTypesAttr(List vars) { + this.vars = vars; + } + + public List getVars() { + return vars; + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> { + ConstPoolReader constPool = clsData.getConstPoolReader(); + int len = reader.readU2(); + List varsList = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + int startOffset = reader.readU2(); + int endOffset = startOffset + reader.readU2() - 1; + int nameIdx = reader.readU2(); + int typeIdx = reader.readU2(); + int varNum = reader.readU2(); + varsList.add(new JavaLocalVar( + varNum, + constPool.getUtf8(nameIdx), + null, + constPool.getUtf8(typeIdx), + startOffset, endOffset)); + } + return new LocalVarTypesAttr(varsList); + }; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LocalVarsAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LocalVarsAttr.java new file mode 100644 index 00000000..631ee100 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/debuginfo/LocalVarsAttr.java @@ -0,0 +1,42 @@ +package jadx.plugins.input.java.data.attributes.debuginfo; + +import java.util.ArrayList; +import java.util.List; + +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class LocalVarsAttr implements IJavaAttribute { + private final List vars; + + public LocalVarsAttr(List vars) { + this.vars = vars; + } + + public List getVars() { + return vars; + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> { + ConstPoolReader constPool = clsData.getConstPoolReader(); + int count = reader.readU2(); + List varsList = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + int startOffset = reader.readU2(); + int length = reader.readU2(); + int endOffset = startOffset + length - 1; + int nameIdx = reader.readU2(); + int typeIdx = reader.readU2(); + int varNum = reader.readU2(); + varsList.add(new JavaLocalVar(varNum, + constPool.getUtf8(nameIdx), + constPool.getUtf8(typeIdx), + null, + startOffset, endOffset)); + } + return new LocalVarsAttr(varsList); + }; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/CodeAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/CodeAttr.java new file mode 100644 index 00000000..531e17bf --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/CodeAttr.java @@ -0,0 +1,20 @@ +package jadx.plugins.input.java.data.attributes.types; + +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class CodeAttr implements IJavaAttribute { + private final int offset; + + public CodeAttr(int offset) { + this.offset = offset; + } + + public int getOffset() { + return offset; + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> new CodeAttr(reader.getOffset()); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/ConstValueAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/ConstValueAttr.java new file mode 100644 index 00000000..ed3df865 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/ConstValueAttr.java @@ -0,0 +1,22 @@ +package jadx.plugins.input.java.data.attributes.types; + +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class ConstValueAttr implements IJavaAttribute { + + private final EncodedValue value; + + public ConstValueAttr(EncodedValue value) { + this.value = value; + } + + public EncodedValue getValue() { + return value; + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> new ConstValueAttr(clsData.getConstPoolReader().readAsEncodedValue(reader.readU2())); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/IgnoredAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/IgnoredAttr.java new file mode 100644 index 00000000..b8ed5abb --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/IgnoredAttr.java @@ -0,0 +1,7 @@ +package jadx.plugins.input.java.data.attributes.types; + +import jadx.plugins.input.java.data.attributes.IJavaAttribute; + +@SuppressWarnings("unused") +public class IgnoredAttr implements IJavaAttribute { +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaAnnotationDefaultAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaAnnotationDefaultAttr.java new file mode 100644 index 00000000..4bed0203 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaAnnotationDefaultAttr.java @@ -0,0 +1,24 @@ +package jadx.plugins.input.java.data.attributes.types; + +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr; +import jadx.plugins.input.java.data.attributes.EncodedValueReader; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; +import jadx.plugins.input.java.data.attributes.JavaAttrStorage; +import jadx.plugins.input.java.data.attributes.JavaAttrType; + +public class JavaAnnotationDefaultAttr extends AnnotationDefaultAttr implements IJavaAttribute { + + public JavaAnnotationDefaultAttr(EncodedValue value) { + super(value); + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> new JavaAnnotationDefaultAttr(EncodedValueReader.read(clsData, reader)); + } + + public static AnnotationDefaultAttr convert(JavaAttrStorage attributes) { + return attributes.get(JavaAttrType.ANNOTATION_DEFAULT); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaAnnotationsAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaAnnotationsAttr.java new file mode 100644 index 00000000..4e0296f9 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaAnnotationsAttr.java @@ -0,0 +1,74 @@ +package jadx.plugins.input.java.data.attributes.types; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import jadx.api.plugins.input.data.annotations.AnnotationVisibility; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.annotations.JadxAnnotation; +import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr; +import jadx.api.plugins.utils.Utils; +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.JavaClassData; +import jadx.plugins.input.java.data.attributes.EncodedValueReader; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; +import jadx.plugins.input.java.data.attributes.JavaAttrStorage; +import jadx.plugins.input.java.data.attributes.JavaAttrType; + +public class JavaAnnotationsAttr implements IJavaAttribute { + private final List list; + + public JavaAnnotationsAttr(List list) { + this.list = list; + } + + public List getList() { + return list; + } + + public static IJavaAttributeReader reader(AnnotationVisibility visibility) { + return (clsData, reader) -> new JavaAnnotationsAttr(readAnnotationsList(visibility, clsData, reader)); + } + + public static List readAnnotationsList(AnnotationVisibility visibility, JavaClassData clsData, DataReader reader) { + int len = reader.readU2(); + List list = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + list.add(readAnnotation(visibility, clsData, reader)); + } + return list; + } + + public static JadxAnnotation readAnnotation(AnnotationVisibility visibility, JavaClassData clsData, DataReader reader) { + ConstPoolReader constPool = clsData.getConstPoolReader(); + String type = constPool.getUtf8(reader.readU2()); + int pairsCount = reader.readU2(); + Map pairs = new LinkedHashMap<>(pairsCount); + for (int j = 0; j < pairsCount; j++) { + String name = constPool.getUtf8(reader.readU2()); + EncodedValue value = EncodedValueReader.read(clsData, reader); + pairs.put(name, value); + } + return new JadxAnnotation(visibility, type, pairs); + } + + public static AnnotationsAttr merge(JavaAttrStorage storage) { + JavaAnnotationsAttr runtimeAnnAttr = storage.get(JavaAttrType.RUNTIME_ANNOTATIONS); + JavaAnnotationsAttr buildAnnAttr = storage.get(JavaAttrType.BUILD_ANNOTATIONS); + if (runtimeAnnAttr == null && buildAnnAttr == null) { + return null; + } + if (buildAnnAttr == null) { + return AnnotationsAttr.pack(runtimeAnnAttr.getList()); + } + if (runtimeAnnAttr == null) { + return AnnotationsAttr.pack(buildAnnAttr.getList()); + } + return AnnotationsAttr.pack(Utils.concat(runtimeAnnAttr.getList(), buildAnnAttr.getList())); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaBootstrapMethodsAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaBootstrapMethodsAttr.java new file mode 100644 index 00000000..c28cdf32 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaBootstrapMethodsAttr.java @@ -0,0 +1,38 @@ +package jadx.plugins.input.java.data.attributes.types; + +import java.util.ArrayList; +import java.util.List; + +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; +import jadx.plugins.input.java.data.attributes.types.data.RawBootstrapMethod; + +public class JavaBootstrapMethodsAttr implements IJavaAttribute { + + private final List list; + + public JavaBootstrapMethodsAttr(List list) { + this.list = list; + } + + public List getList() { + return list; + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> { + int len = reader.readU2(); + List list = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + int methodHandleIdx = reader.readU2(); + int argsCount = reader.readU2(); + int[] args = new int[argsCount]; + for (int j = 0; j < argsCount; j++) { + args[j] = reader.readU2(); + } + list.add(new RawBootstrapMethod(methodHandleIdx, args)); + } + return new JavaBootstrapMethodsAttr(list); + }; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaExceptionsAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaExceptionsAttr.java new file mode 100644 index 00000000..4298711a --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaExceptionsAttr.java @@ -0,0 +1,17 @@ +package jadx.plugins.input.java.data.attributes.types; + +import java.util.List; + +import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class JavaExceptionsAttr extends ExceptionsAttr implements IJavaAttribute { + public JavaExceptionsAttr(List list) { + super(list); + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> new JavaExceptionsAttr(reader.readClassesList(clsData.getConstPoolReader())); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaInnerClsAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaInnerClsAttr.java new file mode 100644 index 00000000..732ebba8 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaInnerClsAttr.java @@ -0,0 +1,34 @@ +package jadx.plugins.input.java.data.attributes.types; + +import java.util.HashMap; +import java.util.Map; + +import jadx.api.plugins.input.data.attributes.types.InnerClassesAttr; +import jadx.api.plugins.input.data.attributes.types.InnerClsInfo; +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class JavaInnerClsAttr extends InnerClassesAttr implements IJavaAttribute { + + public JavaInnerClsAttr(Map map) { + super(map); + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> { + int len = reader.readU2(); + ConstPoolReader constPool = clsData.getConstPoolReader(); + Map clsMap = new HashMap<>(len); + for (int i = 0; i < len; i++) { + String innerCls = constPool.getClass(reader.readU2()); + int outerClsIdx = reader.readU2(); + String outerCls = outerClsIdx == 0 ? null : constPool.getClass(outerClsIdx); + String name = constPool.getUtf8(reader.readU2()); + int accFlags = reader.readU2(); + clsMap.put(innerCls, new InnerClsInfo(innerCls, outerCls, name, accFlags)); + } + return new JavaInnerClsAttr(clsMap); + }; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaParamAnnsAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaParamAnnsAttr.java new file mode 100644 index 00000000..c00f77bb --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaParamAnnsAttr.java @@ -0,0 +1,65 @@ +package jadx.plugins.input.java.data.attributes.types; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jadx.api.plugins.input.data.annotations.AnnotationVisibility; +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr; +import jadx.api.plugins.utils.Utils; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; +import jadx.plugins.input.java.data.attributes.JavaAttrStorage; +import jadx.plugins.input.java.data.attributes.JavaAttrType; + +public class JavaParamAnnsAttr implements IJavaAttribute { + private final List> list; + + public JavaParamAnnsAttr(List> list) { + this.list = list; + } + + public List> getList() { + return list; + } + + public static IJavaAttributeReader reader(AnnotationVisibility visibility) { + return (clsData, reader) -> { + int len = reader.readU1(); + List> list = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + list.add(JavaAnnotationsAttr.readAnnotationsList(visibility, clsData, reader)); + } + return new JavaParamAnnsAttr(list); + }; + } + + public static MethodParamsAttr merge(JavaAttrStorage storage) { + JavaParamAnnsAttr runtimeAnnAttr = storage.get(JavaAttrType.RUNTIME_PARAMETER_ANNOTATIONS); + JavaParamAnnsAttr buildAnnAttr = storage.get(JavaAttrType.BUILD_PARAMETER_ANNOTATIONS); + if (runtimeAnnAttr == null && buildAnnAttr == null) { + return null; + } + if (buildAnnAttr == null) { + return MethodParamsAttr.pack(runtimeAnnAttr.getList()); + } + if (runtimeAnnAttr == null) { + return MethodParamsAttr.pack(buildAnnAttr.getList()); + } + return MethodParamsAttr.pack(mergeParamLists(runtimeAnnAttr.getList(), buildAnnAttr.getList())); + } + + private static List> mergeParamLists(List> first, List> second) { + int firstSize = first.size(); + int secondSize = second.size(); + int size = Math.max(firstSize, secondSize); + List> result = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + List firstList = i < firstSize ? first.get(i) : Collections.emptyList(); + List secondList = i < secondSize ? second.get(i) : Collections.emptyList(); + result.add(Utils.concat(firstList, secondList)); + } + return result; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaSignatureAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaSignatureAttr.java new file mode 100644 index 00000000..a564df14 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaSignatureAttr.java @@ -0,0 +1,16 @@ +package jadx.plugins.input.java.data.attributes.types; + +import jadx.api.plugins.input.data.attributes.types.SignatureAttr; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class JavaSignatureAttr extends SignatureAttr implements IJavaAttribute { + + public JavaSignatureAttr(String signature) { + super(signature); + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> new JavaSignatureAttr(clsData.getConstPoolReader().getUtf8(reader.readU2())); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaSourceFileAttr.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaSourceFileAttr.java new file mode 100644 index 00000000..f5ca0df8 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/JavaSourceFileAttr.java @@ -0,0 +1,16 @@ +package jadx.plugins.input.java.data.attributes.types; + +import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; +import jadx.plugins.input.java.data.attributes.IJavaAttribute; +import jadx.plugins.input.java.data.attributes.IJavaAttributeReader; + +public class JavaSourceFileAttr extends SourceFileAttr implements IJavaAttribute { + + public JavaSourceFileAttr(String fileName) { + super(fileName); + } + + public static IJavaAttributeReader reader() { + return (clsData, reader) -> new JavaSourceFileAttr(clsData.getConstPoolReader().getUtf8(reader.readU2())); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/data/RawBootstrapMethod.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/data/RawBootstrapMethod.java new file mode 100644 index 00000000..9e43d900 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/attributes/types/data/RawBootstrapMethod.java @@ -0,0 +1,19 @@ +package jadx.plugins.input.java.data.attributes.types.data; + +public class RawBootstrapMethod { + private final int methodHandleIdx; + private final int[] args; + + public RawBootstrapMethod(int methodHandleIdx, int[] args) { + this.methodHandleIdx = methodHandleIdx; + this.args = args; + } + + public int getMethodHandleIdx() { + return methodHandleIdx; + } + + public int[] getArgs() { + return args; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/ArrayType.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/ArrayType.java new file mode 100644 index 00000000..f4aad55b --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/ArrayType.java @@ -0,0 +1,30 @@ +package jadx.plugins.input.java.data.code; + +import jadx.plugins.input.java.utils.JavaClassParseException; + +public class ArrayType { + + public static String byValue(int val) { + switch (val) { + case 4: + return "Z"; + case 5: + return "C"; + case 6: + return "F"; + case 7: + return "D"; + case 8: + return "B"; + case 9: + return "S"; + case 10: + return "I"; + case 11: + return "J"; + + default: + throw new JavaClassParseException("Unknown array type value: " + val); + } + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/CodeDecodeState.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/CodeDecodeState.java new file mode 100644 index 00000000..1802f9af --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/CodeDecodeState.java @@ -0,0 +1,185 @@ +package jadx.plugins.input.java.data.code; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import jadx.api.plugins.input.insns.Opcode; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.JavaClassData; +import jadx.plugins.input.java.data.code.StackState.SVType; + +public class CodeDecodeState { + private final JavaClassData clsData; + private final DataReader reader; + private final int maxStack; + private final Set excHandlers; + + private final Map jumpStack = new HashMap<>(); // save current stack for jump target + + private JavaInsnData insn; + private StackState stack; + private boolean excHandler; + + public CodeDecodeState(JavaClassData clsData, DataReader reader, int maxStack, Set excHandlers) { + this.clsData = clsData; + this.reader = reader; + this.maxStack = maxStack; + this.excHandlers = excHandlers; + this.stack = new StackState(maxStack); + } + + public void onInsn(int offset) { + StackState stackState = jumpStack.get(offset); + if (stackState != null) { + this.stack = stackState; + } + if (excHandlers.contains(offset)) { + stack.push(SVType.NARROW); // push exception + excHandler = true; + } else { + excHandler = false; + } + } + + public void registerJump(int jumpOffset) { + Integer key = jumpOffset; + if (!jumpStack.containsKey(key)) { + jumpStack.put(key, stack.copy()); + } + } + + public void decoded() { + if (excHandler && insn.getOpcode() == Opcode.MOVE) { + // replace first 'move' in exception handler with 'move-exception' + insn.setOpcode(Opcode.MOVE_EXCEPTION); + insn.setRegsCount(1); + } + } + + public JavaInsnData insn() { + return insn; + } + + public void setInsn(JavaInsnData insn) { + this.insn = insn; + } + + public DataReader reader() { + return reader; + } + + public JavaClassData clsData() { + return clsData; + } + + public CodeDecodeState local(int arg, int local) { + insn.setArgReg(arg, localToReg(local)); + return this; + } + + public CodeDecodeState pop(int arg) { + insn.setArgReg(arg, stack.pop()); + return this; + } + + public CodeDecodeState peek(int arg) { + insn.setArgReg(arg, stack.peek()); + return this; + } + + public SVType peekType(int at) { + return stack.peekTypeAt(at); + } + + public CodeDecodeState peekFrom(int pos, int arg) { + insn.setArgReg(arg, stack.peekAt(pos)); + return this; + } + + public CodeDecodeState push(int arg) { + insn.setArgReg(arg, stack.push(SVType.NARROW)); + return this; + } + + public CodeDecodeState push(int arg, SVType type) { + insn.setArgReg(arg, stack.push(type)); + return this; + } + + public CodeDecodeState pushWide(int arg) { + insn.setArgReg(arg, stack.push(SVType.WIDE)); + return this; + } + + public void discard() { + stack.pop(); + } + + public void discardWord() { + SVType type = stack.peekTypeAt(0); + stack.pop(); + if (type == SVType.NARROW) { + stack.pop(); + } + } + + public void clear() { + stack.clear(); + } + + public int push(String type) { + return stack.push(getSVType(type)); + } + + /** + * Must be after all pop and push + */ + public void jump(int offset) { + int jumpOffset = insn.getOffset() + offset; + insn.setTarget(jumpOffset); + registerJump(jumpOffset); + } + + public CodeDecodeState idx(int idx) { + insn.setIndex(idx); + return this; + } + + public CodeDecodeState lit(long lit) { + insn.setLiteral(lit); + return this; + } + + private int localToReg(int local) { + return maxStack + local; + } + + public SVType fieldType() { + String type = insn.constPoolReader().getFieldType(insn().getIndex()); + return getSVType(type); + } + + public SVType getSVType(String type) { + if (type.equals("J") || type.equals("D")) { + return SVType.WIDE; + } + return SVType.NARROW; + } + + public int u1() { + return reader.readU1(); + } + + public int u2() { + return reader.readU2(); + } + + public int s1() { + return reader.readS1(); + } + + public int s2() { + return reader.readS2(); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java new file mode 100644 index 00000000..50aa5d2c --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java @@ -0,0 +1,232 @@ +package jadx.plugins.input.java.data.code; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.IDebugInfo; +import jadx.api.plugins.input.data.ILocalVar; +import jadx.api.plugins.input.data.ITry; +import jadx.api.plugins.input.data.impl.CatchData; +import jadx.api.plugins.input.data.impl.DebugInfo; +import jadx.api.plugins.input.insns.InsnData; +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.JavaClassData; +import jadx.plugins.input.java.data.attributes.JavaAttrStorage; +import jadx.plugins.input.java.data.attributes.JavaAttrType; +import jadx.plugins.input.java.data.attributes.debuginfo.JavaLocalVar; +import jadx.plugins.input.java.data.attributes.debuginfo.LineNumberTableAttr; +import jadx.plugins.input.java.data.attributes.debuginfo.LocalVarTypesAttr; +import jadx.plugins.input.java.data.attributes.debuginfo.LocalVarsAttr; +import jadx.plugins.input.java.data.code.trycatch.JavaSingleCatch; +import jadx.plugins.input.java.data.code.trycatch.JavaTryData; +import jadx.plugins.input.java.utils.JavaClassParseException; + +public class JavaCodeReader implements ICodeReader { + + private final JavaClassData clsData; + private final DataReader reader; + private final int codeOffset; + + public JavaCodeReader(JavaClassData clsData, int offset) { + this.clsData = clsData; + this.reader = clsData.getData(); + this.codeOffset = offset; + } + + @Override + public ICodeReader copy() { + return this; + } + + @Override + public void visitInstructions(Consumer insnConsumer) { + Set excHandlers = getExcHandlers(); + reader.absPos(codeOffset); + int maxStack = reader.readU2(); + reader.skip(2); + int codeSize = reader.readU4(); + + CodeDecodeState state = new CodeDecodeState(clsData, reader, maxStack, excHandlers); + JavaInsnData insn = new JavaInsnData(state); + state.setInsn(insn); + int offset = 0; + while (offset < codeSize) { + int opcode = reader.readU1(); + JavaInsnInfo insnInfo = JavaInsnsRegister.get(opcode); + if (insnInfo == null) { + throw new JavaClassParseException("Unknown opcode: 0x" + Integer.toHexString(opcode)); + } + insn.setDecoded(false); + insn.setInsnInfo(insnInfo); + insn.setInsnStart(reader.getOffset()); + insn.setOffset(offset); + insn.setInsnInfo(insnInfo); + insn.setRegsCount(insnInfo.getRegsCount()); + insn.setOpcode(insnInfo.getApiOpcode()); + insn.setPayloadSize(insnInfo.getPayloadSize()); + insn.setOpcodeUnit(opcode); + insn.setPayload(null); + + state.onInsn(offset); + insnConsumer.accept(insn); + + int payloadSize = insn.getPayloadSize(); + if (!insn.isDecoded()) { + if (payloadSize == -1) { + insn.skip(); + payloadSize = insn.getPayloadSize(); + } else { + reader.skip(payloadSize); + } + } + offset += 1 + payloadSize; + } + } + + @Override + public int getRegistersCount() { + reader.absPos(codeOffset); + int maxStack = reader.readU2(); + int maxLocals = reader.readU2(); + return maxStack + maxLocals; + } + + @Override + public int getArgsStartReg() { + reader.absPos(codeOffset); + return reader.readU2(); // maxStack + } + + @Override + public int getUnitsCount() { + return reader.absPos(codeOffset + 4).readU4(); + } + + @Override + public @Nullable IDebugInfo getDebugInfo() { + reader.absPos(codeOffset); + int maxStack = reader.readU2(); + reader.skip(2); + reader.skip(reader.readU4()); + reader.skip(reader.readU2() * 8); + + JavaAttrStorage attrs = clsData.getAttributesReader().load(reader); + LineNumberTableAttr linesAttr = attrs.get(JavaAttrType.LINE_NUMBER_TABLE); + LocalVarsAttr varsAttr = attrs.get(JavaAttrType.LOCAL_VAR_TABLE); + if (linesAttr == null && varsAttr == null) { + return null; + } + Map linesMap = linesAttr != null ? linesAttr.getLineMap() : Collections.emptyMap(); + + List vars; + if (varsAttr == null) { + vars = Collections.emptyList(); + } else { + List javaVars = varsAttr.getVars(); + LocalVarTypesAttr typedVars = attrs.get(JavaAttrType.LOCAL_VAR_TYPE_TABLE); + if (typedVars != null && !typedVars.getVars().isEmpty()) { + // merge signature from typedVars into javaVars + Map varsMap = new HashMap<>(javaVars.size()); + javaVars.forEach(v -> varsMap.put(v, v)); + for (JavaLocalVar typedVar : typedVars.getVars()) { + JavaLocalVar jv = varsMap.get(typedVar); + if (jv != null) { + jv.setSignature(typedVar.getSignature()); + } + } + } + javaVars.forEach(v -> v.shiftRegNum(maxStack)); + vars = Collections.unmodifiableList(javaVars); + } + return new DebugInfo(linesMap, vars); + } + + @Override + public int getCodeOffset() { + return codeOffset; + } + + @Override + public List getTries() { + skipToTries(); + int excTableLen = reader.readU2(); + if (excTableLen == 0) { + return Collections.emptyList(); + } + ConstPoolReader constPool = clsData.getConstPoolReader(); + Map> tries = new HashMap<>(excTableLen); + for (int i = 0; i < excTableLen; i++) { + int start = reader.readU2(); + int end = reader.readU2(); + int handler = reader.readU2(); + int type = reader.readU2(); + JavaTryData tryData = new JavaTryData(start, end); + List catches = tries.computeIfAbsent(tryData, k -> new ArrayList<>()); + if (type == 0) { + catches.add(new JavaSingleCatch(handler, null)); + } else { + catches.add(new JavaSingleCatch(handler, constPool.getClass(type))); + } + } + return tries.entrySet().stream() + .map(e -> { + JavaTryData tryData = e.getKey(); + tryData.setCatch(convertSingleCatches(e.getValue())); + return tryData; + }) + .collect(Collectors.toList()); + } + + private static CatchData convertSingleCatches(List list) { + int allAddr = -1; + for (JavaSingleCatch singleCatch : list) { + if (singleCatch.getType() == null) { + allAddr = singleCatch.getHandler(); + list.remove(singleCatch); + break; + } + } + int len = list.size(); + int[] addrs = new int[len]; + String[] types = new String[len]; + for (int i = 0; i < len; i++) { + JavaSingleCatch singleCatch = list.get(i); + addrs[i] = singleCatch.getHandler(); + types[i] = singleCatch.getType(); + } + return new CatchData(addrs, types, allAddr); + } + + private Set getExcHandlers() { + skipToTries(); + int excTableLen = reader.readU2(); + if (excTableLen == 0) { + return Collections.emptySet(); + } + Set set = new HashSet<>(excTableLen); + for (int i = 0; i < excTableLen; i++) { + reader.skip(4); + int handler = reader.readU2(); + reader.skip(2); + set.add(handler); + } + return set; + } + + private void skipToTries() { + reader.absPos(codeOffset + 4); + int codeSize = reader.readU4(); + reader.skip(codeSize); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnData.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnData.java new file mode 100644 index 00000000..6478190c --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnData.java @@ -0,0 +1,257 @@ +package jadx.plugins.input.java.data.code; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ICallSite; +import jadx.api.plugins.input.data.IFieldRef; +import jadx.api.plugins.input.data.IMethodHandle; +import jadx.api.plugins.input.data.IMethodProto; +import jadx.api.plugins.input.data.IMethodRef; +import jadx.api.plugins.input.insns.InsnData; +import jadx.api.plugins.input.insns.InsnIndexType; +import jadx.api.plugins.input.insns.Opcode; +import jadx.api.plugins.input.insns.custom.ICustomPayload; +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.code.decoders.IJavaInsnDecoder; + +public class JavaInsnData implements InsnData { + + private final CodeDecodeState state; + + private JavaInsnInfo insnInfo; + private Opcode opcode; + private boolean decoded; + private int opcodeUnit; + private int payloadSize; + private int insnStart; + private int offset; + private int regsCount; + private int[] argsReg = new int[16]; + private int resultReg; + private long literal; + private int target; + private int index; + @Nullable + private ICustomPayload payload; + + public JavaInsnData(CodeDecodeState state) { + this.state = state; + } + + @Override + public void decode() { + IJavaInsnDecoder decoder = insnInfo.getDecoder(); + if (decoder != null) { + decoder.decode(state); + state.decoded(); + } + decoded = true; + } + + public void skip() { + IJavaInsnDecoder decoder = insnInfo.getDecoder(); + if (decoder != null) { + decoder.skip(state); + } + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public int getFileOffset() { + return insnStart; + } + + @Override + public Opcode getOpcode() { + return opcode; + } + + public void setOpcode(Opcode opcode) { + this.opcode = opcode; + } + + @Override + public byte[] getByteCode() { + return new byte[0]; + } + + @Override + public InsnIndexType getIndexType() { + return insnInfo.getIndexType(); + } + + @Override + public int getRawOpcodeUnit() { + return opcodeUnit; + } + + @Override + public int getRegsCount() { + return regsCount; + } + + @Override + public int getReg(int argNum) { + return argsReg[argNum]; + } + + @Override + public int getResultReg() { + return resultReg; + } + + public void setResultReg(int resultReg) { + this.resultReg = resultReg; + } + + @Override + public long getLiteral() { + return literal; + } + + @Override + public int getTarget() { + return target; + } + + @Override + public int getIndex() { + return index; + } + + public int getPayloadSize() { + return payloadSize; + } + + @Override + public String getIndexAsString() { + return constPoolReader().getUtf8(index); + } + + @Override + public String getIndexAsType() { + if (insnInfo.getOpcode() == 0xbc) { // newarray + return ArrayType.byValue(index); + } + return constPoolReader().getClass(index); + } + + @Override + public IFieldRef getIndexAsField() { + return constPoolReader().getFieldRef(index); + } + + @Override + public IMethodRef getIndexAsMethod() { + return constPoolReader().getMethodRef(index); + } + + @Override + public ICallSite getIndexAsCallSite() { + return constPoolReader().getCallSite(index); + } + + @Override + public IMethodProto getIndexAsProto(int protoIndex) { + return null; + } + + @Override + public IMethodHandle getIndexAsMethodHandle() { + return null; + } + + @Override + public @Nullable ICustomPayload getPayload() { + return payload; + } + + public void setInsnInfo(JavaInsnInfo insnInfo) { + this.insnInfo = insnInfo; + } + + public boolean isDecoded() { + return decoded; + } + + public void setDecoded(boolean decoded) { + this.decoded = decoded; + } + + public void setOpcodeUnit(int opcodeUnit) { + this.opcodeUnit = opcodeUnit; + } + + public void setPayloadSize(int payloadSize) { + this.payloadSize = payloadSize; + } + + public void setInsnStart(int insnStart) { + this.insnStart = insnStart; + } + + public void setOffset(int offset) { + this.offset = offset; + } + + public void setArgReg(int arg, int reg) { + this.argsReg[arg] = reg; + } + + public void setRegsCount(int regsCount) { + this.regsCount = regsCount; + if (argsReg.length < regsCount) { + argsReg = new int[regsCount]; + } + } + + public int[] getRegsArray() { + return argsReg; + } + + public void setLiteral(long literal) { + this.literal = literal; + } + + public void setTarget(int target) { + this.target = target; + } + + public void setIndex(int index) { + this.index = index; + } + + public void setPayload(ICustomPayload payload) { + this.payload = payload; + } + + public ConstPoolReader constPoolReader() { + return state.clsData().getConstPoolReader(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("0x%04X", offset)); + sb.append(": ").append(getOpcode()); + if (insnInfo == null) { + sb.append(String.format("(0x%04X)", opcodeUnit)); + } else { + int regsCount = getRegsCount(); + if (isDecoded()) { + sb.append(' '); + for (int i = 0; i < regsCount; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append("r").append(argsReg[i]); + } + } + } + return sb.toString(); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnInfo.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnInfo.java new file mode 100644 index 00000000..8181b6c8 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnInfo.java @@ -0,0 +1,59 @@ +package jadx.plugins.input.java.data.code; + +import jadx.api.plugins.input.insns.InsnIndexType; +import jadx.api.plugins.input.insns.Opcode; +import jadx.plugins.input.java.data.code.decoders.IJavaInsnDecoder; + +public class JavaInsnInfo { + private final int opcode; + private final String name; + private final int payloadSize; + private final int regsCount; + private final Opcode apiOpcode; + private final InsnIndexType indexType; + private final IJavaInsnDecoder decoder; + + public JavaInsnInfo(int opcode, String name, int payloadSize, int regsCount, Opcode apiOpcode, + InsnIndexType indexType, IJavaInsnDecoder decoder) { + this.opcode = opcode; + this.name = name; + this.payloadSize = payloadSize; + this.regsCount = regsCount; + this.apiOpcode = apiOpcode; + this.indexType = indexType; + this.decoder = decoder; + } + + public int getOpcode() { + return opcode; + } + + public String getName() { + return name; + } + + public int getPayloadSize() { + return payloadSize; + } + + public int getRegsCount() { + return regsCount; + } + + public Opcode getApiOpcode() { + return apiOpcode; + } + + public InsnIndexType getIndexType() { + return indexType; + } + + public IJavaInsnDecoder getDecoder() { + return decoder; + } + + @Override + public String toString() { + return "0x" + Integer.toHexString(opcode) + ": " + name; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnsRegister.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnsRegister.java new file mode 100644 index 00000000..b6712c84 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaInsnsRegister.java @@ -0,0 +1,382 @@ +package jadx.plugins.input.java.data.code; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.insns.InsnIndexType; +import jadx.api.plugins.input.insns.Opcode; +import jadx.plugins.input.java.data.code.StackState.SVType; +import jadx.plugins.input.java.data.code.decoders.IJavaInsnDecoder; +import jadx.plugins.input.java.data.code.decoders.InvokeDecoder; +import jadx.plugins.input.java.data.code.decoders.LoadConstDecoder; +import jadx.plugins.input.java.data.code.decoders.LookupSwitchDecoder; +import jadx.plugins.input.java.data.code.decoders.TableSwitchDecoder; + +import static jadx.plugins.input.java.data.code.StackState.SVType.NARROW; +import static jadx.plugins.input.java.data.code.StackState.SVType.WIDE; + +@SuppressWarnings("SpellCheckingInspection") +public class JavaInsnsRegister { + + private static final JavaInsnInfo[] INSN_INFO; + + public static final long FLOAT_ZERO = Float.floatToIntBits(0.0f); + public static final long FLOAT_ONE = Float.floatToIntBits(1.0f); + public static final long FLOAT_TWO = Float.floatToIntBits(2.0f); + + public static final long DOUBLE_ZERO = Double.doubleToLongBits(0.0d); + public static final long DOUBLE_ONE = Double.doubleToLongBits(1.0d); + + static { + JavaInsnInfo[] arr = new JavaInsnInfo[0xCA]; + INSN_INFO = arr; + register(arr, 0x00, "nop", 0, 0, Opcode.NOP, null); + + constInsn(arr, 0x01, "aconst_null", Opcode.CONST, 0); + constInsn(arr, 0x02, "iconst_m1", Opcode.CONST, -1); + constInsn(arr, 0x03, "iconst_0", Opcode.CONST, 0); + constInsn(arr, 0x04, "iconst_1", Opcode.CONST, 1); + constInsn(arr, 0x05, "iconst_2", Opcode.CONST, 2); + constInsn(arr, 0x06, "iconst_3", Opcode.CONST, 3); + constInsn(arr, 0x07, "iconst_4", Opcode.CONST, 4); + constInsn(arr, 0x08, "iconst_5", Opcode.CONST, 5); + + constInsn(arr, 0x09, "lconst_0", Opcode.CONST_WIDE, 0L); + constInsn(arr, 0x0a, "lconst_1", Opcode.CONST_WIDE, 1L); + + constInsn(arr, 0x0b, "fconst_0", Opcode.CONST, FLOAT_ZERO); + constInsn(arr, 0x0c, "fconst_1", Opcode.CONST, FLOAT_ONE); + constInsn(arr, 0x0d, "fconst_2", Opcode.CONST, FLOAT_TWO); + + constInsn(arr, 0x0e, "dconst_0", Opcode.CONST_WIDE, DOUBLE_ZERO); + constInsn(arr, 0x0f, "dconst_1", Opcode.CONST_WIDE, DOUBLE_ONE); + + register(arr, 0x10, "bipush", 1, 2, Opcode.CONST, s -> s.lit(s.s1()).push(0)); + register(arr, 0x11, "sipush", 2, 2, Opcode.CONST, s -> s.lit(s.s2()).push(0)); + + loadConst(arr, 0x12, "ldc", false); + loadConst(arr, 0x13, "ldc_w", true); + loadConst(arr, 0x14, "ldc2_w", true); + + register(arr, 0x15, "iload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).push(0)); + register(arr, 0x16, "lload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).pushWide(0)); + register(arr, 0x17, "fload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).push(0)); + register(arr, 0x18, "dload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).pushWide(0)); + register(arr, 0x19, "aload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).push(0)); + + register(arr, 0x1a, "iload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).push(0)); + register(arr, 0x1b, "iload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).push(0)); + register(arr, 0x1c, "iload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).push(0)); + register(arr, 0x1d, "iload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).push(0)); + + register(arr, 0x1e, "lload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).pushWide(0)); + register(arr, 0x1f, "lload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).pushWide(0)); + register(arr, 0x20, "lload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).pushWide(0)); + register(arr, 0x21, "lload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).pushWide(0)); + + register(arr, 0x22, "fload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).push(0)); + register(arr, 0x23, "fload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).push(0)); + register(arr, 0x24, "fload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).push(0)); + register(arr, 0x25, "fload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).push(0)); + + register(arr, 0x26, "dload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).pushWide(0)); + register(arr, 0x27, "dload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).pushWide(0)); + register(arr, 0x28, "dload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).pushWide(0)); + register(arr, 0x29, "dload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).pushWide(0)); + + register(arr, 0x2a, "aload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).push(0)); + register(arr, 0x2b, "aload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).push(0)); + register(arr, 0x2c, "aload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).push(0)); + register(arr, 0x2d, "aload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).push(0)); + + register(arr, 0x2e, "iaload", 0, 3, Opcode.AGET, aget()); + register(arr, 0x2f, "laload", 0, 3, Opcode.AGET_WIDE, agetWide()); + register(arr, 0x30, "faload", 0, 3, Opcode.AGET, aget()); + register(arr, 0x31, "daload", 0, 3, Opcode.AGET_WIDE, agetWide()); + register(arr, 0x32, "aaload", 0, 3, Opcode.AGET_OBJECT, aget()); + register(arr, 0x33, "baload", 0, 3, Opcode.AGET_BYTE_BOOLEAN, aget()); + register(arr, 0x34, "caload", 0, 3, Opcode.AGET_CHAR, aget()); + register(arr, 0x35, "saload", 0, 3, Opcode.AGET_SHORT, aget()); + + register(arr, 0x36, "istore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1())); + register(arr, 0x37, "lstore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1())); + register(arr, 0x38, "fstore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1())); + register(arr, 0x39, "dstore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1())); + register(arr, 0x3a, "astore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1())); + + register(arr, 0x3b, "istore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0)); + register(arr, 0x3c, "istore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1)); + register(arr, 0x3d, "istore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2)); + register(arr, 0x3e, "istore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3)); + + register(arr, 0x3f, "lstore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0)); + register(arr, 0x40, "lstore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1)); + register(arr, 0x41, "lstore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2)); + register(arr, 0x42, "lstore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3)); + + register(arr, 0x43, "fstore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0)); + register(arr, 0x44, "fstore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1)); + register(arr, 0x45, "fstore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2)); + register(arr, 0x46, "fstore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3)); + + register(arr, 0x47, "dstore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0)); + register(arr, 0x48, "dstore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1)); + register(arr, 0x49, "dstore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2)); + register(arr, 0x4a, "dstore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3)); + + register(arr, 0x4b, "astore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0)); + register(arr, 0x4c, "astore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1)); + register(arr, 0x4d, "astore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2)); + register(arr, 0x4e, "astore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3)); + + register(arr, 0x4f, "iastore", 0, 3, Opcode.APUT, aput()); + register(arr, 0x50, "lastore", 0, 3, Opcode.APUT_WIDE, aput()); + register(arr, 0x51, "fastore", 0, 3, Opcode.APUT, aput()); + register(arr, 0x52, "dastore", 0, 3, Opcode.APUT_WIDE, aput()); + register(arr, 0x53, "aastore", 0, 3, Opcode.APUT_OBJECT, aput()); + register(arr, 0x54, "bastore", 0, 3, Opcode.APUT_BYTE_BOOLEAN, aput()); + register(arr, 0x55, "castore", 0, 3, Opcode.APUT_CHAR, aput()); + register(arr, 0x56, "sastore", 0, 3, Opcode.APUT_SHORT, aput()); + + register(arr, 0x57, "pop", 0, 0, Opcode.NOP, CodeDecodeState::discard); + register(arr, 0x58, "pop2", 0, 0, Opcode.NOP, CodeDecodeState::discardWord); + + register(arr, 0x59, "dup", 0, 2, Opcode.MOVE, s -> s.peek(1).push(0, s.peekType(1))); + register(arr, 0x5a, "dup_x1", 0, 6, Opcode.MOVE_MULTI, + s -> s.push(0, s.peekType(1)).peekFrom(1, 1) + .peekFrom(1, 2).peekFrom(2, 3) + .peekFrom(2, 4).peekFrom(0, 5)); + register(arr, 0x5b, "dup_x2", 0, 8, Opcode.MOVE_MULTI, + s -> s.push(0, s.peekType(1)).peekFrom(1, 1) + .peekFrom(1, 2).peekFrom(2, 3) + .peekFrom(2, 4).peekFrom(3, 5) + .peekFrom(3, 6).peekFrom(0, 7)); + register(arr, 0x5c, "dup2", 0, 4, Opcode.MOVE_MULTI, s -> { + if (s.peekType(0) == NARROW) { + s.peekFrom(0, 3).peekFrom(1, 1).push(0, NARROW).push(2, NARROW); + } else { + s.peek(1).push(0, s.peekType(1)); + } + }); + register(arr, 0x5d, "dup2_x1", 0, 10, Opcode.MOVE_MULTI, + s -> { + if (s.peekType(0) == NARROW) { + s.push(0, NARROW).peekFrom(2, 1) + .push(2, NARROW).peekFrom(2, 3) + .peekFrom(2, 4).peekFrom(4, 5) + .peekFrom(3, 6).peekFrom(0, 7) + .peekFrom(4, 8).peekFrom(1, 9); + } else { + s.insn().setRegsCount(6); + s.push(0, WIDE).peekFrom(1, 1) + .peekFrom(1, 2).peekFrom(2, 3) + .peekFrom(2, 4).peekFrom(0, 5); + } + }); + + register(arr, 0x60, "iadd", 0, 3, Opcode.ADD_INT, twoRegsWithResult(NARROW)); + register(arr, 0x61, "ladd", 0, 3, Opcode.ADD_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x62, "fadd", 0, 3, Opcode.ADD_FLOAT, twoRegsWithResult(NARROW)); + register(arr, 0x63, "dadd", 0, 3, Opcode.ADD_DOUBLE, twoRegsWithResult(WIDE)); + + register(arr, 0x64, "isub", 0, 3, Opcode.SUB_INT, twoRegsWithResult(NARROW)); + register(arr, 0x65, "lsub", 0, 3, Opcode.SUB_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x66, "fsub", 0, 3, Opcode.SUB_FLOAT, twoRegsWithResult(NARROW)); + register(arr, 0x67, "dsub", 0, 3, Opcode.SUB_DOUBLE, twoRegsWithResult(WIDE)); + + register(arr, 0x68, "imul", 0, 3, Opcode.MUL_INT, twoRegsWithResult(NARROW)); + register(arr, 0x69, "lmul", 0, 3, Opcode.MUL_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x6a, "fmul", 0, 3, Opcode.MUL_FLOAT, twoRegsWithResult(NARROW)); + register(arr, 0x6b, "dmul", 0, 3, Opcode.MUL_DOUBLE, twoRegsWithResult(WIDE)); + + register(arr, 0x6c, "idiv", 0, 3, Opcode.DIV_INT, twoRegsWithResult(NARROW)); + register(arr, 0x6d, "ldiv", 0, 3, Opcode.DIV_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x6e, "fdiv", 0, 3, Opcode.DIV_FLOAT, twoRegsWithResult(NARROW)); + register(arr, 0x6f, "ddiv", 0, 3, Opcode.DIV_DOUBLE, twoRegsWithResult(WIDE)); + + register(arr, 0x70, "irem", 0, 3, Opcode.REM_INT, twoRegsWithResult(NARROW)); + register(arr, 0x71, "lrem", 0, 3, Opcode.REM_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x72, "frem", 0, 3, Opcode.REM_FLOAT, twoRegsWithResult(NARROW)); + register(arr, 0x73, "drem", 0, 3, Opcode.REM_DOUBLE, twoRegsWithResult(WIDE)); + + register(arr, 0x74, "ineg", 0, 2, Opcode.NEG_INT, oneRegWithResult(NARROW)); + register(arr, 0x75, "lneg", 0, 2, Opcode.NEG_LONG, oneRegWithResult(WIDE)); + register(arr, 0x76, "fneg", 0, 2, Opcode.NEG_FLOAT, oneRegWithResult(NARROW)); + register(arr, 0x77, "dneg", 0, 2, Opcode.NEG_DOUBLE, oneRegWithResult(WIDE)); + + register(arr, 0x78, "ishl", 0, 3, Opcode.SHL_INT, twoRegsWithResult(NARROW)); + register(arr, 0x79, "lshl", 0, 3, Opcode.SHL_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x7a, "ishr", 0, 3, Opcode.SHR_INT, twoRegsWithResult(NARROW)); + register(arr, 0x7b, "lshr", 0, 3, Opcode.SHR_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x7c, "iushr", 0, 3, Opcode.USHR_INT, twoRegsWithResult(NARROW)); + register(arr, 0x7d, "lushr", 0, 3, Opcode.USHR_LONG, twoRegsWithResult(WIDE)); + + register(arr, 0x7e, "iand", 0, 3, Opcode.AND_INT, twoRegsWithResult(NARROW)); + register(arr, 0x7f, "land", 0, 3, Opcode.AND_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x80, "ior", 0, 3, Opcode.OR_INT, twoRegsWithResult(NARROW)); + register(arr, 0x81, "lor", 0, 3, Opcode.OR_LONG, twoRegsWithResult(WIDE)); + register(arr, 0x82, "ixor", 0, 3, Opcode.XOR_INT, twoRegsWithResult(NARROW)); + register(arr, 0x83, "lxor", 0, 3, Opcode.XOR_LONG, twoRegsWithResult(WIDE)); + + register(arr, 0x84, "iinc", 2, 2, Opcode.ADD_INT_LIT, s -> { + int varNum = s.u1(); + s.local(0, varNum).local(1, varNum).lit(s.reader().readS1()); + }); + + register(arr, 0x85, "i2l", 0, 2, Opcode.INT_TO_LONG, oneRegWithResult(WIDE)); + register(arr, 0x86, "i2f", 0, 2, Opcode.INT_TO_FLOAT, oneRegWithResult(NARROW)); + register(arr, 0x87, "i2d", 0, 2, Opcode.INT_TO_DOUBLE, oneRegWithResult(WIDE)); + register(arr, 0x88, "l2i", 0, 2, Opcode.LONG_TO_INT, oneRegWithResult(NARROW)); + register(arr, 0x89, "l2f", 0, 2, Opcode.LONG_TO_FLOAT, oneRegWithResult(NARROW)); + register(arr, 0x8a, "l2d", 0, 2, Opcode.LONG_TO_DOUBLE, oneRegWithResult(WIDE)); + register(arr, 0x8b, "f2i", 0, 2, Opcode.FLOAT_TO_INT, oneRegWithResult(NARROW)); + register(arr, 0x8c, "f2l", 0, 2, Opcode.FLOAT_TO_LONG, oneRegWithResult(WIDE)); + register(arr, 0x8d, "f2d", 0, 2, Opcode.FLOAT_TO_DOUBLE, oneRegWithResult(WIDE)); + register(arr, 0x8e, "d2i", 0, 2, Opcode.DOUBLE_TO_INT, oneRegWithResult(NARROW)); + register(arr, 0x8f, "d2l", 0, 2, Opcode.DOUBLE_TO_LONG, oneRegWithResult(WIDE)); + register(arr, 0x90, "d2f", 0, 2, Opcode.DOUBLE_TO_FLOAT, oneRegWithResult(NARROW)); + register(arr, 0x91, "i2b", 0, 2, Opcode.INT_TO_BYTE, oneRegWithResult(NARROW)); + register(arr, 0x92, "i2c", 0, 2, Opcode.INT_TO_CHAR, oneRegWithResult(NARROW)); + register(arr, 0x93, "i2s", 0, 2, Opcode.INT_TO_SHORT, oneRegWithResult(NARROW)); + + register(arr, 0x94, "lcmp", 0, 3, Opcode.CMP_LONG, twoRegsWithResult(NARROW)); + register(arr, 0x95, "fcmpl", 0, 3, Opcode.CMPL_FLOAT, twoRegsWithResult(NARROW)); + register(arr, 0x96, "fcmpg", 0, 3, Opcode.CMPG_FLOAT, twoRegsWithResult(NARROW)); + register(arr, 0x97, "dcmpl", 0, 3, Opcode.CMPL_DOUBLE, twoRegsWithResult(NARROW)); + register(arr, 0x98, "dcmpg", 0, 3, Opcode.CMPG_DOUBLE, twoRegsWithResult(NARROW)); + + register(arr, 0x99, "ifeq", 2, 1, Opcode.IF_EQZ, zeroCmp()); + register(arr, 0x9a, "ifne", 2, 1, Opcode.IF_NEZ, zeroCmp()); + register(arr, 0x9b, "iflt", 2, 1, Opcode.IF_LTZ, zeroCmp()); + register(arr, 0x9c, "ifge", 2, 1, Opcode.IF_GEZ, zeroCmp()); + register(arr, 0x9d, "ifgt", 2, 1, Opcode.IF_GTZ, zeroCmp()); + register(arr, 0x9e, "ifle", 2, 1, Opcode.IF_LEZ, zeroCmp()); + + register(arr, 0x9f, "if_icmpeq", 2, 2, Opcode.IF_EQ, cmp()); + register(arr, 0xa0, "if_icmpne", 2, 2, Opcode.IF_NE, cmp()); + register(arr, 0xa1, "if_icmplt", 2, 2, Opcode.IF_LT, cmp()); + register(arr, 0xa2, "if_icmpge", 2, 2, Opcode.IF_GE, cmp()); + register(arr, 0xa3, "if_icmpgt", 2, 2, Opcode.IF_GT, cmp()); + register(arr, 0xa4, "if_icmple", 2, 2, Opcode.IF_LE, cmp()); + register(arr, 0xa5, "if_acmpeq", 2, 2, Opcode.IF_EQ, cmp()); + register(arr, 0xa6, "if_acmpne", 2, 2, Opcode.IF_NE, cmp()); + + register(arr, 0xa7, "goto", 2, 0, Opcode.GOTO, s -> s.jump(s.s2())); + + register(arr, 0xaa, "tableswitch", -1, 1, Opcode.PACKED_SWITCH, new TableSwitchDecoder()); + register(arr, 0xab, "lookupswitch", -1, 1, Opcode.SPARSE_SWITCH, new LookupSwitchDecoder()); + + register(arr, 0xac, "ireturn", 0, 1, Opcode.RETURN, s -> s.pop(0)); + register(arr, 0xad, "lreturn", 0, 1, Opcode.RETURN, s -> s.pop(0)); + register(arr, 0xae, "freturn", 0, 1, Opcode.RETURN, s -> s.pop(0)); + register(arr, 0xaf, "dreturn", 0, 1, Opcode.RETURN, s -> s.pop(0)); + register(arr, 0xb0, "areturn", 0, 1, Opcode.RETURN, s -> s.pop(0)); + register(arr, 0xb1, "return", 0, 0, Opcode.RETURN_VOID, null); + + register(arr, 0xb2, "getstatic", 2, 1, Opcode.SGET, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).push(0, s.fieldType())); + register(arr, 0xb3, "putstatic", 2, 1, Opcode.SPUT, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).pop(0)); + register(arr, 0xb4, "getfield", 2, 2, Opcode.IGET, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).pop(1).push(0, s.fieldType())); + register(arr, 0xb5, "putfield", 2, 2, Opcode.IPUT, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).pop(0).pop(1)); + + invoke(arr, 0xb6, "invokevirtual", 2, Opcode.INVOKE_VIRTUAL); + invoke(arr, 0xb7, "invokespecial", 2, Opcode.INVOKE_DIRECT); + invoke(arr, 0xb8, "invokestatic", 2, Opcode.INVOKE_STATIC); + invoke(arr, 0xb9, "invokeinterface", 4, Opcode.INVOKE_INTERFACE); + invoke(arr, 0xba, "invokedynamic", 4, Opcode.INVOKE_CUSTOM); + + register(arr, 0xbb, "new", 2, 1, Opcode.NEW_INSTANCE, InsnIndexType.TYPE_REF, s -> s.idx(s.u2()).push(0)); + register(arr, 0xbc, "newarray", 1, 2, Opcode.NEW_ARRAY, InsnIndexType.TYPE_REF, s -> s.idx(s.u1()).pop(1).push(0).lit(1)); + register(arr, 0xbd, "anewarray", 2, 2, Opcode.NEW_ARRAY, InsnIndexType.TYPE_REF, s -> s.idx(s.u2()).pop(1).push(0).lit(1)); + register(arr, 0xbe, "arraylength", 0, 2, Opcode.ARRAY_LENGTH, oneRegWithResult(NARROW)); + register(arr, 0xbf, "athrow", 0, 1, Opcode.THROW, s -> s.pop(0).clear()); + + register(arr, 0xc0, "checkcast", 2, 2, Opcode.CHECK_CAST, InsnIndexType.TYPE_REF, s -> s.idx(s.u2()).pop(1).push(0)); + register(arr, 0xc1, "instanceof", 2, 2, Opcode.INSTANCE_OF, InsnIndexType.TYPE_REF, s -> s.idx(s.u2()).pop(1).push(0)); + + register(arr, 0xc2, "monitorenter", 0, 1, Opcode.MONITOR_ENTER, s -> s.pop(0)); + register(arr, 0xc3, "monitorexit", 0, 1, Opcode.MONITOR_EXIT, s -> s.pop(0)); + + // register(arr, 0xc4, "wide", 0, 1, Opcode.NOP, s -> s.pop(0)); + + register(arr, 0xc5, "multianewarray", 3, -1, Opcode.NEW_ARRAY, InsnIndexType.TYPE_REF, newArrayMulti()); + register(arr, 0xc6, "ifnull", 2, 1, Opcode.IF_EQZ, zeroCmp()); + register(arr, 0xc7, "ifnonnull", 2, 1, Opcode.IF_NEZ, zeroCmp()); + + register(arr, 0xc8, "goto_w", 4, 0, Opcode.GOTO, s -> s.jump(s.reader().readS4())); + } + + private static IJavaInsnDecoder newArrayMulti() { + return s -> { + s.idx(s.u2()); + int dim = s.u1(); + JavaInsnData insn = s.insn(); + insn.setLiteral(dim); + insn.setRegsCount(dim + 1); + for (int i = dim; i > 0; i--) { + s.pop(i); + } + s.push(0); + }; + } + + private static IJavaInsnDecoder oneRegWithResult(SVType type) { + return s -> s.pop(1).push(0, type); + } + + private static IJavaInsnDecoder twoRegsWithResult(SVType type) { + return s -> s.pop(2).pop(1).push(0, type); + } + + private static IJavaInsnDecoder aget() { + return s -> s.pop(2).pop(1).push(0); + } + + private static IJavaInsnDecoder agetWide() { + return s -> s.pop(2).pop(1).pushWide(0); + } + + private static IJavaInsnDecoder aput() { + return s -> s.pop(0).pop(2).pop(1); + } + + private static IJavaInsnDecoder zeroCmp() { + return s -> s.pop(0).jump(s.s2()); + } + + private static IJavaInsnDecoder cmp() { + return s -> s.pop(1).pop(0).jump(s.s2()); + } + + private static void invoke(JavaInsnInfo[] arr, int opcode, String name, int payloadSize, Opcode apiOpcode) { + InsnIndexType indexType = apiOpcode == Opcode.INVOKE_CUSTOM ? InsnIndexType.CALL_SITE : InsnIndexType.METHOD_REF; + register(arr, opcode, name, payloadSize, -1, apiOpcode, indexType, new InvokeDecoder(payloadSize, apiOpcode)); + } + + private static void constInsn(JavaInsnInfo[] arr, int opcode, String name, Opcode apiOpcode, long literal) { + register(arr, opcode, name, 0, 1, apiOpcode, InsnIndexType.NONE, state -> { + state.insn().setLiteral(literal); + state.push(0, apiOpcode == Opcode.CONST_WIDE ? SVType.WIDE : NARROW); + }); + } + + private static void loadConst(JavaInsnInfo[] arr, int opcode, String name, boolean wide) { + register(arr, opcode, name, wide ? 2 : 1, 2, Opcode.CONST, InsnIndexType.NONE, new LoadConstDecoder(wide)); + } + + private static void register(JavaInsnInfo[] arr, int opcode, String name, int payloadSize, int regsCount, + Opcode apiOpcode, IJavaInsnDecoder decoder) { + register(arr, opcode, name, payloadSize, regsCount, apiOpcode, InsnIndexType.NONE, decoder); + } + + private static void register(JavaInsnInfo[] arr, int opcode, String name, int payloadSize, int regsCount, + Opcode apiOpcode, InsnIndexType indexType, IJavaInsnDecoder decoder) { + if (arr[opcode] != null) { + throw new IllegalStateException("Duplicate opcode init: 0x" + Integer.toHexString(opcode)); + } + arr[opcode] = new JavaInsnInfo(opcode, name, payloadSize, regsCount, apiOpcode, indexType, decoder); + } + + @Nullable + public static JavaInsnInfo get(int opcode) { + return INSN_INFO[opcode]; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/StackState.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/StackState.java new file mode 100644 index 00000000..dc71f123 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/StackState.java @@ -0,0 +1,80 @@ +package jadx.plugins.input.java.data.code; + +import java.util.Arrays; + +public class StackState { + + /** + * Stack value type + */ + public enum SVType { + NARROW, // int, float, etc + WIDE, // long, double + } + + private int pos = -1; + private final SVType[] stack; + + public StackState(int maxStack) { + this.stack = new SVType[maxStack]; + } + + private StackState(int pos, SVType[] stack) { + this.pos = pos; + this.stack = stack; + } + + public StackState copy() { + return new StackState(pos, Arrays.copyOf(stack, stack.length)); + } + + public int peek() { + return pos; + } + + public int peekAt(int at) { + return pos - at; + } + + public SVType peekTypeAt(int at) { + int p = pos - at; + if (checkStackIndex(p)) { + return stack[p]; + } + return SVType.NARROW; + } + + public int push(SVType type) { + int p = ++pos; + if (checkStackIndex(p)) { + stack[p] = type; + } + return p; + } + + private boolean checkStackIndex(int p) { + return p >= 0 && p < stack.length; + } + + public int pop() { + return pos--; + } + + public void clear() { + pos = -1; + } + + @Override + public String toString() { + int size = pos + 1; + String arr; + if (size == 0) { + arr = "empty"; + } else if (size > 0 && size < stack.length) { + arr = Arrays.toString(Arrays.copyOf(stack, size)); + } else { + arr = Arrays.toString(stack) + " (max)"; + } + return "Stack: " + size + ": " + arr; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/IJavaInsnDecoder.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/IJavaInsnDecoder.java new file mode 100644 index 00000000..fb0fa179 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/IJavaInsnDecoder.java @@ -0,0 +1,10 @@ +package jadx.plugins.input.java.data.code.decoders; + +import jadx.plugins.input.java.data.code.CodeDecodeState; + +public interface IJavaInsnDecoder { + void decode(CodeDecodeState state); + + default void skip(CodeDecodeState state) { + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/InvokeDecoder.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/InvokeDecoder.java new file mode 100644 index 00000000..ee6f2c51 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/InvokeDecoder.java @@ -0,0 +1,85 @@ +package jadx.plugins.input.java.data.code.decoders; + +import jadx.api.plugins.input.data.ICallSite; +import jadx.api.plugins.input.data.IMethodProto; +import jadx.api.plugins.input.data.IMethodRef; +import jadx.api.plugins.input.insns.Opcode; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.code.CodeDecodeState; +import jadx.plugins.input.java.data.code.JavaInsnData; + +public class InvokeDecoder implements IJavaInsnDecoder { + private final int payloadSize; + private final Opcode apiOpcode; + + public InvokeDecoder(int payloadSize, Opcode apiOpcode) { + this.payloadSize = payloadSize; + this.apiOpcode = apiOpcode; + } + + @Override + public void decode(CodeDecodeState state) { + DataReader reader = state.reader(); + int mthIdx = reader.readS2(); + if (payloadSize == 4) { + reader.skip(2); + } + JavaInsnData insn = state.insn(); + insn.setIndex(mthIdx); + boolean instanceCall; + IMethodProto mthProto; + if (apiOpcode == Opcode.INVOKE_CUSTOM) { + ICallSite callSite = insn.getIndexAsCallSite(); + insn.setPayload(callSite); + mthProto = (IMethodProto) callSite.getValues().get(2).getValue(); + instanceCall = false; // 'this' arg already included in proto args + } else { + IMethodRef mthRef = insn.getIndexAsMethod(); + mthRef.load(); + insn.setPayload(mthRef); + mthProto = mthRef; + instanceCall = apiOpcode != Opcode.INVOKE_STATIC; + } + + int argsCount = mthProto.getArgTypes().size(); + if (instanceCall) { + argsCount++; + } + insn.setRegsCount(argsCount * 2); // allocate twice of the size for worst case + int[] regs = insn.getRegsArray(); + + // calculate actual count of registers + // set '1' in regs to be filled with stack values later, '0' for skip + int regsCount = 0; + if (instanceCall) { + regs[regsCount++] = 1; + } + for (String type : mthProto.getArgTypes()) { + int size = getRegsCountForType(type); + regs[regsCount++] = 1; + if (size == 2) { + regs[regsCount++] = 0; + } + } + insn.setRegsCount(regsCount); + for (int i = regsCount - 1; i >= 0; i--) { + if (regs[i] == 1) { + state.pop(i); + } + } + String returnType = mthProto.getReturnType(); + if (!returnType.equals("V")) { + insn.setResultReg(state.push(returnType)); + } else { + insn.setResultReg(-1); + } + } + + private int getRegsCountForType(String type) { + char c = type.charAt(0); + if (c == 'J' || c == 'D') { + return 2; + } + return 1; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/LoadConstDecoder.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/LoadConstDecoder.java new file mode 100644 index 00000000..bacbb82e --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/LoadConstDecoder.java @@ -0,0 +1,68 @@ +package jadx.plugins.input.java.data.code.decoders; + +import jadx.api.plugins.input.insns.Opcode; +import jadx.plugins.input.java.data.ConstPoolReader; +import jadx.plugins.input.java.data.ConstantType; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.code.CodeDecodeState; +import jadx.plugins.input.java.data.code.JavaInsnData; +import jadx.plugins.input.java.data.code.StackState.SVType; +import jadx.plugins.input.java.utils.JavaClassParseException; + +public class LoadConstDecoder implements IJavaInsnDecoder { + private final boolean wide; + + public LoadConstDecoder(boolean wide) { + this.wide = wide; + } + + @Override + public void decode(CodeDecodeState state) { + DataReader reader = state.reader(); + JavaInsnData insn = state.insn(); + int index; + if (wide) { + index = reader.readU2(); + } else { + index = reader.readU1(); + } + ConstPoolReader constPoolReader = insn.constPoolReader(); + ConstantType constType = constPoolReader.jumpToConst(index); + switch (constType) { + case INTEGER: + case FLOAT: + insn.setLiteral(constPoolReader.readU4()); + insn.setOpcode(Opcode.CONST); + state.push(0, SVType.NARROW); + break; + + case LONG: + case DOUBLE: + insn.setLiteral(constPoolReader.readU8()); + insn.setOpcode(Opcode.CONST_WIDE); + state.push(0, SVType.WIDE); + break; + + case STRING: + insn.setIndex(constPoolReader.readU2()); + insn.setOpcode(Opcode.CONST_STRING); + state.push(0, SVType.NARROW); + break; + + case UTF8: + insn.setIndex(index); + insn.setOpcode(Opcode.CONST_STRING); + state.push(0, SVType.NARROW); + break; + + case CLASS: + insn.setIndex(index); + insn.setOpcode(Opcode.CONST_CLASS); + state.push(0, SVType.NARROW); + break; + + default: + throw new JavaClassParseException("Unsupported constant type: " + constType); + } + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/LookupSwitchDecoder.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/LookupSwitchDecoder.java new file mode 100644 index 00000000..22d2f85e --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/LookupSwitchDecoder.java @@ -0,0 +1,46 @@ +package jadx.plugins.input.java.data.code.decoders; + +import jadx.api.plugins.input.insns.custom.impl.SwitchPayload; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.code.CodeDecodeState; +import jadx.plugins.input.java.data.code.JavaInsnData; + +public class LookupSwitchDecoder implements IJavaInsnDecoder { + + @Override + public void decode(CodeDecodeState state) { + read(state, false); + } + + @Override + public void skip(CodeDecodeState state) { + read(state, true); + } + + private static void read(CodeDecodeState state, boolean skip) { + DataReader reader = state.reader(); + JavaInsnData insn = state.insn(); + int dataOffset = reader.getOffset(); + int insnOffset = insn.getOffset(); + reader.skip(3 - insnOffset % 4); + int defTarget = insnOffset + reader.readS4(); + int pairs = reader.readS4(); + if (skip) { + reader.skip(pairs * 8); + } else { + state.pop(0); + int[] keys = new int[pairs]; + int[] targets = new int[pairs]; + for (int i = 0; i < pairs; i++) { + keys[i] = reader.readS4(); + int target = insnOffset + reader.readS4(); + targets[i] = target; + state.registerJump(target); + } + insn.setTarget(defTarget); + state.registerJump(defTarget); + insn.setPayload(new SwitchPayload(pairs, keys, targets)); + } + insn.setPayloadSize(reader.getOffset() - dataOffset); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/TableSwitchDecoder.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/TableSwitchDecoder.java new file mode 100644 index 00000000..a57b46c9 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/decoders/TableSwitchDecoder.java @@ -0,0 +1,48 @@ +package jadx.plugins.input.java.data.code.decoders; + +import jadx.api.plugins.input.insns.custom.impl.SwitchPayload; +import jadx.plugins.input.java.data.DataReader; +import jadx.plugins.input.java.data.code.CodeDecodeState; +import jadx.plugins.input.java.data.code.JavaInsnData; + +public class TableSwitchDecoder implements IJavaInsnDecoder { + + @Override + public void decode(CodeDecodeState state) { + read(state, false); + } + + @Override + public void skip(CodeDecodeState state) { + read(state, true); + } + + private static void read(CodeDecodeState state, boolean skip) { + DataReader reader = state.reader(); + JavaInsnData insn = state.insn(); + int dataOffset = reader.getOffset(); + int insnOffset = insn.getOffset(); + reader.skip(3 - insnOffset % 4); + int defTarget = insnOffset + reader.readS4(); + int low = reader.readS4(); + int high = reader.readS4(); + int count = high - low + 1; + if (skip) { + reader.skip(count * 4); + } else { + state.pop(0); + int[] keys = new int[count]; + int[] targets = new int[count]; + for (int i = 0; i < count; i++) { + int target = insnOffset + reader.readS4(); + keys[i] = low + i; + targets[i] = target; + state.registerJump(target); + } + insn.setTarget(defTarget); + state.registerJump(defTarget); + insn.setPayload(new SwitchPayload(count, keys, targets)); + } + insn.setPayloadSize(reader.getOffset() - dataOffset); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaSingleCatch.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaSingleCatch.java new file mode 100644 index 00000000..da743a5b --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaSingleCatch.java @@ -0,0 +1,21 @@ +package jadx.plugins.input.java.data.code.trycatch; + +import org.jetbrains.annotations.Nullable; + +public class JavaSingleCatch { + private final int handler; + private final @Nullable String type; + + public JavaSingleCatch(int handler, @Nullable String type) { + this.handler = handler; + this.type = type; + } + + public int getHandler() { + return handler; + } + + public @Nullable String getType() { + return type; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaTryData.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaTryData.java new file mode 100644 index 00000000..9172ea41 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaTryData.java @@ -0,0 +1,57 @@ +package jadx.plugins.input.java.data.code.trycatch; + +import jadx.api.plugins.input.data.ICatch; +import jadx.api.plugins.input.data.ITry; + +public class JavaTryData implements ITry { + + private final int startAddr; + private final int endAddr; + private ICatch catchHandler; + + public JavaTryData(int startAddr, int endAddr) { + this.startAddr = startAddr; + this.endAddr = endAddr; + } + + @Override + public ICatch getCatch() { + return catchHandler; + } + + public void setCatch(ICatch catchHandler) { + this.catchHandler = catchHandler; + } + + @Override + public int getStartAddress() { + return startAddr; + } + + @Override + public int getEndAddress() { + return endAddr; + } + + @Override + public int hashCode() { + return startAddr + 31 * endAddr; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof JavaTryData)) { + return false; + } + JavaTryData that = (JavaTryData) o; + return startAddr == that.startAddr && endAddr == that.endAddr; + } + + @Override + public String toString() { + return "Try{" + startAddr + " - " + endAddr + ": " + catchHandler + '}'; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DescriptorParser.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DescriptorParser.java new file mode 100644 index 00000000..a15554a2 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DescriptorParser.java @@ -0,0 +1,111 @@ +package jadx.plugins.input.java.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jadx.plugins.input.java.data.JavaMethodProto; + +public class DescriptorParser { + + public static void fillMethodProto(String mthDesc, JavaMethodProto mthProto) { + new DescriptorParser(mthDesc).parseMethodDescriptor(mthProto); + } + + public static JavaMethodProto parseToMethodProto(String mthDesc) { + JavaMethodProto mthProto = new JavaMethodProto(); + new DescriptorParser(mthDesc).parseMethodDescriptor(mthProto); + return mthProto; + } + + private final String desc; + private int pos; + + private DescriptorParser(String desc) { + this.desc = desc; + } + + private void parseMethodDescriptor(JavaMethodProto mthProto) { + validate('('); + if (check(')')) { + mthProto.setArgTypes(Collections.emptyList()); + } else { + mthProto.setArgTypes(readArgsList()); + } + validate(')'); + mthProto.setReturnType(readType()); + } + + private List readArgsList() { + List list = new ArrayList<>(5); + do { + list.add(readType()); + } while (!check(')')); + return list; + } + + private String readType() { + int cur = pos; + if (cur >= desc.length()) { + return null; + } + char ch = desc.charAt(cur); + switch (ch) { + case 'L': + int end = desc.indexOf(';', cur); + if (end == -1) { + throw new JavaClassParseException("Unexpected object type descriptor: " + desc); + } + int lastChar = end + 1; + String type = desc.substring(cur, lastChar); + pos = lastChar; + return type; + + case '[': + pos++; + return "[" + readType(); + + default: + String primitiveType = parsePrimitiveType(ch); + pos = cur + 1; + return primitiveType; + } + } + + public String parsePrimitiveType(char f) { + switch (f) { + case 'Z': + return "Z"; + case 'B': + return "B"; + case 'C': + return "C"; + case 'S': + return "S"; + case 'I': + return "I"; + case 'J': + return "J"; + case 'F': + return "F"; + case 'D': + return "D"; + case 'V': + return "V"; + + default: + throw new JavaClassParseException("Unexpected char '" + f + "' in descriptor " + desc); + } + } + + private boolean check(char exp) { + return desc.charAt(pos) == exp; + } + + private void validate(char exp) { + if (!check(exp)) { + throw new JavaClassParseException("Unexpected char in descriptor: " + desc + " at pos " + pos + ", expected: " + exp); + } + pos++; + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java new file mode 100644 index 00000000..f8c7ba44 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java @@ -0,0 +1,56 @@ +package jadx.plugins.input.java.utils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DisasmUtils { + private static final Logger LOG = LoggerFactory.getLogger(DisasmUtils.class); + + /** + * Use javap as a temporary disassembler for java bytecode + */ + public static String get(byte[] bytes) { + try { + Path tmpCls = null; + try { + tmpCls = Files.createTempFile("jadx", ".class"); + Files.write(tmpCls, bytes, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + + Process process = Runtime.getRuntime().exec(new String[] { + "javap", "-constants", "-v", "-p", "-c", + tmpCls.toAbsolutePath().toString() + }); + process.waitFor(2, TimeUnit.SECONDS); + return inputStreamToString(process.getInputStream()); + } finally { + if (tmpCls != null) { + Files.delete(tmpCls); + } + } + } catch (Exception e) { + LOG.error("Java class disasm error", e); + return "error"; + } + } + + public static String inputStreamToString(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[8 * 1024]; + while (true) { + int r = in.read(buf); + if (r == -1) { + break; + } + out.write(buf, 0, r); + } + return out.toString(); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/JavaClassParseException.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/JavaClassParseException.java new file mode 100644 index 00000000..279625d9 --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/JavaClassParseException.java @@ -0,0 +1,13 @@ +package jadx.plugins.input.java.utils; + +public class JavaClassParseException extends RuntimeException { + private static final long serialVersionUID = -8452845601753645491L; + + public JavaClassParseException(String message, Throwable cause) { + super(message, cause); + } + + public JavaClassParseException(String message) { + super(message); + } +} diff --git a/jadx-plugins/jadx-java-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-java-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 00000000..2eea606f --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +jadx.plugins.input.java.JavaInputPlugin diff --git a/jadx-plugins/jadx-java-input/src/test/java/jadx/plugins/input/java/utils/DescriptorParserTest.java b/jadx-plugins/jadx-java-input/src/test/java/jadx/plugins/input/java/utils/DescriptorParserTest.java new file mode 100644 index 00000000..9d88903e --- /dev/null +++ b/jadx-plugins/jadx-java-input/src/test/java/jadx/plugins/input/java/utils/DescriptorParserTest.java @@ -0,0 +1,37 @@ +package jadx.plugins.input.java.utils; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import jadx.plugins.input.java.data.JavaMethodRef; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +class DescriptorParserTest { + + @Test + public void testPrimitives() { + check("()V", "V"); + check("(I)D", "D", "I"); + } + + @Test + public void testObjects() { + check("(Ljava/lang/String;Ljava/lang/Object;)V", "V", "Ljava/lang/String;", "Ljava/lang/Object;"); + } + + @SuppressWarnings("CatchMayIgnoreException") + private void check(String desc, String retType, String... argTypes) { + JavaMethodRef mthRef = new JavaMethodRef(); + try { + DescriptorParser.fillMethodProto(desc, mthRef); + } catch (Exception e) { + fail("Parse failed for: " + desc, e); + } + + assertThat(mthRef.getReturnType()).isEqualTo(retType); + assertThat(mthRef.getArgTypes()).isEqualTo(Arrays.asList(argTypes)); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java index ba2f7bca..97e66947 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java @@ -27,10 +27,21 @@ public class JadxPluginManager { public void register(JadxPlugin plugin) { Objects.requireNonNull(plugin); - LOG.debug("Loaded plugin: {}", plugin.getPluginInfo().getName()); + LOG.debug("Register plugin: {}", plugin.getPluginInfo().getPluginId()); allPlugins.put(plugin.getClass(), plugin); } + public boolean unload(String pluginId) { + return allPlugins.values().removeIf(p -> { + String id = p.getPluginInfo().getPluginId(); + boolean match = id.equals(pluginId); + if (match) { + LOG.debug("Unload plugin: {}", id); + } + return match; + }); + } + public List getAllPlugins() { return new ArrayList<>(allPlugins.values()); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java index 9f01c2c2..8634282f 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java @@ -19,6 +19,7 @@ public class AccessFlags { public static final int SYNTHETIC = 0x1000; public static final int ANNOTATION = 0x2000; public static final int ENUM = 0x4000; + public static final int MODULE = 0x8000; public static final int CONSTRUCTOR = 0x10000; public static final int DECLARED_SYNCHRONIZED = 0x20000; @@ -72,6 +73,9 @@ public class AccessFlags { break; case CLASS: + if (hasFlag(flags, MODULE)) { + code.append("module "); + } if (hasFlag(flags, STRICT)) { code.append("strict "); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICallSite.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICallSite.java index da2d1b6b..bdf59ad9 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICallSite.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICallSite.java @@ -3,8 +3,9 @@ package jadx.api.plugins.input.data; import java.util.List; import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.insns.custom.ICustomPayload; -public interface ICallSite { +public interface ICallSite extends ICustomPayload { List getValues(); diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java index 863e1884..ef308ba1 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java @@ -5,12 +5,13 @@ import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; -import jadx.api.plugins.input.data.annotations.EncodedValue; -import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; public interface IClassData { IClassData copy(); + String getInputFileName(); + String getType(); int getAccessFlags(); @@ -20,15 +21,9 @@ public interface IClassData { List getInterfacesTypes(); - String getSourceFile(); - - String getInputFileName(); - void visitFieldsAndMethods(Consumer fieldsConsumer, Consumer mthConsumer); - List getStaticFieldInitValues(); - - List getAnnotations(); + List getAttributes(); String getDisassembledCode(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java index bf300b56..55b4d349 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java @@ -14,7 +14,9 @@ public interface ICodeReader { int getRegistersCount(); - int getInsnsCount(); + int getArgsStartReg(); + + int getUnitsCount(); @Nullable IDebugInfo getDebugInfo(); diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java index 99c59d74..45fb2176 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java @@ -2,16 +2,11 @@ package jadx.api.plugins.input.data; import java.util.List; -import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; -public interface IFieldData { - String getParentClassType(); - - String getType(); - - String getName(); +public interface IFieldData extends IFieldRef { int getAccessFlags(); - List getAnnotations(); + List getAttributes(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldRef.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldRef.java new file mode 100644 index 00000000..84074283 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldRef.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.data; + +public interface IFieldRef { + String getParentClassType(); + + String getName(); + + String getType(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java index 874d2c95..ae29956b 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java @@ -4,7 +4,7 @@ import java.util.List; import org.jetbrains.annotations.Nullable; -import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; public interface IMethodData { @@ -12,14 +12,10 @@ public interface IMethodData { int getAccessFlags(); - boolean isDirect(); - @Nullable ICodeReader getCodeReader(); String disassembleMethod(); - List getAnnotations(); - - List> getParamsAnnotations(); + List getAttributes(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodHandle.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodHandle.java index 9c936e95..2c41f2b1 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodHandle.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodHandle.java @@ -4,7 +4,7 @@ public interface IMethodHandle { MethodHandleType getType(); - IFieldData getFieldRef(); + IFieldRef getFieldRef(); IMethodRef getMethodRef(); diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodRef.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodRef.java index d4aa3e9b..6c4e6caa 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodRef.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodRef.java @@ -1,8 +1,8 @@ package jadx.api.plugins.input.data; -import java.util.List; +import jadx.api.plugins.input.insns.custom.ICustomPayload; -public interface IMethodRef { +public interface IMethodRef extends IMethodProto, ICustomPayload { int getUniqId(); @@ -14,8 +14,4 @@ public interface IMethodRef { String getParentClassType(); String getName(); - - String getReturnType(); - - List getArgTypes(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java index 85f54ff4..578aa6b3 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java @@ -5,5 +5,5 @@ public interface ITry { int getStartAddress(); - int getInstructionCount(); + int getEndAddress(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java index 8c799f73..8969689d 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java @@ -2,7 +2,11 @@ package jadx.api.plugins.input.data.annotations; import java.util.Objects; -public class EncodedValue { +import jadx.api.plugins.input.data.attributes.IJadxAttrType; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.api.plugins.input.data.attributes.JadxAttrType; + +public class EncodedValue implements IJadxAttribute { public static final EncodedValue NULL = new EncodedValue(EncodedType.ENCODED_NULL, null); private final EncodedType type; @@ -33,6 +37,11 @@ public class EncodedValue { return type == that.getType() && Objects.equals(value, that.getValue()); } + @Override + public IJadxAttrType getAttrType() { + return JadxAttrType.CONSTANT_VALUE; + } + @Override public int hashCode() { return Objects.hash(getType(), getValue()); diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/JadxAnnotation.java similarity index 50% rename from jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java rename to jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/JadxAnnotation.java index d4b42b60..8447219d 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/JadxAnnotation.java @@ -1,17 +1,13 @@ -package jadx.plugins.input.dex.sections.annotations; +package jadx.api.plugins.input.data.annotations; import java.util.Map; -import jadx.api.plugins.input.data.annotations.AnnotationVisibility; -import jadx.api.plugins.input.data.annotations.EncodedValue; -import jadx.api.plugins.input.data.annotations.IAnnotation; - -public class DexAnnotation implements IAnnotation { +public class JadxAnnotation implements IAnnotation { private final AnnotationVisibility visibility; private final String type; private final Map values; - public DexAnnotation(AnnotationVisibility visibility, String type, Map values) { + public JadxAnnotation(AnnotationVisibility visibility, String type, Map values) { this.visibility = visibility; this.type = type; this.values = values; @@ -34,6 +30,6 @@ public class DexAnnotation implements IAnnotation { @Override public String toString() { - return "DexAnnotation{" + visibility + ", type=" + type + ", values=" + values + '}'; + return "Annotation{" + visibility + ", type=" + type + ", values=" + values + '}'; } } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttrType.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttrType.java new file mode 100644 index 00000000..96c3c2aa --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttrType.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.data.attributes; + +/** + * Marker interface for attribute type. + * Used for attach attribute instance class information (T). + * T - class of attribute instance + */ +public interface IJadxAttrType { +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttribute.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttribute.java new file mode 100644 index 00000000..71f17138 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttribute.java @@ -0,0 +1,17 @@ +package jadx.api.plugins.input.data.attributes; + +public interface IJadxAttribute { + + IJadxAttrType getAttrType(); + + /** + * Mark type to skip unloading on node unload + */ + default boolean keepLoaded() { + return false; + } + + default String toAttrString() { + return this.toString(); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/JadxAttrType.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/JadxAttrType.java new file mode 100644 index 00000000..a6449692 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/JadxAttrType.java @@ -0,0 +1,38 @@ +package jadx.api.plugins.input.data.attributes; + +import jadx.api.plugins.input.data.annotations.EncodedValue; +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.AnnotationsAttr; +import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr; +import jadx.api.plugins.input.data.attributes.types.InnerClassesAttr; +import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr; +import jadx.api.plugins.input.data.attributes.types.SignatureAttr; +import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; + +public final class JadxAttrType implements IJadxAttrType { + + // class, method, field + public static final JadxAttrType ANNOTATION_LIST = bind(); + public static final JadxAttrType SIGNATURE = bind(); + + // class + public static final JadxAttrType SOURCE_FILE = bind(); + public static final JadxAttrType INNER_CLASSES = bind(); + public static final JadxAttrType ANNOTATION_DEFAULT_CLASS = bind(); // dex specific + + // field + public static final JadxAttrType CONSTANT_VALUE = bind(); + + // method + public static final JadxAttrType ANNOTATION_MTH_PARAMETERS = bind(); + public static final JadxAttrType ANNOTATION_DEFAULT = bind(); + public static final JadxAttrType EXCEPTIONS = bind(); + + private static JadxAttrType bind() { + return new JadxAttrType<>(); + } + + private JadxAttrType() { + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/PinnedAttribute.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/PinnedAttribute.java new file mode 100644 index 00000000..550c9a37 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/PinnedAttribute.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.data.attributes; + +public abstract class PinnedAttribute implements IJadxAttribute { + + @Override + public final boolean keepLoaded() { + return true; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationDefaultAttr.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationDefaultAttr.java new file mode 100644 index 00000000..464af404 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationDefaultAttr.java @@ -0,0 +1,30 @@ +package jadx.api.plugins.input.data.attributes.types; + +import jadx.api.plugins.input.data.annotations.EncodedValue; +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.PinnedAttribute; + +public class AnnotationDefaultAttr extends PinnedAttribute { + + private final EncodedValue value; + + public AnnotationDefaultAttr(EncodedValue value) { + this.value = value; + } + + public EncodedValue getValue() { + return value; + } + + @Override + public IJadxAttrType getAttrType() { + return JadxAttrType.ANNOTATION_DEFAULT; + } + + @Override + public String toString() { + return "ANNOTATION_DEFAULT: " + value; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationDefaultClassAttr.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationDefaultClassAttr.java new file mode 100644 index 00000000..f8beab45 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationDefaultClassAttr.java @@ -0,0 +1,32 @@ +package jadx.api.plugins.input.data.attributes.types; + +import java.util.Map; + +import jadx.api.plugins.input.data.annotations.EncodedValue; +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.PinnedAttribute; + +public class AnnotationDefaultClassAttr extends PinnedAttribute { + + private final Map values; + + public AnnotationDefaultClassAttr(Map values) { + this.values = values; + } + + public Map getValues() { + return values; + } + + @Override + public IJadxAttrType getAttrType() { + return JadxAttrType.ANNOTATION_DEFAULT_CLASS; + } + + @Override + public String toString() { + return "ANNOTATION_DEFAULT_CLASS: " + values; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationsAttr.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationsAttr.java new file mode 100644 index 00000000..b84e104c --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationsAttr.java @@ -0,0 +1,71 @@ +package jadx.api.plugins.input.data.attributes.types; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.annotations.AnnotationVisibility; +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.attributes.JadxAttrType; +import jadx.api.plugins.input.data.attributes.PinnedAttribute; + +public class AnnotationsAttr extends PinnedAttribute { + + @Nullable + public static AnnotationsAttr pack(List annotationList) { + if (annotationList.isEmpty()) { + return null; + } + Map annMap = new HashMap<>(annotationList.size()); + for (IAnnotation ann : annotationList) { + if (ann.getVisibility() != AnnotationVisibility.SYSTEM) { + annMap.put(ann.getAnnotationClass(), ann); + } + } + if (annMap.isEmpty()) { + return null; + } + return new AnnotationsAttr(annMap); + } + + private final Map map; + + public AnnotationsAttr(Map map) { + this.map = map; + } + + public IAnnotation get(String className) { + return map.get(className); + } + + public Collection getAll() { + return map.values(); + } + + public List getList() { + return map.isEmpty() ? Collections.emptyList() : new ArrayList<>(map.values()); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public JadxAttrType getAttrType() { + return JadxAttrType.ANNOTATION_LIST; + } + + @Override + public String toString() { + return map.toString(); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/ExceptionsAttr.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/ExceptionsAttr.java new file mode 100644 index 00000000..0a343bff --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/ExceptionsAttr.java @@ -0,0 +1,29 @@ +package jadx.api.plugins.input.data.attributes.types; + +import java.util.List; + +import jadx.api.plugins.input.data.attributes.IJadxAttrType; +import jadx.api.plugins.input.data.attributes.JadxAttrType; +import jadx.api.plugins.input.data.attributes.PinnedAttribute; + +public class ExceptionsAttr extends PinnedAttribute { + private final List list; + + public ExceptionsAttr(List list) { + this.list = list; + } + + public List getList() { + return list; + } + + @Override + public IJadxAttrType getAttrType() { + return JadxAttrType.EXCEPTIONS; + } + + @Override + public String toString() { + return "EXCEPTIONS:" + list; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/InnerClassesAttr.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/InnerClassesAttr.java new file mode 100644 index 00000000..0947e075 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/InnerClassesAttr.java @@ -0,0 +1,30 @@ +package jadx.api.plugins.input.data.attributes.types; + +import java.util.Map; + +import jadx.api.plugins.input.data.attributes.IJadxAttrType; +import jadx.api.plugins.input.data.attributes.JadxAttrType; +import jadx.api.plugins.input.data.attributes.PinnedAttribute; + +public class InnerClassesAttr extends PinnedAttribute { + + private final Map map; + + public InnerClassesAttr(Map map) { + this.map = map; + } + + public Map getMap() { + return map; + } + + @Override + public IJadxAttrType getAttrType() { + return JadxAttrType.INNER_CLASSES; + } + + @Override + public String toString() { + return "INNER_CLASSES:" + map; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/InnerClsInfo.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/InnerClsInfo.java new file mode 100644 index 00000000..9275360b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/InnerClsInfo.java @@ -0,0 +1,45 @@ +package jadx.api.plugins.input.data.attributes.types; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.AccessFlagsScope; + +public class InnerClsInfo { + private final String innerCls; + private final @Nullable String outerCls; + private final @Nullable String name; + private final int accessFlags; + + public InnerClsInfo(String innerCls, @Nullable String outerCls, @Nullable String name, int accessFlags) { + this.innerCls = innerCls; + this.outerCls = outerCls; + this.name = name; + this.accessFlags = accessFlags; + } + + public String getInnerCls() { + return innerCls; + } + + public @Nullable String getOuterCls() { + return outerCls; + } + + public @Nullable String getName() { + return name; + } + + public int getAccessFlags() { + return accessFlags; + } + + @Override + public String toString() { + return "InnerCls{" + innerCls + + ", outerCls=" + outerCls + + ", name=" + name + + ", accessFlags=" + AccessFlags.format(accessFlags, AccessFlagsScope.CLASS) + + '}'; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/MethodParamsAttr.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/MethodParamsAttr.java new file mode 100644 index 00000000..8519dda8 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/MethodParamsAttr.java @@ -0,0 +1,45 @@ +package jadx.api.plugins.input.data.attributes.types; + +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.api.plugins.input.data.attributes.JadxAttrType; +import jadx.api.plugins.input.data.attributes.PinnedAttribute; + +public class MethodParamsAttr extends PinnedAttribute { + + @Nullable + public static MethodParamsAttr pack(List> annotationRefList) { + if (annotationRefList.isEmpty()) { + return null; + } + List list = new ArrayList<>(annotationRefList.size()); + for (List annList : annotationRefList) { + list.add(AnnotationsAttr.pack(annList)); + } + return new MethodParamsAttr(list); + } + + private final List paramList; + + private MethodParamsAttr(List paramsList) { + this.paramList = paramsList; + } + + public List getParamList() { + return paramList; + } + + @Override + public JadxAttrType getAttrType() { + return JadxAttrType.ANNOTATION_MTH_PARAMETERS; + } + + @Override + public String toString() { + return paramList.toString(); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/SignatureAttr.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/SignatureAttr.java new file mode 100644 index 00000000..fe9f34c0 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/SignatureAttr.java @@ -0,0 +1,29 @@ +package jadx.api.plugins.input.data.attributes.types; + +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.PinnedAttribute; + +public class SignatureAttr extends PinnedAttribute { + + private final String signature; + + public SignatureAttr(String signature) { + this.signature = signature; + } + + public String getSignature() { + return signature; + } + + @Override + public IJadxAttrType getAttrType() { + return JadxAttrType.SIGNATURE; + } + + @Override + public String toString() { + return "SIGNATURE: " + signature; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/SourceFileAttr.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/SourceFileAttr.java new file mode 100644 index 00000000..272d1824 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/attributes/types/SourceFileAttr.java @@ -0,0 +1,28 @@ +package jadx.api.plugins.input.data.attributes.types; + +import jadx.api.plugins.input.data.attributes.IJadxAttrType; +import jadx.api.plugins.input.data.attributes.JadxAttrType; +import jadx.api.plugins.input.data.attributes.PinnedAttribute; + +public class SourceFileAttr extends PinnedAttribute { + + private final String fileName; + + public SourceFileAttr(String fileName) { + this.fileName = fileName; + } + + public String getFileName() { + return fileName; + } + + @Override + public IJadxAttrType getAttrType() { + return JadxAttrType.SOURCE_FILE; + } + + @Override + public String toString() { + return "SOURCE:" + fileName; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCallSite.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CallSite.java similarity index 53% rename from jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCallSite.java rename to jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CallSite.java index 8d189a7f..1cb7f9b0 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCallSite.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CallSite.java @@ -1,4 +1,4 @@ -package jadx.plugins.input.dex.sections; +package jadx.api.plugins.input.data.impl; import java.util.List; @@ -7,33 +7,30 @@ import jadx.api.plugins.input.data.IMethodHandle; import jadx.api.plugins.input.data.IMethodRef; import jadx.api.plugins.input.data.annotations.EncodedValue; -public class DexCallSite implements ICallSite { +public class CallSite implements ICallSite { private final List values; - public DexCallSite(List values) { + public CallSite(List values) { this.values = values; } + @Override + public void load() { + for (EncodedValue value : values) { + switch (value.getType()) { + case ENCODED_METHOD_HANDLE: + ((IMethodHandle) value.getValue()).load(); + break; + case ENCODED_METHOD: + ((IMethodRef) value.getValue()).load(); + break; + } + } + } + @Override public List getValues() { return values; } - - @Override - public void load() { - for (EncodedValue value : values) { - Object obj = value.getValue(); - if (obj instanceof IMethodRef) { - ((IMethodRef) obj).load(); - } else if (obj instanceof IMethodHandle) { - ((IMethodHandle) obj).load(); - } - } - } - - @Override - public String toString() { - return "CallSite{" + values + '}'; - } } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CatchData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CatchData.java new file mode 100644 index 00000000..ba4f8f3f --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CatchData.java @@ -0,0 +1,43 @@ +package jadx.api.plugins.input.data.impl; + +import jadx.api.plugins.input.data.ICatch; + +public class CatchData implements ICatch { + private final int[] addr; + private final String[] types; + private final int allAddr; + + public CatchData(int[] addr, String[] types, int allAddr) { + this.addr = addr; + this.types = types; + this.allAddr = allAddr; + } + + @Override + public int[] getAddresses() { + return addr; + } + + @Override + public String[] getTypes() { + return types; + } + + @Override + public int getCatchAllAddress() { + return allAddr; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Catch:"); + int size = types.length; + for (int i = 0; i < size; i++) { + sb.append(' ').append(types[i]).append("->").append(addr[i]); + } + if (allAddr != -1) { + sb.append(" all->").append(allAddr); + } + return sb.toString(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/DebugInfo.java similarity index 82% rename from jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java rename to jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/DebugInfo.java index 955dfb45..e6a10da5 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/DebugInfo.java @@ -1,4 +1,4 @@ -package jadx.plugins.input.dex.sections.debuginfo; +package jadx.api.plugins.input.data.impl; import java.util.List; import java.util.Map; @@ -28,6 +28,6 @@ public class DebugInfo implements IDebugInfo { @Override public String toString() { - return "DebugInfo{sourceLineMap=" + sourceLineMap + ", localVars=" + localVars + '}'; + return "DebugInfo{lines=" + sourceLineMap + ", localVars=" + localVars + '}'; } } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/FieldRefHandle.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/FieldRefHandle.java index 4a2c9820..7fe6d39f 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/FieldRefHandle.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/FieldRefHandle.java @@ -1,16 +1,16 @@ package jadx.api.plugins.input.data.impl; -import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IFieldRef; import jadx.api.plugins.input.data.IMethodHandle; import jadx.api.plugins.input.data.IMethodRef; import jadx.api.plugins.input.data.MethodHandleType; public class FieldRefHandle implements IMethodHandle { - private final IFieldData fieldRef; + private final IFieldRef fieldRef; private final MethodHandleType type; - public FieldRefHandle(MethodHandleType type, IFieldData fieldRef) { + public FieldRefHandle(MethodHandleType type, IFieldRef fieldRef) { this.fieldRef = fieldRef; this.type = type; } @@ -21,7 +21,7 @@ public class FieldRefHandle implements IMethodHandle { } @Override - public IFieldData getFieldRef() { + public IFieldRef getFieldRef() { return fieldRef; } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/JadxFieldRef.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/JadxFieldRef.java new file mode 100644 index 00000000..a8b6c1b7 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/JadxFieldRef.java @@ -0,0 +1,50 @@ +package jadx.api.plugins.input.data.impl; + +import jadx.api.plugins.input.data.IFieldRef; + +public class JadxFieldRef implements IFieldRef { + private String parentClassType; + private String name; + private String type; + + public JadxFieldRef() { + } + + public JadxFieldRef(String parentClassType, String name, String type) { + this.parentClassType = parentClassType; + this.name = name; + this.type = type; + } + + @Override + public String getParentClassType() { + return parentClassType; + } + + public void setParentClassType(String parentClassType) { + this.parentClassType = parentClassType; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return parentClassType + "->" + name + ":" + type; + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/MethodRefHandle.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/MethodRefHandle.java index 5b3cebe7..8a9035ec 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/MethodRefHandle.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/MethodRefHandle.java @@ -7,12 +7,12 @@ import jadx.api.plugins.input.data.MethodHandleType; public class MethodRefHandle implements IMethodHandle { - private final IMethodRef methodRef; private final MethodHandleType type; + private final IMethodRef methodRef; public MethodRefHandle(MethodHandleType type, IMethodRef methodRef) { - this.methodRef = methodRef; this.type = type; + this.methodRef = methodRef; } @Override diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/TryData.java similarity index 50% rename from jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java rename to jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/TryData.java index fcb606c2..f247e032 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/TryData.java @@ -1,18 +1,18 @@ -package jadx.plugins.input.dex.sections.trycatch; +package jadx.api.plugins.input.data.impl; import jadx.api.plugins.input.data.ICatch; import jadx.api.plugins.input.data.ITry; -public class DexTryData implements ITry { +public class TryData implements ITry { - private final ICatch catchHandler; private final int startAddr; - private final int insnsCount; + private final int endAddr; + private final ICatch catchHandler; - public DexTryData(ICatch catchHandler, int startAddr, int insnsCount) { - this.catchHandler = catchHandler; + public TryData(int startAddr, int endAddr, ICatch catchHandler) { this.startAddr = startAddr; - this.insnsCount = insnsCount; + this.endAddr = endAddr; + this.catchHandler = catchHandler; } @Override @@ -26,7 +26,12 @@ public class DexTryData implements ITry { } @Override - public int getInstructionCount() { - return insnsCount; + public int getEndAddress() { + return endAddr; + } + + @Override + public String toString() { + return "Try{" + startAddr + " - " + endAddr + ": " + catchHandler + '}'; } } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java index 6a9e6c06..51181d80 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java @@ -1,8 +1,10 @@ package jadx.api.plugins.input.insns; -import org.jetbrains.annotations.Nullable; - -import jadx.api.plugins.input.data.*; +import jadx.api.plugins.input.data.ICallSite; +import jadx.api.plugins.input.data.IFieldRef; +import jadx.api.plugins.input.data.IMethodHandle; +import jadx.api.plugins.input.data.IMethodProto; +import jadx.api.plugins.input.data.IMethodRef; import jadx.api.plugins.input.insns.custom.ICustomPayload; public interface InsnData { @@ -25,6 +27,13 @@ public interface InsnData { int getReg(int argNum); + /** + * Workaround to set result reg without additional move-result insn + * + * @return result reg number or -1 if not needed + */ + int getResultReg(); + long getLiteral(); int getTarget(); @@ -35,7 +44,7 @@ public interface InsnData { String getIndexAsType(); - IFieldData getIndexAsField(); + IFieldRef getIndexAsField(); IMethodRef getIndexAsMethod(); @@ -45,6 +54,5 @@ public interface InsnData { IMethodHandle getIndexAsMethodHandle(); - @Nullable ICustomPayload getPayload(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java index 1153c796..814f33e2 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java @@ -5,5 +5,6 @@ public enum InsnIndexType { TYPE_REF, STRING_REF, FIELD_REF, - METHOD_REF + METHOD_REF, + CALL_SITE } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java index 003c2731..adf2a15d 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java @@ -17,6 +17,7 @@ public enum Opcode { AGET, AGET_BOOLEAN, AGET_BYTE, + AGET_BYTE_BOOLEAN, AGET_CHAR, AGET_OBJECT, AGET_SHORT, @@ -25,6 +26,7 @@ public enum Opcode { APUT, APUT_BOOLEAN, APUT_BYTE, + APUT_BYTE_BOOLEAN, APUT_CHAR, APUT_OBJECT, APUT_SHORT, @@ -40,8 +42,6 @@ public enum Opcode { CMPG_FLOAT, CMPL_DOUBLE, CMPL_FLOAT, - CMP_G, - CMP_L, CMP_LONG, CONST, @@ -112,6 +112,7 @@ public enum Opcode { MONITOR_EXIT, MOVE, + MOVE_MULTI, MOVE_EXCEPTION, MOVE_OBJECT, MOVE_RESULT, @@ -130,7 +131,6 @@ public enum Opcode { NEG_LONG, NEW_INSTANCE, - NOT, NOT_INT, NOT_LONG, @@ -172,11 +172,9 @@ public enum Opcode { FILLED_NEW_ARRAY, FILLED_NEW_ARRAY_RANGE, - FILL_ARRAY, FILL_ARRAY_DATA, FILL_ARRAY_DATA_PAYLOAD, - SWITCH, PACKED_SWITCH, PACKED_SWITCH_PAYLOAD, SPARSE_SWITCH, diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/impl/SwitchPayload.java similarity index 69% rename from jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java rename to jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/impl/SwitchPayload.java index 1b0889b9..c9bb04af 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/impl/SwitchPayload.java @@ -1,14 +1,14 @@ -package jadx.plugins.input.dex.insns.payloads; +package jadx.api.plugins.input.insns.custom.impl; import jadx.api.plugins.input.insns.custom.ISwitchPayload; -public class DexSwitchPayload implements ISwitchPayload { +public class SwitchPayload implements ISwitchPayload { private final int size; private final int[] keys; private final int[] targets; - public DexSwitchPayload(int size, int[] keys, int[] targets) { + public SwitchPayload(int size, int[] keys, int[] targets) { this.size = size; this.keys = keys; this.targets = targets; diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/Utils.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/Utils.java new file mode 100644 index 00000000..9e78bb98 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/Utils.java @@ -0,0 +1,69 @@ +package jadx.api.plugins.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; + +public class Utils { + + public static void addToList(List list, @Nullable T item) { + if (item != null) { + list.add(item); + } + } + + public static void addToList(List list, @Nullable I item, Function map) { + if (item != null) { + T value = map.apply(item); + if (value != null) { + list.add(value); + } + } + } + + public static List concat(List a, List b) { + int aSize = a.size(); + int bSize = b.size(); + if (aSize == 0 && bSize == 0) { + return Collections.emptyList(); + } + if (aSize == 0) { + return b; + } + if (bSize == 0) { + return a; + } + List list = new ArrayList<>(aSize + bSize); + list.addAll(a); + list.addAll(b); + return list; + } + + public static String listToStr(List list) { + if (list == null) { + return "null"; + } + if (list.isEmpty()) { + return ""; + } + if (list.size() == 1) { + return Objects.toString(list.get(0)); + } + StringBuilder sb = new StringBuilder(); + Iterator it = list.iterator(); + sb.append(it.next()); + while (it.hasNext()) { + sb.append(", ").append(it.next()); + } + return sb.toString(); + } + + public static String formatOffset(int offset) { + return String.format("0x%04x", offset); + } +} diff --git a/settings.gradle b/settings.gradle index 96feead1..f9b2f634 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,5 +7,6 @@ include 'jadx-samples' include 'jadx-plugins' include 'jadx-plugins:jadx-plugins-api' include 'jadx-plugins:jadx-dex-input' +include 'jadx-plugins:jadx-java-input' include 'jadx-plugins:jadx-smali-input' include 'jadx-plugins:jadx-java-convert'